API reference
Everything you need to send your first email and keep it landing in inboxes.
Quickstart
The API is a plain JSON REST interface over HTTPS. Base URL:
https://api.sendflit.com/v1
Three steps to your first send:
- Create an account and add your domain in the dashboard.
- Publish the DNS records shown, then verify the domain.
- Create an API key and call
POST /v1/email.
403. This protects you as much as us: unverified
sending is how a shared sending reputation gets destroyed.Authentication
Pass your key as a bearer token on every request.
-H "Authorization: Bearer re_your_api_key"
Keys carry a scope, so a key that only needs to send cannot read your audience:
| Scope | Can do |
|---|---|
| full | Everything, including key and domain management. |
| sending | Send email and read its own delivery status. |
| read | Read-only. Cannot send. |
Keys are shown once at creation and stored only as a SHA-256 hash. Rotate one by
creating a replacement and revoking the old key with
DELETE /v1/keys/<id> — revocation is immediate.
Domains
POST/v1/domains registers a sending domain and returns the DNS
records to publish. Each domain gets its own 2048-bit DKIM keypair, so receivers
verify your signature.
curl -X POST https://api.sendflit.com/v1/domains \
-H "Authorization: Bearer $SENDFLIT_KEY" \
-d '{"name": "yourdomain.com"}'| Record | Host | Purpose |
|---|---|---|
| TXT | sendflit._domainkey.yourdomain.com | DKIM — signs your outgoing mail |
| TXT | yourdomain.com | SPF — authorises sending for the domain |
| TXT | _dmarc.yourdomain.com | DMARC — policy and aggregate reports |
Then POST/v1/domains/<id>/verify resolves the records and
marks the domain verified. If your DNS is in Route 53, connect AWS credentials once
and POST/v1/domains/<id>/configure-dns writes all three records
for you, merging into any SPF record you already have rather than replacing it.
Sending email
POST/v1/email sends one message.
{
"from": "hello@yourdomain.com",
"to": "user@example.com",
"subject": "Welcome aboard",
"html": "<p>Thanks for signing up.</p>",
"reply_to": "support@yourdomain.com",
"scheduled_at": "2026-09-01T09:00:00Z"
}| Field | Type | Notes |
|---|---|---|
| from | string | Required. Must be a verified domain on your account. |
| to | string | Required. One recipient. |
| subject | string | Required unless supplied by a template. |
| html / text | string | At least one. Both are recommended. |
| cc / bcc | string[] | Optional. |
| reply_to | string | Optional. |
| headers | object | Optional custom headers. |
| scheduled_at | ISO 8601 | Send later. Cancel with DELETE until it fires. |
| template_name | string | Render a stored template with variables. |
| variables | object | Values for {{name}} placeholders. |
| attachments | object[] | Up to 10, base64, 10 MB each. |
POST/v1/emails/batch takes up to 100 messages in one call and
returns a per-message result, so one bad recipient does not fail the batch.
GET/v1/emails lists sends with limit,
offset, q and status.
DELETE/v1/emails/<id> cancels a scheduled send.
Templates
Store reusable HTML with {{variable}} placeholders and render at
send time by name. Subject lines are rendered too.
{
"from": "hello@yourdomain.com",
"to": "user@example.com",
"template_name": "welcome",
"variables": {"name": "Ada", "plan": "Pro"}
}Broadcasts & audiences
Contacts belong to an audience. A broadcast sends to every subscribed contact in one, skipping anyone on your suppression list.
Broadcast delivery is asynchronous: the call returns once the messages are queued,
and the worker drains them. Poll GET/v1/broadcasts/<id> for
progress — the status moves from sending to sent when
the last message leaves.
curl -X POST https://api.sendflit.com/v1/contacts/import \ -H "Authorization: Bearer $SENDFLIT_KEY" \ --data-binary $'email,name,audience\\nada@example.com,Ada,default'
Tracking
When a message has an HTML body we append a 1×1 open pixel and rewrite links through a signed redirect. Both are bound to the message id and HMAC-signed, so the redirect cannot be repointed at somebody else's URL.
Bulk mail also carries List-Unsubscribe and
List-Unsubscribe-Post headers, which is what Gmail and Yahoo require of
bulk senders. The unsubscribe link opens a confirmation page; only a POST records the
unsubscribe, so link scanners cannot unsubscribe your recipients by crawling.
Suppressions
A suppressed address is refused at send time with 403. Addresses are
added automatically on an unsubscribe, a permanent bounce, or a spam complaint.
Transient bounces — a full mailbox, a throttled receiver — do not
suppress.
| Endpoint | Purpose |
|---|---|
| GET/v1/suppressions | List suppressed addresses. |
| POST/v1/suppressions | Add one manually. |
| DELETE/v1/suppressions?email= | Remove one. |
Webhooks
Register an endpoint and we POST every event to it. Each request carries an
X-Sendflit-Signature: sha256=<hex> header — an HMAC of the raw
body using your webhook secret. Always verify it, and compare in constant time.
import hashlib, hmac
def verify(raw_body: bytes, header: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(f"sha256={expected}", header)| Event | Fires when |
|---|---|
| email.sent | Accepted by the sending infrastructure. |
| email.delivered | The receiving server accepted it. |
| email.opened | The tracking pixel loaded. |
| email.clicked | A tracked link was followed. |
| email.bounced | Delivery failed permanently. |
| email.complained | Marked as spam by the recipient. |
| email.unsubscribed | The recipient used the unsubscribe link. |
| email.failed | We could not hand it off. |
MCP server
SendFlit speaks the Model Context Protocol at /mcp, so an AI agent can
use your account directly. Authenticate with a normal API key — the agent
inherits that key's scope, rate limit and quota, so a sending key cannot
be talked into reading your audience.
{
"mcpServers": {
"sendflit": {
"url": "https://api.sendflit.com/mcp",
"headers": { "Authorization": "Bearer re_your_api_key" }
}
}
}Tools available: send_email, schedule_email, list_emails, get_metrics, add_contact, create_broadcast, create_template, add_domain, and the AI helpers for composing drafts, generating subject variants, summarising metrics, and building segments and automations from a description.
Errors & limits
Errors are JSON with a detail field. Every response carries an
X-Request-Id — quote it if you contact support.
| Status | Meaning |
|---|---|
| 400 | Malformed request, or an unsigned tracking link. |
| 401 | Missing, invalid or revoked credentials. |
| 402 | Requires a paid plan — start a checkout. |
| 403 | Suppressed recipient, unverified domain, or insufficient key scope. |
| 404 | Not found, or not yours. |
| 409 | Conflict — the resource already exists. |
| 422 | Validation failed. The message says which field. |
| 429 | Rate limit, daily limit or monthly quota reached. |
| 502 | The upstream mail provider rejected the handoff. |
Rate limits are per API key, per second, and vary by plan. Monthly and daily send quotas are enforced per account; a broadcast is charged for its whole audience up front, so it either fits or is refused before any mail goes out.