Skip to content

Resend

Fromenance is a communication provenance platform, and the Resend adapter turns Resend’s email.sent and email.delivered events into registrations without changing your application code. Resend exposes the rendered body through its API, so registrations made this way carry a full content fingerprint.

By the end you will have a Resend source with a webhook URL and secret, the Resend webhook pointed at it, the code placed in your template, and events showing on the source’s health panel.

Admin app: Sources, Add source, kind Resend. Or from the API with an admin key:

Terminal window
curl -X POST https://api.fromenance.com/v1/sending-sources \
-H "Authorization: Bearer fr_live_..." \
-H "Content-Type: application/json" \
-d '{"name":"Resend transactional","kind":"resend","config":{"api_key":"re_..."},"expected_daily_volume":20000,"silence_alert_minutes":120}'

The response includes webhook_url (https://api.fromenance.com/v1/hooks/esp/src_...) and secret, shown once. config.api_key is optional: a Resend API key with read access lets Fromenance fetch the rendered body of each sent email (GET https://api.resend.com/emails/{id}) so the registration gets a fingerprint. It is encrypted at rest with your tenant key.

  1. Resend dashboard, Webhooks, Add webhook.
  2. Endpoint URL: the webhook_url from step 1.
  3. Events: email.sent and email.delivered. Other event types are ignored.
  4. Save, then copy the Signing secret Resend shows (whsec_...) and paste it into the source in the admin app (Sources, the source, Signing secret). The secret Fromenance generated at creation is replaced by Resend’s, because Resend signs with its own key.

Resend signs webhooks through Svix. Each request carries svix-id, svix-timestamp, and svix-signature; Fromenance verifies the HMAC-SHA256 over id.timestamp.body with the signing secret and rejects requests outside the timestamp tolerance. A failed check returns 401 and increments signature_failures on the source.

From each event Fromenance reads, per recipient in data.to:

Registration field Resend field
message_id data.email_id
recipient (hashed immediately, never stored) each address in data.to
sent_at data.created_at
from_address data.from
subject fingerprint data.subject
verify code tag fromenance_code, or the X-Fromenance-Code header when Resend echoes headers
template_id, campaign_id tags template_id, campaign_id
content fingerprint fetched through the Messages API when config.api_key is set

The adapter does not require a reservation: a send event without a code registers the message and issues a code. But a code that is not in the footer cannot help the customer, so for Resend you have two options.

Option A, reserve before send (recommended). Call POST /v1/communications/reserve with the recipient, place the code in the template, and send with the tag so the event completes the reservation:

await resend.emails.send({
from: "alerts@northfieldbank.example",
to: [customer.email],
subject: "A new device signed in to your account",
html: render(template, { fromenance_code: reserved.verify_code }),
tags: [
{ name: "fromenance_code", value: reserved.verify_code },
{ name: "template_id", value: "new-device-alert-v2" },
],
});

Option B, register with the SDK and skip the webhook. If you already render the email in your code, @fromenance/sdk register() gives you the code before send with no webhook at all. Use the webhook when sends happen in systems you do not control.

  • Sources, the source, Send test event (POST /v1/sending-sources/{id}/test-event) runs a synthetic sent event through the adapter and creates a sandbox registration.
  • Send one real message. Within seconds health.last_event_at updates and registered_today increments.
Field Meaning
last_event_at, events_last_hour Webhook traffic
signature_failures Requests rejected for a bad Svix signature. A non zero and rising count means the secret in Fromenance does not match Resend
reserved, completed, reserve_to_complete_ratio Reservations versus sends that completed them
coverage_gaps Reservations that expired without a send event
silent No event within silence_alert_minutes (default 1440) while the source is active. Raises an alert in the admin app

Rotate the secret with POST /v1/sending-sources/{id}/rotate-secret if it leaks; Resend’s own signing secret is replaced from the Resend dashboard.