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_xxxxxxxxxxxxxxxxxxxxxxxxTreat 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_emailstringrequiredVerified identity on your account. Either the exact email or any address @ a verified domain.
tostring[]required1–50 recipients. cc + bcc count toward this total.
ccstring[]Optional CC list.
bccstring[]Optional BCC list. Recipients added to envelope only.
reply_tostringReply-To header for replies that bypass your sending mailbox.
subjectstringRequired when not using a template. Rendered as Jinja2 if a template is used.
htmlstringHTML body. Required if text is not provided (or unless using a template).
textstringPlain-text alternative. Derived from HTML when blank if a template is used.
templatestringSlug of a system catalog template or one of your cloned templates. Resolved per-account.
variablesobjectRender context for a template. Nested objects supported, e.g. { user: { first_name } }.
headersobjectAdditional 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
queued→sent→delivered→opened→clickedNegative 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
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:sendSend an email (template mode with variables, or inline subject+html). Returns {id, status}.
get_email_statusemail:readGet 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.
brand_nameSender display name shown in the email footer.
logo_urlHeader logo image URL. Omit to render text-only.
primary_colorButton + accent color (hex). Defaults to #00a071.
accent_colorSecondary accent color (hex). Defaults to #00e5a0.
support_emailFooter "Need help?" contact.
support_urlFooter help link.
footer_htmlRaw HTML rendered in the footer (e.g. unsubscribe link + physical address).
button_radiusButton corner radius in px (0–24). Default 8.
privacy_policy_urlPrivacy policy URL. When set, a "Privacy Policy" link is auto-appended to the footer of every template.
terms_urlTerms & 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.
400Validation failed — missing subject or body, invalid emails, too many recipients (>50).
401Missing or unknown `X-API-Key` header.
402Key belongs to a non-paying account. Upgrade at `/dashboard/billing`.
403`from_email` is not a verified identity on the calling account.
404Template slug not found (system templates and your own templates are both searched).
429Daily quota or per-second rate limit exceeded.
502Upstream 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
deliverybounceopenclickcomplaintSample 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
429with aretry-afterheader.