Skip to content

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.

Terminal window
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.

  1. Mailgun, Sending, your domain, Webhooks.
  2. Copy the HTTP webhook signing key shown at the top and put it on the source.
  3. Add webhook, event type Delivered Messages, URL the webhook_url from step 1. Optionally add Accepted as well; both are treated as sends and the second event for the same message and recipient is ignored.
  4. Save. Mailgun sends a test on save; a 200 confirms the URL.

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
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.

  • Send test event on the source runs a synthetic delivered event through the adapter.
  • Send one real message. health.completed increments within seconds of delivery.

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.