Skip to content

SendGrid

Fromenance is a communication provenance platform, and the SendGrid adapter turns Event Webhook processed and delivered events into registrations. SendGrid does not expose the rendered body, so the adapter uses the reserve flow: you fetch a code before the send, place it through a substitution tag, and the event completes the registration.

By the end you will have a SendGrid source, the Signed Event Webhook pointed at it, the reserve call in your send path, and completions showing on the source’s health panel.

Because SendGrid signs with a public key rather than a shared secret, paste SendGrid’s Verification Key into the source when you create it. Get it first (step 2) or create the source now and update config later.

Terminal window
curl -X POST https://api.fromenance.com/v1/sending-sources \
-H "Authorization: Bearer fr_live_..." \
-H "Content-Type: application/json" \
-d '{"name":"SendGrid alerts","kind":"sendgrid","config":{"verification_key":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE..."},"expected_daily_volume":50000}'

The response includes webhook_url (https://api.fromenance.com/v1/hooks/esp/src_...). No shared secret is returned for SendGrid sources; the verification key is the credential. In the admin app the field is labelled Verification key on the Add source form.

2. Configure the Event Webhook at SendGrid

Section titled “2. Configure the Event Webhook at SendGrid”
  1. SendGrid, Settings, Mail Settings, Event Webhook, Create new webhook (or edit the existing one).
  2. HTTP Post URL: the webhook_url from step 1.
  3. Actions to be posted: tick Processed and Delivered. Other events are ignored; ticking them costs nothing but traffic.
  4. Save, then open Signature verification, enable Signed Event Webhook, and copy the Verification Key (a base64 ECDSA P-256 public key). Put it in the source’s config.verification_key if you did not at creation:
Terminal window
curl -X PATCH https://api.fromenance.com/v1/sending-sources/src_... \
-H "Authorization: Bearer fr_live_..." \
-H "Content-Type: application/json" \
-d '{"config":{"verification_key":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE..."}}'
  1. Use Test Your Integration; a 200 confirms the URL is reachable. Signature failures show as 401 and count on the source.

What SendGrid sends and how it is verified

Section titled “What SendGrid sends and how it is verified”

SendGrid signs each POST with ECDSA over timestamp + body and sends X-Twilio-Email-Event-Webhook-Signature (DER, base64) and X-Twilio-Email-Event-Webhook-Timestamp. Fromenance verifies the signature against your verification key with SHA-256. A request without both headers, or that fails verification, is rejected with 401 and signature_failures increments.

From each processed or delivered event:

Registration field SendGrid field
message_id sg_message_id up to .filter, or smtp-id
recipient (hashed immediately, never stored) email
sent_at timestamp (Unix seconds)
verify code fromenance_code (from custom_args)
template_id template_id from custom_args, else the first category
campaign_id campaign_id from custom_args
content fingerprint not available from SendGrid; add it later with PATCH /v1/communications/{id} if you render the body yourself

Before each send, reserve a code for the recipient and pass it both in the footer and in custom_args so the event can complete the reservation:

import { Fromenance } from "@fromenance/sdk";
import sgMail from "@sendgrid/mail";
const fr = new Fromenance({ apiKey: process.env.FROMENANCE_API_KEY!, tenantSecret: process.env.FROMENANCE_TENANT_SECRET! });
const reserved = await fr.reserve({ to: customer.email, template_id: "fraud-alert-v3", provider: "sendgrid" });
await sgMail.send({
to: customer.email,
from: "alerts@northfieldbank.example",
templateId: "d-...",
dynamicTemplateData: { first_name: customer.firstName, fromenance_code: reserved.verify_code },
customArgs: { fromenance_code: reserved.verify_code, template_id: "fraud-alert-v3" },
});

In the template footer, use the substitution tag that matches your template type: {{fromenance_code}} in dynamic (Handlebars) templates, -fromenance_code- in legacy templates with substitutions. The code must appear twice:

Not sure this email is from Northfield Bank? Forward it to verify@northfieldbank.example
or enter code {{fromenance_code}} at northfieldbank.example/verify. Reference: {{fromenance_code}}

A processed event with a fromenance_code that matches a reservation completes it with the message id and time. An event without a code still registers the message (with a code you never placed), so matching for that message relies on the recipient plus fingerprint, which SendGrid cannot supply. Always reserve.

  • Send test event on the source runs a synthetic event through the adapter.
  • Send one real message. health.completed increments and reserve_to_complete_ratio approaches 1.

silent is raised when no event arrives within silence_alert_minutes (default 1440). A falling reserve_to_complete_ratio means sends are reserving codes but the webhook is not delivering: check the Event Webhook status page in SendGrid. coverage_gaps counts reservations that expired after 24 hours without an event. Rotate the source with a new verification key by updating config; the rotate-secret call is not needed for SendGrid.