Transactional Email API
Transactional email on your own letterhead
Verify a domain you own once, then send, schedule and receive DKIM-signed mail with the cm_ API key you already have — logged end to end and honest about what accepted means.
Authorization: Bearer cm_… → POST https://api.callmissed.com/api/v1/email/send → 202 Accepted
The essentials
- One cm_ key
- The key you already hold. Toggle the email permission on it; no second credential.
- 202 + suppressed[]
- Accepted sends answer 202 and name the recipients your suppression list dropped.
- DKIM on your domain
- Your domain signs every message once its two delegated keys verify.
- ₹30 per 1,000
- Per recipient, from the shared credit balance. Rejected sends cost nothing.
Four records to publish, one call to verify
POST /domains answers with the exact DNS set: an ownership token, SPF, and two CNAMEs that delegate DKIM signing to us — rotation included. Publish them, then call verify. All four checks must pass before the domain sends.
- TXT
@→cmv-verify=9f3ac2…
A one-off token proving you control the domain
- TXT
@→v=spf1 include:… ~all
Authorises our sending infrastructure for the domain
- CNAME
cm1._domainkey→…dkim1…
Delegates the DKIM signing key for the domain
- CNAME
cm2._domainkey→…dkim2…
A second delegated DKIM key, used for rotation
- MX
@→…inbound…
Routes inbound mail — only needed to receive · optional
Simulated example — record values are abbreviated; yours are generated per domain and re-fetchable with GET /domains/{id}/records.
POST /api/v1/email/domains/{id}/verify
- Ownership token found
- SPF authorises sending
- DKIM key delegated
- Rotation key delegated
Simulated example. A send from any other local part registers it and retries; that can briefly surface as 503 sender_propagating.
Verification registers donotreply@your-domain as a sender, so it works the moment the checks pass. Other local parts are registered via the senders endpoint — or automatically on first use, which can briefly answer 503 sender_propagating while it propagates.
The same four records, in the console
Add the domain you send from, publish the records it hands you, verify, and follow each email in the Sends log as it is delivered.
Compose once, substitute at send time
Save a subject and body with {{ params.KEY }} placeholders, then pass the values per send — or per recipient set in a batch. Try it: edit a value and the letter re-inks.
Rendered at send time
Subject
Your receipt, Ada
Hi Ada, your order 1043 is confirmed. Total: ₹1,240.
- name
- order_id
- amount
Substituted values are HTML-escaped, inserted once and never re-scanned — a param that itself contains {{ … }} stays literal. Explicit subject, body or sender fields on the send override the template.
From POST to postmark
Walk one send down the rail: the request, the 202, the DKIM signature, and the feedback that comes back — including the recipient who never makes it past the suppression list.
Simulated example — the full sequence is on the page below; play re-walks it in order.
- Request
POST /api/v1/email/send
Three recipients on one send. [email protected] is already on your suppression list.
request body { "from": "Acme Ops <[email protected]>", "to": [ "Ada <[email protected]>", "Bo <[email protected]>", "[email protected]" ], "subject": "Your receipt", "text": "Thanks for your order.", "reply_to": "[email protected]" } - Response
202 Accepted
status: sent means accepted for delivery — not delivered. suppressed[] names who was dropped before anything was attempted.
response · 202 Accepted { "id": "9d0f8b3a-1c2e-4a5b-…", "message_id": "<[email protected]>", "messageId": "<[email protected]>", "messageIds": ["<[email protected]>"], "status": "sent", "suppressed": ["[email protected]"] }Accepted is not delivered
202 means the message passed validation, suppression and quota checks and was accepted for delivery. What the recipient's server does with it comes back later — as send-log statuses and webhook events, not in this response.
- Signing
DKIM-signed, handed to delivery
The message is signed with the verified From domain’s key, then handed off.
DKIM-Signature: v=1; d=acme.com; s=cm1; bh=…; b=…
- Feedback
Delivery reports back
ada@ is marked delivered in the send log. bo@ bounces — a signed email.bounced webhook fires and the address joins your suppression list.
[email protected]delivered
Send log row shows status: delivered, set from delivery feedback.
[email protected]bounced
email.bounced webhook fires; the address is auto-added to suppressions.
[email protected]suppressed
Dropped before sending, returned in suppressed[], never billed.
webhook delivery — signed, logged per attempt POST https://your-app.com/hooks/email X-CallMissed-Signature: sha256=… X-CallMissed-Event: email.bounced { "type": "email.bounced", "data": { "message_id": "<[email protected]>", "recipient": "[email protected]", "detail": "550 5.1.1 recipient address rejected" } }
Send it at a time, not just at once
Add scheduledAt and the send waits up to 72 hours out — enqueued now, billed and delivered when it fires, cancellable while pending.
For many recipients at once, messageVersions carries up to 1,000 recipient sets in one call — each overriding subject, body or params — and the deduplicated union is billed once. An address claimed by an earlier version is simply not sent twice.
Simulated example — slots are computed from your clock. The API takes a UTC timestamp that is in the future and within 72 hours.
{
"from": "Acme Ops <[email protected]>",
"to": ["Ada <[email protected]>"],
"subject": "Reminder",
"text": "Your appointment is tomorrow.",
"scheduledAt": "2026-07-28T09:00:00Z"
}{
"id": "3f1c9a2d-4b5e-6a7f-…",
"status": "scheduled",
"scheduledAt": "2026-07-28T09:00:00Z",
"batchId": "7a2b1c0d-9e8f-4a5b-…",
"suppressed": []
}Nothing is billed or delivered at enqueue time — both happen when it fires. Inspect pending rows with GET /scheduled/{id-or-batchId} and cancel with DELETE before it fires; a cancelled send is never charged.

