Skip to main content

Delivery, Suppressions & Usage

Read the send log, manage the suppression list, and check email spend and pricing.

Overview

Four read-mostly surfaces cover what happened after a send: the send log (/sends), the suppression list (/suppressions), engagement metrics (/emails/metrics), and spend (/usage). Everything except the suppression writes is a read, so a read-only key works.

To be told about a bounce or a complaint as it happens instead of polling, subscribe to Email Webhooks.

Suppressions

A suppression list per account prevents sending to addresses that hard-bounced or complained. Entries are added automatically from delivery feedback, and you can manage them:

EndpointPurpose
GET /api/v1/email/suppressionsList suppressed addresses, newest first. limit (1–500, default 100) and offset (≥0, default 0)
POST /api/v1/email/suppressionsSuppress an address manually (201)
POST /api/v1/email/suppressions/batchSuppress up to 100 addresses in one call (201)
GET /api/v1/email/suppressions/{id}Retrieve one suppression by id
DELETE /api/v1/email/suppressions/{id}Remove a suppression (204)
Create fieldTypeRequiredNotes
addressstringYesA valid email address. Stored lower-cased
reasonstringNoOne of hard_bounce, complaint, manual, unsubscribe. Defaults to manual; anything else is a 422
detailstringNoYour own note about why

Adding an address that is already suppressed is safe: the existing entry is returned unchanged rather than duplicated or rejected.

SuppressionOut returns id, address, reason, detail, and created_at. GET /suppressions/{id} returns the same object for a single entry; an id that is not yours is a 404.

Suppress in bulk

POST /api/v1/email/suppressions/batch applies one reason and detail to many addresses:

FieldTypeRequiredNotes
addressesarrayYes1–100 valid email addresses. Stored lower-cased
reasonstringNoSame set as the single-add route. Defaults to manual; anything else is a 422
detailstringNoYour own note, applied to every address in the batch
curl -X POST https://api.callmissed.com/api/v1/email/suppressions/batch \
  -H "Authorization: Bearer cm_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "addresses": ["one@example.com", "two@example.com", "three@example.com"],
    "reason": "unsubscribe",
    "detail": "Imported from legacy list"
  }'

The 201 response is an array of SuppressionOut in the order you sent, so it lines up with your input. It is idempotent per address: one already on the list is returned unchanged rather than erroring, and duplicates within a single payload are collapsed. That means a partially-applied batch can simply be retried.

# List
curl https://api.callmissed.com/api/v1/email/suppressions \
  -H "Authorization: Bearer cm_your_key"

# Suppress an address manually
curl -X POST https://api.callmissed.com/api/v1/email/suppressions \
  -H "Authorization: Bearer cm_your_key" \
  -H "Content-Type: application/json" \
  -d '{"address": "blocked@example.com", "reason": "manual"}'

# Remove a suppression
curl -X DELETE https://api.callmissed.com/api/v1/email/suppressions/3f1c9a2d-4b5e-6a7f-8c9d-0e1f2a3b4c5d \
  -H "Authorization: Bearer cm_your_key"

Suppressed recipients are dropped from to, cc, and bcc before sending and returned in the send response's suppressed array. If every recipient is suppressed the send is refused with 403 all_recipients_suppressed. A 404 on a suppression id uses the plain-string detail shape.

Delivery Log & Usage

GET /api/v1/email/sends is your send log, newest first. Returns an array of SendOut:

FieldTypeNotes
idstring (UUID)The send id returned by POST /send
message_idstring | nullRFC 5322 Message-ID; null if the message was never built
from_addressstringThe sender the message went out with
subjectstringThe rendered subject
statusstringqueued, sent (accepted for delivery), delivered, bounced, complained, rejected (we refused it), or failed (delivery error)
size_bytesintegerAssembled message size
sent_atstring | nullWhen it was accepted for delivery
delivered_atstring | nullSet from delivery feedback
bounced_atstring | nullSet from bounce feedback
complained_atstring | nullSet from a spam complaint
created_atstring | nullWhen the row was written

One row per message, so a messageVersions batch writes one row per version. This is how you find out which versions of a batch failed.

Filtering the send log

Query paramTypeNotes
limitinteger1–200, default 50
offsetinteger≥0, default 0
statusstringExact send status, e.g. delivered, bounced, queued. An unknown value is a 422 listing the valid set
message_idstringExact RFC 5322 Message-ID match, up to 255 chars
recipientstringMatch a To: address on the send, up to 320 chars. Case-insensitive and exact per address, so bob@ex.com will not match notbob@ex.com. A display form like Alice <alice@x.com> matches on the bare address. cc and bcc are deliberately not searched
sincestringISO 8601. Only sends created at or after this timestamp
untilstringISO 8601. Only sends created at or before this timestamp

Filters combine. When you pass both since and until, the window may not exceed 90 days, and until must not precede since; either violation is a 422.

# Everything that bounced in a date window
curl -G https://api.callmissed.com/api/v1/email/sends \
  -H "Authorization: Bearer cm_your_key" \
  --data-urlencode "status=bounced" \
  --data-urlencode "since=2026-07-01T00:00:00Z" \
  --data-urlencode "until=2026-07-31T23:59:59Z"

# Every send to one recipient
curl -G https://api.callmissed.com/api/v1/email/sends \
  -H "Authorization: Bearer cm_your_key" \
  --data-urlencode "recipient=customer@example.com"

