Event reference
Every hook emailkit can call — when it fires, what it carries, and what to do with it.
Everything emailkit tells your app arrives through one hooks object on the client. Four groups, all optional — handle what you care about, skip the rest:
EmailKit({
emailDrivers: [...],
hooks: {
email: { ... }, // messages: received, sent, delivered, opened, clicked…
mailbox: { ... }, // inboxes users connect — save what emailkit hands you
domain: { ... }, // sending domains as they're created and verified
webhook: { ... }, // the provider subscriptions emailkit manages
},
});
Whichever provider produced an event, your hook sees the same shape. This page walks through when each hook fires; every hook links to its own page with the exact payload and a working handler.
The life of an email
Ten email hooks sounds like a lot, but they tell one story. Sending:
Your app sends a message.
The provider accepted it. Record the send under its messageId — every later event carries the same one.
The recipient’s server took the message, with responseTime when the provider measures it.
The message was opened — client, device, and location when available, plus a botDetection verdict so scanners don’t count.
A tracked link was followed. url says which one; the same client and bot data comes along.
Receiving is one hook:
The full message: addresses, subject, text and HTML bodies, reply context for threading, headers, and attachments.
When sending goes wrong
Three hooks cover the unhappy paths — in a good week, none of them fire:
The message couldn’t be delivered. severity separates hard from soft bounces — suppress recipients that bounce permanent.
The recipient marked it as spam. Stop emailing them before it hurts your sender reputation.
The provider refused the send — blocked address, policy, suppression list. reason and category say why.
Catch-alls
Every email event, before the specific hook runs, tagged with its type and the emailDriver that produced it. Built for logging, metrics, and one-table event stores.
A verified provider event emailkit can’t normalize. Rare — but nothing gets silently dropped.
Bots don’t count
Raw open and click numbers lie: security scanners open every message and follow every link before a human ever sees it. emailkit classifies each open and click and attaches the verdict to the event — no setup, it’s on every onOpened and onClicked call:
onOpened: async (event) => {
if (event.botDetection?.isBot) return; // e.g. reason: "known-bot-agent"
await markOpened(event.messageId);
},
The classifier checks known scanner user agents (Barracuda, Outlook safe-link crawlers, HTTP libraries), opens arriving impossibly fast after send, HEAD requests on tracked links, and malformed browser signatures. It also knows the Gmail, Yahoo, and Outlook image proxies are real humans opening mail, so those still count. reason tells you which rule decided.
Need the same logic elsewhere? checkOpenBot and checkClickBot are exported from emailkit.
Resource hooks
Email hooks tell you what happened; resource hooks tell you what to save. emailkit keeps no database, so whenever something worth remembering changes — a connected inbox, a domain, a webhook subscription — it hands the new state to your app. Write it down and you’re done. (How emailkit works explains this stateless design.)
A mailbox was connected — OAuth flow or mailboxes.connect(). Comes with the mailbox and, when the driver returns one, its auth — encrypt and store both.
emailkit refreshed the tokens. Overwrite the stored auth — the old one is dead.
A mailbox was created through the API, on providers that support it.
The mailbox is gone — clean up your records.
A domain was registered with the provider. The event carries the DNS records to show your user.
A domains.verify() check passed — the domain can send.
The domain was removed.
emailkit registered a webhook with a provider. Persist it — mailbox subscriptions expire and get renewed.
A subscription was renewed or its status changed. Store the new state; previousWebhook shows what changed.
The subscription no longer exists.
emailkit can’t fix this alone — auth revoked, endpoint unreachable. reason plus recommendedActions tell you the next move.
Notifications were missed or history gapped. Run a sync to replay the gap.
Every webhook lifecycle event in one stream.
Every resource event names its emailDriver, includes the affected item, and keeps the provider’s original payload in raw.
Good to know
- Live webhooks and recovered events both use the same hooks.
- The same event can arrive more than once, so hooks should safely ignore duplicates.
- emailkit verifies provider webhooks before running your hooks.
- A field stays optional when the provider does not supply it; emailkit does not invent a value.
- The original event stays available in
rawfor debugging and provider-specific work.
