Guides

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.