Handling delivery events
Polling the event log versus receiving webhooks, and what each event means.
A 200 from POST /messages means we accepted the message. Everything after that is an event.
Two ways to find out
Poll the event log for one-off checks, reconciliation, and anything historical:
curl "$MAILYTE_BASE/api/v1/email-logs/messages/<message_id>" \
-H "Authorization: Bearer $MAILYTE_API_KEY"Receive webhooks for anything ongoing. They arrive sooner and cost you no requests.
Use both: webhooks for the live path, the log to reconcile after an outage. The log is the durable record; webhooks are a notification layer on top of it.
The lifecycle
| Event | Means | Act on it? |
|---|---|---|
email.accepted |
We have the message and have queued it | No — it is a receipt |
email.delivered |
The receiving server took it | Mark sent |
email.deferred |
Temporarily refused; we will retry | No — retrying yourself doubles the mail |
email.bounced |
Permanently refused | Yes — stop sending to this address |
email.complained |
Marked as spam by the recipient | Yes, urgently |
email.dropped |
We refused to send it | Yes — usually a suppression |
email.opened |
A tracking pixel loaded | Cautiously — see below |
email.clicked |
A tracked link was followed | Cautiously |
Deferred is not a bounce
A deferral is the receiving server saying "not now". Greylisting, a full mailbox, a rate limit at their end. We retry on a schedule designed not to make it worse.
The mistake is treating a deferral as a failure and sending again from your side. Now two copies are in flight, the receiving server sees a sender that ignores backpressure, and a temporary problem becomes a reputation one.
Bounced means stop
A bounce is permanent: the mailbox does not exist, or the domain does not accept mail. The address is added to your suppression list automatically, and further sends to it are refused.
That refusal is a feature. Repeatedly mailing addresses that do not exist is one of the clearest signals of a list that was not collected honestly, and receiving servers weight it heavily.
Complaints matter more than they look
A complaint is someone pressing "this is spam". The volume needed to damage a sending reputation is much smaller than most people expect — a fraction of a percent sustained is enough.
Treat one complaint as a permanent, immediate opt-out from everything, not just the campaign it came from.
Opens and clicks are unreliable, by nature
Not a limitation of our implementation. A limitation of the technique.
Open tracking works by embedding a remote image. So:
- Privacy proxies fetch it on the recipient's behalf, often immediately and sometimes for mail the person never opens. Apple Mail Privacy Protection does this by default for a large share of consumer mail.
- Many clients block remote images entirely, so a genuine open records nothing.
- Security scanners follow links before the recipient sees the message, producing clicks nobody made.
The result is that opens over-count in one direction and under-count in the other, and clicks can be entirely machine-generated. We have seen this concretely: a 30-day window here once contained 499 opens and 36 clicks that were, on inspection, all our own infrastructure and no human at all.
Use them as a weak signal about a population, never as evidence about a person. "Did Ada read my email" is not a question this data can answer. "Is engagement on this segment falling" is.
Reconciling
After an outage, do not guess at what you missed:
curl "$MAILYTE_BASE/api/v1/email-logs?since=2026-09-17T00:00:00Z&event=bounced" \
-H "Authorization: Bearer $MAILYTE_API_KEY"Walk the log from your last known good timestamp and apply anything you have not already recorded. If your webhook handler is idempotent — and it should be — replaying events you already have is harmless.
Next
Suppressions and compliance — what happens to the addresses that bounce and complain.