Email Sending API
All email delivery goes through Mailgun. Resend has been fully retired. Emails are sent from Kinetic Email <ai@kinetic.email> — both the sending domain and From address are env-overridable (MAILGUN_DOMAIN, MAILGUN_FROM).
POST /api/send-email-mailgun
The single sending endpoint, used by the AI Playground, Station Sandbox, and all other surfaces. Requires authentication — the endpoint sends real email from the Kinetic domain, so verifyUser gates every request and the sender is always the verified session user (a client-supplied sender is never trusted).
Sends emails as raw MIME via Mailgun's messages.mime REST API, which supports the text/x-amp-html MIME part required for AMP4Email.
Request
curl -X POST https://kinetic.email/api/send-email-mailgun \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"to": "recipient@example.com",
"subject": "Interactive AMP email!",
"html": "<!DOCTYPE html>...",
"ampHtml": "<!doctype html><html ⚡4email>...",
"emailName": "spring-launch",
"projectId": "optional-project-id",
"databaseId": "optional-workspace-id"
}'
| Field | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient email address |
subject | string | No | Email subject line (defaults to "Your Kinetic Email") |
html | string | Yes | Full HTML email content (fallback for non-AMP clients) |
ampHtml | string | No | AMP4Email HTML content |
emailName | string | No | Email name for workspace tracking |
projectId | string | No | Workspace project ID for tracking |
databaseId | string | No | Convex workspace ID for multi-tenant tracking |
The sender email is not a request field — it is taken from the verified Bearer token, which prevents spoofing the attribution banner.
Response
{
"success": true,
"message": "Email sent successfully via Mailgun!"
}
Suppression & Bounce Cooldowns
Before sending, the recipient is checked against the suppression system:
| Check | Behavior |
|---|---|
email_suppressions entry | Rejected 400 (hard bounce, complaint, or manual unsubscribe) |
| Soft bounce within 24h | Rejected 400 with retry_after |
| 3 soft bounces within 7 days | 30-day suppression with retry_after |
Processing Pipeline
- Tracking token stamping — Workspace-scoped placeholders (
{{EMAIL_NAME}},{{PROJECT_ID}},{{DATABASE_ID}}) resolved - Image URL resolution — Relative image paths converted to absolute
https://kinetic.email/...URLs - Per-recipient substitution —
{{UUID}}(SHA-256 hash of recipient email),{{SEND_ID}}(unique UUID),{{VIB_URL}}(hosted View in Browser page),{{RECIPIENT_EMAIL}} - Attribution banner — If the sender is not the recipient, a "sent to you by" banner is prepended
- AMP HTML processing — Same URL resolution and placeholder substitution applied to the AMP part
- Raw MIME assembly — RFC 2822 compliant
multipart/alternativemessage - Convex logging — Send logged for real-time tracking and View in Browser retrieval
MIME Structure
When ampHtml is provided, the email is sent as multipart/alternative with three MIME parts:
multipart/alternative
├── text/plain (plain text fallback)
├── text/x-amp-html (AMP version — rendered in Gmail, Yahoo)
└── text/html (HTML fallback — rendered in all other clients)
When no ampHtml is provided, the same MIME structure is used without the AMP part.
POST /api/mailgun-webhook
Webhook endpoint for Mailgun delivery events. Not called directly — Mailgun POSTs to this endpoint when delivery events occur.
Webhook Verification
HMAC-SHA256 signature verification is mandatory. MAILGUN_WEBHOOK_SIGNING_KEY must be configured — if it is missing the endpoint fails closed (500), and requests with a missing or invalid signature are rejected (401). Without this check anyone could POST forged bounce/complaint events and poison the suppression list.
Events Handled
| Event | Action |
|---|---|
delivered | Logged to email_delivery_events |
failed (permanent) | Logged + recipient added to email_suppressions |
failed (temporary) | Logged to email_bounce_log with escalation (4+ consecutive = permanent suppress) |
complained | Logged + recipient permanently suppressed |
opened / clicked / unsubscribed | Logged to email_delivery_events |
Hard bounces, escalated soft bounces, and complaints trigger Telegram alerts.
Error Handling
| Status | Error | Cause |
|---|---|---|
| 401 | Authentication required | Missing or invalid JWT |
| 400 | Missing required fields | to or html not provided |
| 400 | Recipient suppressed | Suppression list or bounce cooldown |
| 500 | Send failed | Mailgun API error |
Sending does not charge tokens — only AI generation does.