Skip to main content

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"
}'
FieldTypeRequiredDescription
tostringYesRecipient email address
subjectstringNoEmail subject line (defaults to "Your Kinetic Email")
htmlstringYesFull HTML email content (fallback for non-AMP clients)
ampHtmlstringNoAMP4Email HTML content
emailNamestringNoEmail name for workspace tracking
projectIdstringNoWorkspace project ID for tracking
databaseIdstringNoConvex 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:

CheckBehavior
email_suppressions entryRejected 400 (hard bounce, complaint, or manual unsubscribe)
Soft bounce within 24hRejected 400 with retry_after
3 soft bounces within 7 days30-day suppression with retry_after

Processing Pipeline

  1. Tracking token stamping — Workspace-scoped placeholders ({{EMAIL_NAME}}, {{PROJECT_ID}}, {{DATABASE_ID}}) resolved
  2. Image URL resolution — Relative image paths converted to absolute https://kinetic.email/... URLs
  3. Per-recipient substitution{{UUID}} (SHA-256 hash of recipient email), {{SEND_ID}} (unique UUID), {{VIB_URL}} (hosted View in Browser page), {{RECIPIENT_EMAIL}}
  4. Attribution banner — If the sender is not the recipient, a "sent to you by" banner is prepended
  5. AMP HTML processing — Same URL resolution and placeholder substitution applied to the AMP part
  6. Raw MIME assembly — RFC 2822 compliant multipart/alternative message
  7. 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

EventAction
deliveredLogged 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)
complainedLogged + recipient permanently suppressed
opened / clicked / unsubscribedLogged to email_delivery_events

Hard bounces, escalated soft bounces, and complaints trigger Telegram alerts.


Error Handling

StatusErrorCause
401Authentication requiredMissing or invalid JWT
400Missing required fieldsto or html not provided
400Recipient suppressedSuppression list or bounce cooldown
500Send failedMailgun API error

Sending does not charge tokens — only AI generation does.