Mailgun
Fromenance is a communication provenance platform, and the Mailgun adapter turns delivered and accepted webhook events into registrations. Mailgun does not return the rendered body on events, so the adapter uses the reserve flow: you fetch a code before the send and pass it as the user variable v:fromenance_code.
By the end you will have a Mailgun source, the webhook configured for your sending domain, the signing key on the source, the reserve call in your send path, and events showing on the source’s health panel.
1. Create the source
Section titled “1. Create the source”curl -X POST https://api.fromenance.com/v1/sending-sources \ -H "Authorization: Bearer fr_live_..." \ -H "Content-Type: application/json" \ -d '{"name":"Mailgun alerts","kind":"mailgun","expected_daily_volume":30000}'The response includes webhook_url and a secret. Mailgun signs with its own HTTP webhook signing key, so paste that key into the source (Sources, the source, Signing key); the minted secret is replaced.
2. Configure the webhook at Mailgun
Section titled “2. Configure the webhook at Mailgun”- Mailgun, Sending, your domain, Webhooks.
- Copy the HTTP webhook signing key shown at the top and put it on the source.
- Add webhook, event type Delivered Messages, URL the
webhook_urlfrom step 1. Optionally add Accepted as well; both are treated as sends and the second event for the same message and recipient is ignored. - Save. Mailgun sends a test on save; a
200confirms the URL.
What Mailgun sends and how it is verified
Section titled “What Mailgun sends and how it is verified”Each POST carries a signature object with timestamp, token, and signature. Fromenance computes HMAC-SHA256 over timestamp + token with the signing key, compares in constant time, and rejects timestamps more than 5 minutes from now. A failure returns 401 and increments signature_failures.
From event-data of each delivered or accepted event:
| Registration field | Mailgun field |
|---|---|
message_id |
message.headers.message-id, wrapped in angle brackets |
| recipient (hashed immediately, never stored) | recipient |
sent_at |
timestamp |
from_address |
message.headers.from |
| subject fingerprint | message.headers.subject |
| verify code | user-variables.fromenance_code |
template_id |
user-variables.template_id, else the first tag |
campaign_id |
user-variables.campaign_id |
| content fingerprint | not available from Mailgun; add it later with PATCH /v1/communications/{id} if you render the body yourself |
3. Reserve, set the variable, send
Section titled “3. Reserve, set the variable, send”import FormData from "form-data";import Mailgun from "mailgun.js";import { Fromenance } from "@fromenance/sdk";
const fr = new Fromenance({ apiKey: process.env.FROMENANCE_API_KEY!, tenantSecret: process.env.FROMENANCE_TENANT_SECRET! });const mg = new Mailgun(FormData).client({ username: "api", key: process.env.MAILGUN_API_KEY! });
const reserved = await fr.reserve({ to: customer.email, template_id: "card-declined-v2", provider: "mailgun" });
await mg.messages.create("mg.northfieldbank.example", { from: "Northfield Bank <alerts@northfieldbank.example>", to: [customer.email], template: "card-declined-v2", "h:X-Mailgun-Variables": JSON.stringify({ first_name: customer.firstName, fromenance_code: reserved.verify_code }), "v:fromenance_code": reserved.verify_code, "v:template_id": "card-declined-v2",});In a Mailgun template use {{fromenance_code}} twice in the footer. For batch sends with recipient variables, use %recipient.fromenance_code% and supply a per recipient code from a batch of reservations. Variables set with the v: prefix are echoed back in user-variables on the event, which is how the reservation is completed.
4. Confirm
Section titled “4. Confirm”- Send test event on the source runs a synthetic delivered event through the adapter.
- Send one real message.
health.completedincrements within seconds of delivery.
Source health
Section titled “Source health”silent is raised when no event arrives within silence_alert_minutes. Mailgun retries failed webhook deliveries for 8 hours; a burst of signature_failures after rotating the signing key in Mailgun means the source still holds the old key. coverage_gaps counts reservations that expired without an event.