Webhook Events Reference
Every webhook event EmailEngine sends, in one place: the envelope they share, a summary of each event with its key data fields and a compact example, and the conditions under which optional fields appear. Each event has its own page under Webhooks with the full field-by-field schema and handler examples; this page is the lookup table that points to them.
Event Structure
All webhook events share this envelope:
{
"serviceUrl": "https://emailengine.example.com",
"event": "messageNew",
"account": "user@example.com",
"path": "INBOX",
"specialUse": "\\Inbox",
"date": "2025-01-15T10:30:00.000Z",
"data": {}
}
| Field | Type | Description |
|---|---|---|
serviceUrl | string | Base URL of the EmailEngine instance that generated the event |
event | string | Event name, one of the names in the complete event list |
account | string | Account the event belongs to |
date | string | When the event was generated (ISO 8601) |
data | object | Event-specific payload |
path | string, optional | Mailbox path, on message and mailbox events |
specialUse | string, optional | Special-use flag of that mailbox, such as \Inbox or \Sent, when the server reports one |
_route | object, optional | Present when the event is delivered through a webhook route; carries _route.id |
The event ID is not in the body. It is sent as the X-EE-Wh-Event-Id header, and every retry of the same event carries the same ID, so deduplicate on the header. The full header set, including the X-EE-Wh-Signature HMAC and how to verify it, is documented under Webhook HTTP headers and Verify webhook authenticity.
Account Events
accountAdded
A new account was registered with EmailEngine.
{
"serviceUrl": "https://emailengine.example.com",
"event": "accountAdded",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"account": "user@example.com"
}
}
data.account is the only field. Query GET /v1/account/{account} for the name, address and state. Full schema: accountAdded.
accountDeleted
An account was removed from EmailEngine.
{
"serviceUrl": "https://emailengine.example.com",
"event": "accountDeleted",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"account": "user@example.com"
}
}
Full schema: accountDeleted.
accountInitialized
The account connected and completed its first mailbox sync.
{
"serviceUrl": "https://emailengine.example.com",
"event": "accountInitialized",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"initialized": true
}
}
data.initialized is always true. Full schema: accountInitialized.
authenticationError
EmailEngine failed to authenticate the account, against the mail server or the OAuth2 token endpoint.
{
"serviceUrl": "https://emailengine.example.com",
"event": "authenticationError",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"response": "Invalid credentials (Failure)",
"serverResponseCode": "AUTHENTICATIONFAILED"
}
}
Key fields: data.response (the server's text), data.serverResponseCode (optional, for example AUTHENTICATIONFAILED, or OauthRenewError for OAuth2 accounts), data.tokenRequest (optional, details of a failed OAuth2 token refresh). Sent again, with the same event name, when repeated failures make the authentication-failure safety net switch syncing off for the account. Full schema: authenticationError.
authenticationSuccess
The account authenticated successfully.
{
"serviceUrl": "https://emailengine.example.com",
"event": "authenticationSuccess",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"user": "user@example.com"
}
}
data.user is the login that succeeded. Full schema: authenticationSuccess.
connectError
EmailEngine could not establish a connection to the mail server.
{
"serviceUrl": "https://emailengine.example.com",
"event": "connectError",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"response": "connect ECONNREFUSED 192.168.1.100:993",
"serverResponseCode": "ECONNREFUSED"
}
}
Key fields: data.response, data.serverResponseCode (optional, for example ECONNREFUSED, ETIMEDOUT, ENOTFOUND). Full schema: connectError.
Message Events
messageNew
A message was found in a folder that was not there before. IMAP does not distinguish an arriving message from one moved, copied or uploaded into the folder, so all of those trigger it. inboxNewOnly limits it to the Inbox.
{
"serviceUrl": "https://emailengine.example.com",
"event": "messageNew",
"account": "user@example.com",
"path": "INBOX",
"specialUse": "\\Inbox",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"id": "AAAABAABNc",
"uid": 12345,
"path": "INBOX",
"emailId": "abc123",
"threadId": "thread_xyz",
"date": "2025-01-15T10:25:00.000Z",
"flags": [],
"unseen": true,
"flagged": false,
"answered": false,
"draft": false,
"size": 8271,
"subject": "Important Message",
"from": {
"name": "John Doe",
"address": "john@example.com"
},
"to": [
{
"name": "Jane Smith",
"address": "jane@example.com"
}
],
"messageId": "<abc123@example.com>",
"text": {
"id": "text_123",
"encodedSize": {
"plain": 1535,
"html": 1630
},
"plain": "Message content...",
"html": "<p>Message content...</p>",
"hasMore": false
},
"attachments": [
{
"id": "att_456",
"contentType": "application/pdf",
"filename": "document.pdf",
"encodedSize": 52341
}
],
"messageSpecialUse": "\\Inbox",
"seemsLikeNew": true
}
}
Key fields: data.id (the message ID for API calls), data.uid, data.emailId and data.threadId (when the server provides them), data.date, data.flags, data.unseen, data.subject, the address fields from, sender, replyTo, to, cc, bcc, data.messageId, data.inReplyTo, data.text (with notifyText), data.attachments (with notifyAttachments), data.headers (with notifyHeaders), data.labels and data.category (Gmail), data.messageSpecialUse, data.seemsLikeNew, data.isAutoReply, data.isBounce, data.isComplaint, data.summary and data.embeddings (AI processing). The conditions are summarized under Conditional fields. Full schema: messageNew.
messageDeleted
A message previously present in a folder is no longer there, whether it was deleted or moved.
{
"serviceUrl": "https://emailengine.example.com",
"event": "messageDeleted",
"account": "user@example.com",
"path": "INBOX",
"specialUse": "\\Inbox",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"id": "AAAABAABNc",
"uid": 12345
}
}
For IMAP accounts data holds only id and uid. Gmail API and Microsoft Graph accounts add provider fields such as threadId and the last known labels. Full schema: messageDeleted.
messageUpdated
Flags or labels of a message changed.
{
"serviceUrl": "https://emailengine.example.com",
"event": "messageUpdated",
"account": "user@example.com",
"path": "INBOX",
"specialUse": "\\Inbox",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"id": "AAAABAABNc",
"uid": 12345,
"changes": {
"flags": {
"added": ["\\Seen"],
"removed": [],
"value": ["\\Seen", "\\Flagged"]
}
}
}
}
Key fields: data.changes.flags and, on Gmail, data.changes.labels, each with added, removed and the full current value. Full schema: messageUpdated.
messageMissing
A message EmailEngine expected to find could not be fetched after several retries, which points at a syncing problem.
{
"serviceUrl": "https://emailengine.example.com",
"event": "messageMissing",
"account": "user@example.com",
"path": "INBOX",
"specialUse": "\\Inbox",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"id": "AAAABAABNc",
"uid": 12345,
"missingRetries": 5,
"missingDelay": 12450
}
}
Key fields: data.id, data.uid, data.missingRetries (fetch attempts made), data.missingDelay (milliseconds spent retrying). Full schema: messageMissing.
Mailbox Events
mailboxNew
A folder was found that was not there before.
{
"serviceUrl": "https://emailengine.example.com",
"event": "mailboxNew",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"path": "Projects/2025",
"name": "2025",
"specialUse": false,
"uidValidity": "1697551353"
}
}
Key fields: data.path, data.name, data.specialUse (a flag such as \Sent, or false), data.uidValidity (a string). Full schema: mailboxNew.
mailboxDeleted
A previously present folder is no longer found.
{
"serviceUrl": "https://emailengine.example.com",
"event": "mailboxDeleted",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"path": "Old/Archive",
"name": "Archive",
"specialUse": false
}
}
Full schema: mailboxDeleted.
mailboxReset
The message IDs stored for a folder stopped being usable: the server reported a different UIDVALIDITY (reason: "uidValidityChange") or EmailEngine had to rebuild its own index for the folder (reason: "syncStateLost"). Refetch the folder; every stored ID for it is now invalid.
{
"serviceUrl": "https://emailengine.example.com",
"event": "mailboxReset",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"path": "INBOX",
"name": "INBOX",
"specialUse": "\\Inbox",
"uidValidity": "1234567899",
"prevUidValidity": "1234567890",
"reason": "uidValidityChange"
}
}
Key fields: data.path, data.uidValidity, data.prevUidValidity (both strings), data.reason. Full schema: mailboxReset.
Sending Events
messageSent
A queued message was accepted by the outgoing mail server. Acceptance is not delivery; a later bounce arrives as messageBounce.
{
"serviceUrl": "https://emailengine.example.com",
"event": "messageSent",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"messageId": "<abc123@example.com>",
"response": "250 2.0.0 Ok: queued as ABC123",
"queueId": "queue_456",
"envelope": {
"from": "sender@example.com",
"to": ["recipient@example.com"]
}
}
}
Key fields: data.messageId (the final Message-ID; data.originalMessageId is added when the server rewrote it, as Amazon SES, AWS WorkMail and Microsoft Graph do), data.response, data.queueId, data.envelope.from and data.envelope.to, data.networkRouting (optional). There is no to, subject or gateway field; correlate on queueId. Full schema: messageSent.
messageDeliveryError
One delivery attempt failed. EmailEngine retries, and sends one of these per failed attempt.
{
"serviceUrl": "https://emailengine.example.com",
"event": "messageDeliveryError",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"queueId": "queue_456",
"envelope": {
"from": "sender@example.com",
"to": ["invalid@example.com"]
},
"messageId": "<abc123@example.com>",
"error": "Recipient address rejected: User unknown",
"errorCode": "EPROTOCOL",
"smtpResponse": "550 5.1.1 <invalid@example.com>: Recipient address rejected: User unknown",
"smtpResponseCode": 550,
"smtpCommand": "RCPT TO",
"job": {
"id": "42",
"attemptsMade": 1,
"attempts": 10,
"nextAttempt": "2025-01-15T10:07:45.465Z"
}
}
}
Key fields: data.error, data.errorCode, data.smtpResponse, data.smtpResponseCode, data.smtpCommand, data.job (attemptsMade, attempts, nextAttempt). Full schema: messageDeliveryError.
messageFailed
Every delivery attempt failed and the message was abandoned.
{
"serviceUrl": "https://emailengine.example.com",
"event": "messageFailed",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"messageId": "<abc123@example.com>",
"queueId": "queue_456",
"error": "Error: Invalid login: 535 5.7.8 Error: authentication failed"
}
}
Key fields: data.messageId, data.queueId, data.error (first line of the final error), data.networkRouting (optional). Full schema: messageFailed.
messageBounce
A bounce (delivery status notification) for a sent message arrived in the mailbox. One event per bounced recipient.
{
"serviceUrl": "https://emailengine.example.com",
"event": "messageBounce",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"bounceMessage": "AAAAAQAABWw",
"recipient": "bounced@example.com",
"action": "failed",
"response": {
"source": "smtp",
"message": "550 5.1.1 <bounced@example.com>: Recipient address rejected: User unknown",
"status": "5.1.1",
"category": "user_unknown",
"recommendedAction": "remove"
},
"mta": "mx.example.com",
"messageId": "<abc123@example.com>"
}
}
Key fields: data.bounceMessage (the ID of the bounce message itself), data.recipient, data.action (failed, delayed, delivered, relayed, expanded), data.response (status, category, recommendedAction, and blocklist or retryAfter when detected), data.messageId (the bounced message), data.messageHeaders. There is no bounceType field: action and response.status say whether the failure is permanent. The bounce categories and recommended actions are listed on the event page. Full schema: messageBounce.
messageComplaint
An abuse report (ARF feedback loop complaint) arrived in the mailbox, meaning a recipient marked a sent message as spam.
{
"serviceUrl": "https://emailengine.example.com",
"event": "messageComplaint",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"complaintMessage": "AAAAAQAABvE",
"arf": {
"source": "Hotmail",
"feedbackType": "abuse",
"originalRcptTo": ["recipient@hotmail.co.uk"],
"arrivalDate": "2021-10-22T13:04:36.017Z"
},
"headers": {
"messageId": "<abc123@example.com>",
"from": "sender@example.com",
"to": ["recipient@hotmail.co.uk"],
"subject": "Newsletter"
}
}
}
Key fields: data.complaintMessage, data.arf (source, feedbackType, originalRcptTo, arrivalDate, sourceIp, userAgent), data.headers of the complained-about message (coverage depends on the reporting provider). Full schema: messageComplaint.
Tracking Events
Require trackOpens or trackClicks to be enabled, instance-wide or per submission. Both are prone to false positives from clients and security scanners that prefetch images and links.
trackOpen
The tracking pixel of a sent message was requested.
{
"serviceUrl": "https://emailengine.example.com",
"event": "trackOpen",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"messageId": "<abc123@example.com>",
"remoteAddress": "203.0.113.45",
"userAgent": "Mozilla/5.0"
}
}
Key fields: data.messageId, data.remoteAddress, data.userAgent. Full schema: trackOpen.
trackClick
A rewritten link in a sent message was followed.
{
"serviceUrl": "https://emailengine.example.com",
"event": "trackClick",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"messageId": "<abc123@example.com>",
"url": "https://example.com/page",
"remoteAddress": "203.0.113.45",
"userAgent": "Mozilla/5.0"
}
}
Key fields: data.messageId, data.url (the original destination), data.remoteAddress, data.userAgent. Full schema: trackClick.
List Management Events
listUnsubscribe
A recipient used the unsubscribe link EmailEngine added to a message sent with listId, or their mail client issued a one-click unsubscribe request (RFC 8058).
{
"serviceUrl": "https://emailengine.example.com",
"event": "listUnsubscribe",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"recipient": "recipient@example.com",
"messageId": "<abc123@example.com>",
"listId": "my-newsletter-list",
"remoteAddress": "203.0.113.45",
"userAgent": "Mozilla/5.0"
}
}
Key fields: data.recipient, data.messageId, data.listId, data.remoteAddress, data.userAgent. Full schema: listUnsubscribe.
listSubscribe
A recipient re-subscribed to a list after unsubscribing.
{
"serviceUrl": "https://emailengine.example.com",
"event": "listSubscribe",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"recipient": "recipient@example.com",
"listId": "my-newsletter-list",
"remoteAddress": "203.0.113.45",
"userAgent": "Mozilla/5.0"
}
}
Full schema: listSubscribe.
Export Events
Sent by mailbox exports.
exportCompleted
An export finished and its file is ready to download.
{
"serviceUrl": "https://emailengine.example.com",
"event": "exportCompleted",
"account": "user@example.com",
"date": "2025-01-15T10:35:00.000Z",
"data": {
"exportId": "exp_abc123def456abc123def456",
"folders": ["INBOX", "Sent"],
"startDate": "2024-01-01T00:00:00.000Z",
"endDate": "2024-12-31T23:59:59.000Z",
"messagesExported": 450,
"messagesSkipped": 5,
"bytesWritten": 52428800,
"duration": 15000,
"expiresAt": "2025-01-16T10:30:00.000Z"
}
}
Key fields: data.exportId, data.folders, data.startDate, data.endDate, data.messagesExported, data.messagesSkipped, data.bytesWritten, data.duration (ms), data.expiresAt. Full schema: exportCompleted.
exportFailed
An export stopped with an error. There is no resume; start a new export.
{
"serviceUrl": "https://emailengine.example.com",
"event": "exportFailed",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"exportId": "exp_abc123def456abc123def456",
"error": "Connection timeout",
"errorCode": "ConnectionTimeout",
"phase": "exporting",
"messagesExported": 250,
"messagesQueued": 1500
}
}
Key fields: data.exportId, data.error, data.errorCode (optional), data.phase (indexing or exporting), data.messagesExported, data.messagesQueued. Full schema: exportFailed.
Complete Event List
| Event | Category | Trigger |
|---|---|---|
accountAdded | Account | Account registered |
accountDeleted | Account | Account removed |
accountInitialized | Account | First sync completed |
authenticationError | Account | Authentication failed |
authenticationSuccess | Account | Authentication succeeded |
connectError | Account | Connection to the mail server failed |
messageNew | Message | Message found in a folder |
messageDeleted | Message | Message no longer found in a folder |
messageUpdated | Message | Flags or labels changed |
messageMissing | Message | Expected message could not be fetched |
messageSent | Sending | Queued message accepted by the mail server |
messageDeliveryError | Sending | One delivery attempt failed, will be retried |
messageFailed | Sending | All delivery attempts failed |
messageBounce | Sending | Bounce received |
messageComplaint | Sending | Abuse report received |
trackOpen | Tracking | Tracking pixel requested |
trackClick | Tracking | Tracked link followed |
listUnsubscribe | List | Recipient unsubscribed |
listSubscribe | List | Recipient re-subscribed |
mailboxNew | Mailbox | Folder found |
mailboxDeleted | Mailbox | Folder no longer found |
mailboxReset | Mailbox | Stored message IDs for a folder are no longer valid |
exportCompleted | Export | Export finished |
exportFailed | Export | Export failed |
Event Filtering
webhookEvents is an allowlist with no default: an event is delivered only if the list names it or contains "*", so leaving it unset delivers nothing. Webhook routes carry their own filters and are not affected by it.
{
"webhooks": "https://your-app.com/webhook",
"webhooksEnabled": true,
"webhookEvents": ["messageNew", "messageSent", "messageDeliveryError"]
}
Conditional Fields
Fields that appear only under a condition. The setting names are POST /v1/settings keys.
| Field | Appears when |
|---|---|
data.text.plain, data.text.html, data.text.hasMore | notifyText is on (it is by default), up to notifyTextSize bytes |
data.text.webSafe | notifyWebSafeHtml is on; the HTML is then the web-safe rendering |
data.attachments[] | notifyAttachments is on; attachments over notifyAttachmentSize are skipped |
data.headers | The header is named in notifyHeaders |
data.summary | generateEmailSummary is on |
data.embeddings | openAiGenerateEmbeddings is on |
data.labels | Gmail accounts (IMAP and API), and MS Graph accounts, where the array carries Outlook categories |
data.category | Gmail accounts with resolveGmailCategories on |
data.emailId, data.threadId | The server provides them: Gmail, MS Graph and IMAP servers with OBJECTID support |
data.cc, data.bcc, data.replyTo, data.sender, data.inReplyTo | The message carries the header, and for sender and replyTo, only when they differ from from |
data.isAutoReply, data.isBounce, data.isComplaint | Detected on the message |
data.seemsLikeNew | messageNew only |
path, specialUse (top level) | Message and mailbox events |
Delivery and Retries
Webhooks are queued and delivered by the notify queue. A delivery counts as successful on any 2xx response. A failed or timed-out attempt is retried up to 10 attempts in total, with exponential backoff starting at 5 seconds and 20% jitter; each attempt is capped at 30 seconds (EENGINE_WEBHOOK_TIMEOUT). After the last attempt the job stays in the queue's failed set, visible under System > Queues in the admin interface. Details, the two failures that are not retried, and the handler pattern this calls for are under Delivery and retries.
See Also
- Webhooks overview - Setup, headers, signatures, retries and debugging
- Webhook routing - Different events to different endpoints, with their own filters
- Pre-processing functions - Filtering and reshaping payloads before delivery
- Webhooks API - Reading routes programmatically
- Quick reference - The same events in one table with the API and settings summaries