Claim a pigeonhole, read what arrives
Publish the MX record on a verified domain and addresses like support@your-domain start receiving. Mail to anything unclaimed is refused at the door — there is no catch-all.
- Claim the addressPOST /api/v1/email/inbound/addresses
- Pick a mailbox name on a verified domain — support, replies, anything up to 64 chars. Addresses are globally unique; an already-claimed name answers 409.
- Forward to your appforward_url on the address
- Every message that lands is POSTed to your endpoint as JSON — stored first, then forwarded, so a down endpoint never loses mail.
- Read it backGET /api/v1/email/inbound/messages/{id}
- Forwards are unsigned, so treat them as a knock on the door: confirm anything you act on by re-reading the message with your key.

Every send is a ledger line
What happened after the hand-off is written down: a send log with real statuses, a suppression list that maintains itself, engagement metrics per window, and signed webhooks for the events that matter.
GET /api/v1/email/sends · Simulated example
| № | Recipient | Subject | Status | Logged |
|---|---|---|---|---|
| 1043 | [email protected] | Your receipt | delivered | 09:41 |
| 1042 | [email protected] | Your receipt | bounced | 09:41 |
| 1041 | list · 240 recipients | March invoice run | delivered | 09:12 |
| 1040 | [email protected] | Appointment reminder | queued | scheduled |
| 1039 | [email protected] | Welcome note | complained | 08:57 |
| 1038 | — | Missing sender | rejected | 08:30 |
- Send logGET /api/v1/email/sends
- One row per message — queued, sent, delivered, bounced, complained, rejected or failed — filterable by status, recipient, message id and a ≤90-day window.
- SuppressionsGET /api/v1/email/suppressions
- Hard bounces and complaints land here automatically, singly or in batches of 100. Suppressed addresses are dropped pre-send and returned in suppressed[].
- Engagement metricsGET /api/v1/email/emails/metrics
- One aggregate row per window: sent, delivered, bounced, complained, opens and clicks with the rates derived for you — tracked subsets handled honestly.
- Webhook eventsPOST /api/v1/email/webhooks
- Bounce and complaint events POST to your endpoint, HMAC-SHA256 signed over the raw body, retried 3 times, and every attempt is in the delivery log.
Three ceilings, four error shapes
Requests per minute, recipients per calendar month, and a per-domain daily quota that warms up on clean volume — a refusal always names which one you hit. Error bodies come in four shapes; branch on the HTTP status first.
| Plan | Send rate | Recipients / month | Daily-quota ceiling |
|---|---|---|---|
| Free | 10 requests / min | 2,000 | 200 |
| Starter | 60 requests / min | 50,000 | 5,000 |
| Pro | 300 requests / min | 1,000,000 | 50,000 |
| Enterprise | 1,000 requests / min | Unlimited | 500,000 |
The enforced daily figure is the domain’s, not the plan’s: a newly verified domain starts at 200 sends a day and climbs 200 → 1,000 → 5,000 → 20,000 → 50,000, one step per clean-sending day, then doubling. Too many bounces or complaints pauses the domain itself — that breaker stays on until the list quality is fixed.
Send rejection
reason nested under detail — domain_not_verified, quota_exceeded, sender_propagating and friends.
{ "detail": { "error": "acme.com has not completed DNS verification", "reason": "domain_not_verified" } }502 relay_failed
Flat shape, not wrapped in detail, and it carries the send id for the log.
{ "error": "The message could not be accepted for delivery", "reason": "relay_failed", "id": "9d0f8b3a-1c2e-4a5b-…" }Auth, not-found, conflict
detail is a plain string with no reason — 401s, 404s, 409s, sender-route errors.
{ "detail": "Domain not found" }Schema validation
detail is an array — rejected before the send pipeline runs, before any charge.
{ "detail": [{ "type": "value_error", "loc": ["body"], "msg": "invalid email address" }] }
Franking, by the thousand
Thirty credits — thirty rupees — per thousand recipients, from the same balance your model calls draw on. No per-mailbox fees, no sending-tier subscription, no separate invoice.
See the full pricing- Credits charged
- 750
- In rupees
- ₹750
- Approx. USD
- $7.81
Simulated example at the published rate — 30 credits (₹30) per 1,000 emails, about 3 paise per recipient, drawn from the same credit balance as every other API. Only accepted sends are billed. The 1,000-credit signup bonus alone covers roughly 33,333 emails.
Three calls, end to end
The whole service answers to the cm_ key with the email permission on — the same header you send for model calls. Brevo-shaped payloads are accepted unchanged.
Register & verify a domain
POST /api/v1/email/domains → POST /domains/{id}/verify
# 1. Add the domain — the response carries the DNS records to publish
curl -X POST https://api.callmissed.com/api/v1/email/domains \
-H "Authorization: Bearer cm_your_key" \
-H "Content-Type: application/json" \
-d '{"domain": "acme.com"}'
# 2. Publish the records, then verify (repeat until verified is true)
curl -X POST https://api.callmissed.com/api/v1/email/domains/{domain_id}/verify \
-H "Authorization: Bearer cm_your_key"Verification registers donotreply@your-domain as a sender and turns on DKIM signing.
Send from a template
POST /api/v1/email/send
curl -X POST https://api.callmissed.com/api/v1/email/send \
-H "Authorization: Bearer cm_your_key" \
-H "Content-Type: application/json" \
-d '{
"templateId": "9d0f8b3a-1c2e-4a5b-8f7d-6e2a1b0c9d4e",
"to": ["Ada <[email protected]>"],
"params": { "name": "Ada", "order_id": "1043" },
"reply_to": "[email protected]"
}'params fills {{ params.KEY }} placeholders in the saved subject and bodies; explicit send fields override the template.
Schedule or batch a send
POST /api/v1/email/send
curl -X POST https://api.callmissed.com/api/v1/email/send \
-H "Authorization: Bearer cm_your_key" \
-H "Content-Type: application/json" \
-d '{
"from": "Acme Ops <[email protected]>",
"to": ["Ada <[email protected]>"],
"subject": "Reminder",
"text": "Your appointment is tomorrow.",
"scheduledAt": "2026-07-28T09:00:00Z"
}'scheduledAt accepts a UTC timestamp up to 72 hours out. Cancel a pending send with DELETE /api/v1/email/scheduled/{id} — never charged.
Postmarked questions
- Do I need a separate email API key?
- No. The Email API uses the same cm_ key as every other CallMissed API — toggle the email permission on for the key in the console. No separate sender account, no second credential.
- What does a 503 sender_propagating mean?
- Verification registers exactly one sender, donotreply@your-domain. Any other local part has to be registered — up front via the senders endpoint, or automatically when the send path retries a refused send. Registration is eventually consistent, so a first send from a brand-new address can briefly return 503 sender_propagating. Retry in a minute or two; nothing else is needed.
- Is a 202 the same as delivered?
- No. 202 means accepted for delivery: the message was validated, suppression-filtered, DKIM-signed and handed off. Whether it actually reached a mailbox comes back later — as send-log statuses (delivered, bounced, complained) and as signed webhook events. We make no inbox-placement promise: delivery depends on recipient servers and your domain reputation.
- What does email cost?
- 30 credits — ₹30 — per 1,000 emails, billed per recipient from the same credit balance as every other API. Only accepted sends are billed; rejections and failed relays cost nothing. New accounts get 1,000 free signup credits.
- Can I schedule sends or send to a list?
- Both. Add a scheduledAt timestamp (future, within 72 hours) and the send is enqueued — nothing is billed or delivered until it fires, and a pending send can be cancelled. For many recipient sets in one call, messageVersions carries up to 1,000 versions; the deduplicated union of recipients is billed once.
- Can I receive email too?
- Yes. Publish the MX record on a verified domain, claim addresses like support@your-domain, then read received mail over the API or POST it to your app with a per-address forward_url. Mail to unclaimed addresses is refused — there is no catch-all.

Put your domain on the envelope
Add the domain, publish four records, and the next receipt your app sends leaves under your own signature — DKIM-signed and logged. The first thousand sends come out of your signup credits.





