Documentation

Mailgun

Connect Mailgun for email delivery, marketing campaigns, and inbound routing.

Mailgun is the alternate email vendor in AppEngine, supported alongside SendGrid via the same Upstream/Connect abstraction. It's a drop-in replacement for transactional and marketing email — the application code that calls sendgrid-provider/send works against mailgun-provider/send with no changes other than the integration ID.

Connect Mailgun

Mailgun uses an API key plus a sending domain.

await fetch('/api/upstream/save-integration', {
  method: 'POST',
  headers: { orgid: ORG_ID, Authorization: `Bearer ${jwt}` },
  body: JSON.stringify({
    type: 'mailgun-provider',
    name: 'Mailgun Production',
    credentials: {
      apiKey: 'key-xxx',
      webhookSigningKey: 'whsk-xxx',
    },
    publicConfig: {
      domain: 'mg.example.com',
      region: 'us',
      defaultFromEmail: '[email protected]',
      defaultFromName: 'Example Co',
    },
  }),
});

region is us or eu and selects which Mailgun API host the connector talks to. domain is the sending domain you've authenticated in the Mailgun control panel.

Domain authentication

Mailgun requires SPF, DKIM, and a CNAME for tracking before sending mail at any scale. The Mailgun control panel exposes the records and verifies them. Use the platform's broadcast email-domain configurator to track this state in AppEngine:

POST/broadcast/email-domain/configureJWT

Once Mailgun reports the domain as verified, the integration's publicConfig.domainVerified: true is updated by the platform's verification job.

Sending email

await fetch('/api/upstream/call/mailgun-provider/send', {
  method: 'POST',
  headers: { orgid: ORG_ID, Authorization: `Bearer ${jwt}` },
  body: JSON.stringify({
    to: '[email protected]',
    subject: 'Your order has shipped',
    html: '<p>Tracking: ...</p>',
    text: 'Tracking: ...',
    from: '[email protected]',
    variables: { orderId: 'ord-12345' },
  }),
});

The variables field is Mailgun's equivalent of SendGrid's customArgs — propagated to engagement events. The connector normalises the call shape so application code uses the same field name regardless of vendor.

Webhooks

POST/connect/webhook/mailgunNo auth

Mailgun POSTs events here. The Connect module verifies signatures using the webhook signing key from the integration's credentials, then dispatches event types:

  • delivered → mark message delivered
  • opened → write email_opened activity
  • clicked → write email_clicked activity
  • permanent_failure (bounce) → suppress recipient
  • temporary_failure → retry on the next cycle
  • unsubscribed → mark recipient unsubscribed
  • complained (spam report) → suppress and flag

Configure each event type in Mailgun's Webhooks settings to point at:

https://api.appmint.io/connect/webhook/mailgun?orgid=<your-org>

Routes (inbound)

Mailgun Routes can post inbound emails to the platform. Use it for support inboxes that accept replies as ticket comments. Create a route in the Mailgun control panel:

  • Filter expression: match_recipient("[email protected]")
  • Action: forward("https://api.appmint.io/connect/webhook/mailgun/inbound?orgid=<your-org>")

The platform extracts the In-Reply-To header and threads the message into the matching ticket.

Suppressions

Mailgun maintains bounces, complaints, and unsubscribes per domain. Read via Upstream:

await fetch('/api/upstream/call/mailgun-provider/suppressions', {
  headers: { orgid: ORG_ID, Authorization: `Bearer ${jwt}` },
});

The platform's broadcast send pipeline checks Mailgun suppressions before sending so a re-subscribed recipient that's still on the Mailgun bounce list won't accidentally get retried.

Tracking and engagement

Mailgun handles tracking (opens, clicks) when enabled on the domain. AppEngine layers its unified automation webhook on top — Mailgun events drive deliverability metrics, the unified webhook drives CRM activity log.

To enable tracking, ensure the domain has tracking on (Mailgun control panel → Domains → tracking settings) and your messages set o:tracking-opens: yes and o:tracking-clicks: yes. The connector defaults both to on.

Multi-region

If you serve EU customers, run a separate Mailgun integration in the EU region for compliance. The two integrations are independent — different domain, different keys, different webhooks. Use the use-case routing layer to pick by recipient country if needed.

Common quirks

  • Free tier limits — Mailgun free tier ships emails with a "via mailgun.org" tag and aggressive rate limits. Always use a paid plan for production.
  • Domain reputation — new Mailgun domains start cold. Warm them up with low-volume transactional traffic before pointing a 100k-recipient broadcast at them.
  • Webhook retries — Mailgun retries failed webhook deliveries with exponential backoff. The Connect handler is idempotent (event IDs are deduplicated) so retries are safe.

The Mailgun and SendGrid connectors implement the same Upstream call shape. Switching vendors is configuration, not code — change the integration ID in the use-case routing rule and existing application code keeps working.