API Reference

SESMetric REST API

One endpoint to send transactional email. Pair with templates and webhooks for a full delivery pipeline. All examples use a paid-tier key — issue one at /dashboard/keys.

Authentication

Every request sends an X-API-Key header. Issue keys at /dashboard/keys — they are scoped (email:send, email:read) and tied to a paying account. Keys are shown once at creation; store them in a secret manager.

X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxx

Treat keys like passwords. Never embed in client-side JavaScript — call your own server and proxy.

POST/v1/send

Queue a transactional email. Returns immediately with a msg_… id; delivery events flow to your registered webhooks. Provide either an inline subject+html/text body or a template slug with variables.

Request fields

from_emailstringrequired

Verified identity on your account. Either the exact email or any address @ a verified domain.

tostring[]required

1–50 recipients. cc + bcc count toward this total.

ccstring[]

Optional CC list.

bccstring[]

Optional BCC list. Recipients added to envelope only.

reply_tostring

Reply-To header for replies that bypass your sending mailbox.

subjectstring

Required when not using a template. Rendered as Jinja2 if a template is used.

htmlstring

HTML body. Required if text is not provided (or unless using a template).

textstring

Plain-text alternative. Derived from HTML when blank if a template is used.

templatestring

Slug of a system catalog template or one of your cloned templates. Resolved per-account.

variablesobject

Render context for a template. Nested objects supported, e.g. { user: { first_name } }.

headersobject

Additional message headers. Sender-controlled metadata only.

tagsstring[]

Free-form labels surfaced in analytics + webhook events.

attachmentsAttachment[]

Each: { filename, content_base64, content_type }. Total payload ≤ 25 MB.

Inline body

curl -X POST https://api.sesmetric.com/v1/send \
  -H "X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
  "from_email": "you@yourdomain.com",
  "to": [
    "recipient@example.com"
  ],
  "subject": "Welcome, Alex",
  "html": "<p>Hi Alex — welcome aboard.</p>",
  "text": "Hi Alex — welcome aboard."
}'

Using a template

curl -X POST https://api.sesmetric.com/v1/send \
  -H "X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
  "from_email": "you@yourdomain.com",
  "to": [
    "recipient@example.com"
  ],
  "template": "welcome",
  "variables": {
    "user": {
      "first_name": "Alex"
    }
  }
}'

Response

{
  "id": "msg_3f8c1b2e9a4d4b...",
  "status": "queued"
}

GET/v1/messages/{sm_mail_id}

Return the full event lifecycle for a single email. Pass the id returned by /v1/send. Requires the email:read scope. The response merges rows from the six ClickHouse event tables (send_events, delivery_events, open_events, click_events,bounce_events, complaint_events) into one chronological timeline.

Querying

curl https://api.sesmetric.com/v1/messages/msg_3f8c1b2e9a4d4b \
  -H "X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxx"

Response

{
  "sm_mail_id": "msg_3f8c1b2e9a4d4b...",
  "user_id": "42",
  "status": "opened",
  "events": [
    { "type": "sent",      "timestamp": "2026-06-22T10:00:00", "recipients": ["r@example.com"], "subject": "Welcome" },
    { "type": "delivered", "timestamp": "2026-06-22T10:00:02", "smtp_response": "250 ok", "reporting_mta": "smtp-out.google.com" },
    { "type": "opened",    "timestamp": "2026-06-22T11:14:33", "user_agent": "Mozilla/5.0", "ip_address": "203.0.113.7" }
  ]
}

Status progression

queuedsentdeliveredopenedclicked

Negative events override: bounced or complained win over opened/clicked.

MCP server for AI agents

Send email from any MCP-compatible AI client (Claude Code, Cursor, custom agents) via a dedicated MCP server at https://ai.sesmetric.com/mcp. Same API key as the REST surface — the email:send scope authorizes send_email, email:readauthorizes get_email_status, and template discovery is read-only.

Client config

{
  "mcpServers": {
    "sesmetric-mail": {
      "url": "https://ai.sesmetric.com/mcp",
      "headers": { "X-API-Key": "sk_live_REPLACE_ME" }
    }
  }
}

Tools

ToolScopeDescription
list_templates

List the 35+ system templates available out of the box.