Retrieve one send

GET /api/v1/email/sends/{send_id} returns a single SendOut for the id you got back from POST /send:

curl https://api.callmissed.com/api/v1/email/sends/9d0f8b3a-1c2e-4a5b-8f7d-6e2a1b0c9d4e \
  -H "Authorization: Bearer cm_your_key"

The fields are identical to a row from the list, so the two surfaces cannot drift. An id that is not yours is a 404, not a 403.

Message bodies are not returned: we do not retain the rendered html/text after the message is handed off for delivery. Keep your own copy if you need to display what was sent.

GET /api/v1/email/usage is spend for the account. Cost is summed from the price stamped on each send at send time, so a later price change never rewrites history. Returns UsageOut:

FieldTypeNotes
currencystringINR
price_per_1000numberCurrent price per 1,000 emails
billed_sendsintegerSends that were actually charged, all time
total_costnumberAll-time spend in whole currency units
sends_30dintegerBilled sends in the trailing 30 days
cost_30dnumberSpend in the trailing 30 days

Both endpoints are reads, so a read-only key works.

# Newest 50 sends
curl "https://api.callmissed.com/api/v1/email/sends?limit=50&offset=0" \
  -H "Authorization: Bearer cm_your_key"

# Spend
curl https://api.callmissed.com/api/v1/email/usage \
  -H "Authorization: Bearer cm_your_key"

Engagement metrics

GET /api/v1/email/emails/metrics is one aggregate row for a time window: volume, engagement and the derived rates. A read, so a read-only key works.

The repeated email/emails in that path is correct, not a typo: the metrics route is named /emails/metrics and sits under the /api/v1/email prefix like every other endpoint here.

Query paramNotes
start_dateISO 8601. Defaults to 6 days before end_date. A value with no offset is treated as UTC
end_dateISO 8601. Defaults to now; a future value is clamped to now

The window may not exceed 90 days, and start_date must be on or before end_date; either violation is a 422.

# Default window (the last 6 days)
curl https://api.callmissed.com/api/v1/email/emails/metrics \
  -H "Authorization: Bearer cm_your_key"

# An explicit window
curl -G https://api.callmissed.com/api/v1/email/emails/metrics \
  -H "Authorization: Bearer cm_your_key" \
  --data-urlencode "start_date=2026-07-01T00:00:00Z" \
  --data-urlencode "end_date=2026-07-31T23:59:59Z"
{
  "start_date": "2026-07-01T00:00:00Z",
  "end_date": "2026-07-31T23:59:59Z",
  "sent": 4820,
  "delivered": 4731,
  "bounced": 61,
  "complained": 3,
  "opened": 5904,
  "unique_opened": 2140,
  "clicked": 812,
  "unique_clicked": 655,
  "tracked_opens": 4820,
  "tracked_clicks": 4820,
  "delivery_rate": 0.9815,
  "bounce_rate": 0.0127,
  "complaint_rate": 0.0006,
  "open_rate": 0.4439,
  "click_rate": 0.1359
}
FieldTypeNotes
start_datestringThe window actually used, after defaults and clamping
end_datestringAs above
sentintegerSends accepted for delivery in the window. This is the denominator for the delivery, bounce and complaint rates
deliveredintegerConfirmed delivered
bouncedintegerBounced
complainedintegerMarked as spam
openedintegerTotal open hits. A mail client refetching the pixel increments this
unique_openedintegerDistinct sends that were opened at least once
clickedintegerTotal click hits
unique_clickedintegerDistinct sends that were clicked at least once
tracked_opensintegerSends in the window that actually carried an open pixel
tracked_clicksintegerSends in the window that actually carried rewritten links
delivery_ratenumberdelivered / sent
bounce_ratenumberbounced / sent
complaint_ratenumbercomplained / sent
open_ratenumberunique_opened / tracked_opens
click_ratenumberunique_clicked / tracked_clicks

Every rate is a fraction in [0, 1] rounded to 4 decimal places. An empty window returns zeros rather than nulls or an error, so a graph always has a number to plot.

Open and click rates divide by the tracked subset, not by sent. A message that carried no pixel cannot be opened, so counting it in the denominator would understate your real open rate. Whether a send carried tracking is recorded at send time, which is what keeps a rate meaningful across a window where you flipped a domain toggle. If tracked_opens is 0, tracking is off for the domains you sent from: see Open and click tracking.

Only sends accepted for delivery are counted. A rejected or still-queued send never reached a mailbox, so it is not in any denominator.

This is the aggregate total for one window. There is no per-day or per-dimension breakdown; call it once per window you want to chart.

Pricing

30 credits (₹30) per 1,000 emails, charged per recipient to your credit balance, the same credits as every other API (your signup bonus counts). Only accepted sends are billed; rejected or failed sends cost nothing. See Credits & Pricing.

Common failures on these routes

StatusBodyMeaning
401string detailMissing, malformed or unrecognised Authorization header
403string detailThe API key is read-only and this route writes (suppression writes only)
404string detailThe suppression id, or the send id, is not yours
422string detailAn unknown status on GET /sends, a date window over 90 days, until before since, or an unknown reason on POST /suppressions/batch
422schema array detailAn unknown reason on POST /suppressions, or a malformed address in a batch

Every shape is spelled out on Limits, Quotas & Errors.