Mailgun Integration
Mailgun is the email delivery provider for Kinetic Email. (Resend was fully retired — all sends now go through Mailgun.)
Setup
- Create a Mailgun account and verify the sending domain
- Set environment variables:
MAILGUN_API=your-api-key # required
MAILGUN_DOMAIN=kinetic.email # optional override (default: kinetic.email)
MAILGUN_FROM="Kinetic Email <ai@kinetic.email>" # optional override
MAILGUN_WEBHOOK_SIGNING_KEY=your-signing-key # required for webhooks (fails closed)
Emails are sent from Kinetic Email <ai@kinetic.email> by default. The domain and From address are env-overridable so a move to a dedicated sending subdomain is a Vercel env change, not a deploy.
How It's Used
The /api/send-email-mailgun endpoint sends all email as raw MIME via Mailgun's messages.mime REST API — this supports the text/x-amp-html MIME part required for AMP4Email:
POST https://api.mailgun.net/v3/{MAILGUN_DOMAIN}/messages.mime
Authorization: Basic base64("api:" + MAILGUN_API)
Authentication Requirement
The send endpoint requires a verified user session (verifyUser, Bearer JWT). The sender email is always the verified session user — it is never taken from the request body, which prevents spoofing the "sent to you by" attribution banner.
Suppression Enforcement
Every send is checked against the suppression system first:
email_suppressions— hard bounces, complaints, and manual unsubscribes are rejected outright- Soft-bounce cooldowns — a bounce in the last 24 hours blocks sending (with
retry_after), and 3 soft bounces within 7 days triggers a 30-day suppression
Delivery Webhooks
Mailgun POSTs delivery events to /api/mailgun-webhook. HMAC-SHA256 signature verification is mandatory: if MAILGUN_WEBHOOK_SIGNING_KEY is unset the endpoint fails closed, and unsigned or badly signed requests are rejected with 401 — otherwise anyone could POST forged bounce events and poison the suppression list.
| Event | Handling |
|---|---|
delivered, opened, clicked | Logged to email_delivery_events |
failed (permanent) | Recipient permanently suppressed + Telegram alert |
failed (temporary) | Logged to email_bounce_log; 4+ consecutive escalates to permanent suppression |
complained | Recipient permanently suppressed + Telegram alert |
See Email Sending API for the full request/response reference.