get_template

Fetch a template by slug, including its variables schema.

send_emailemail:send

Send an email (template mode with variables, or inline subject+html). Returns {id, status}.

get_email_statusemail:read

Get the full event lifecycle (sent → delivered → opened → clicked / bounced / complained) for an email.

Example agent session

# 1. Discover available templates
list_templates({ category: "auth" })
# → [{ slug: "password-reset", variables: [{name:"user",required:true}, {name:"reset_url",required:true}] }, …]

# 2. Send a password-reset email
send_email({
  from_email: "support@yourdomain.com",
  to: ["user@example.com"],
  template: "password-reset",
  variables: { user: { first_name: "Alex" }, reset_url: "https://app.example.com/reset?token=…" }
})
# → { id: "msg_3f8c1b2e9a4d4b…", status: "queued" }

# 3. Check the email's status
get_email_status({ sm_mail_id: "msg_3f8c1b2e9a4d4b…" })
# → { status: "delivered", events: [{type:"sent",…}, {type:"delivered",…}] }

The same AI MCP is also reachable at https://api.sesmetric.com/v1/mcp for clients that prefer the API host. The CMS MCP (articles + docs) remains at/mcp on api.sesmetric.com with separate admin-scoped keys.

Branding

Configure at /dashboard/branding. Branding is auto-injected into every template render via a branding object in the Jinja2 context — templates reference{{ branding.logo_url }},{{ branding.primary_color }},{{ branding.footer_html }}, etc.

FieldPurpose
brand_name

Sender display name shown in the email footer.

logo_url

Header logo image URL. Omit to render text-only.

primary_color

Button + accent color (hex). Defaults to #00a071.

accent_color

Secondary accent color (hex). Defaults to #00e5a0.

support_email

Footer "Need help?" contact.

support_url

Footer help link.

footer_html

Raw HTML rendered in the footer (e.g. unsubscribe link + physical address).

button_radius

Button corner radius in px (0–24). Default 8.

privacy_policy_url

Privacy policy URL. When set, a "Privacy Policy" link is auto-appended to the footer of every template.

terms_url

Terms & conditions URL. When set, a "Terms & Conditions" link is auto-appended to the footer.

Legal links (privacy + terms) appear in every system template's footer the moment you set them — no per-template wiring required.

Error responses

Errors return a JSON object with a detail field. The HTTP status code is authoritative for client retry decisions.

StatusCause
400

Validation failed — missing subject or body, invalid emails, too many recipients (>50).

401

Missing or unknown `X-API-Key` header.

402

Key belongs to a non-paying account. Upgrade at `/dashboard/billing`.

403

`from_email` is not a verified identity on the calling account.

404

Template slug not found (system templates and your own templates are both searched).

429

Daily quota or per-second rate limit exceeded.

502

Upstream SMTP relay returned an error. Retry after a short backoff.

Webhooks

Register HTTPS endpoints at /dashboard/webhooks to receive delivery events. Each POST is signed with HMAC-SHA256 using your webhook secret. Verify the signature before trusting the payload.

Event types

deliverybounceopenclickcomplaint

Sample payload

POST https://your.app/sesmetric-events
X-SM-Signature: sha256=a1b2c3d4...
Content-Type: application/json

{
  "event_type": "delivery",
  "user_id": 42,
  "payload": {
    "sm_mail_id": "msg_3f8c1b2e9a4d4b...",
    "message_id": "0100018f...000000",
    "recipients": ["recipient@example.com"],
    "smtp_response": "250 ok",
    "event_timestamp": "2026-05-19T13:42:11Z"
  }
}

Signature verification

import crypto from 'crypto';

function verify(rawBody, header, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  // Use timingSafeEqual to defeat timing attacks.
  return crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}

Retry schedule: 30s, 5m, 30m, 2h, 6h. After 5 failed attempts the delivery row is marked abandoned.

Rate limits & quotas

  • • Daily send quota is enforced per account by the mail-sender worker. View remaining quota at /dashboard.
  • • Free accounts cannot call /v1/send — upgrade or request a trial.
  • • 50 total recipients (to + cc + bcc) per request.
  • • 25 MB total payload including attachments.
  • • Excess returns 429 with a retry-after header.