Skip to main content

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": {}
}
FieldTypeDescription
serviceUrlstringBase URL of the EmailEngine instance that generated the event
eventstringEvent name, one of the names in the complete event list
accountstringAccount the event belongs to
datestringWhen the event was generated (ISO 8601)
dataobjectEvent-specific payload
pathstring, optionalMailbox path, on message and mailbox events
specialUsestring, optionalSpecial-use flag of that mailbox, such as \Inbox or \Sent, when the server reports one
_routeobject, optionalPresent 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

EventCategoryTrigger
accountAddedAccountAccount registered
accountDeletedAccountAccount removed
accountInitializedAccountFirst sync completed
authenticationErrorAccountAuthentication failed
authenticationSuccessAccountAuthentication succeeded
connectErrorAccountConnection to the mail server failed
messageNewMessageMessage found in a folder
messageDeletedMessageMessage no longer found in a folder
messageUpdatedMessageFlags or labels changed
messageMissingMessageExpected message could not be fetched
messageSentSendingQueued message accepted by the mail server
messageDeliveryErrorSendingOne delivery attempt failed, will be retried
messageFailedSendingAll delivery attempts failed
messageBounceSendingBounce received
messageComplaintSendingAbuse report received
trackOpenTrackingTracking pixel requested
trackClickTrackingTracked link followed
listUnsubscribeListRecipient unsubscribed
listSubscribeListRecipient re-subscribed
mailboxNewMailboxFolder found
mailboxDeletedMailboxFolder no longer found
mailboxResetMailboxStored message IDs for a folder are no longer valid
exportCompletedExportExport finished
exportFailedExportExport 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.

FieldAppears when
data.text.plain, data.text.html, data.text.hasMorenotifyText is on (it is by default), up to notifyTextSize bytes
data.text.webSafenotifyWebSafeHtml is on; the HTML is then the web-safe rendering
data.attachments[]notifyAttachments is on; attachments over notifyAttachmentSize are skipped
data.headersThe header is named in notifyHeaders
data.summarygenerateEmailSummary is on
data.embeddingsopenAiGenerateEmbeddings is on
data.labelsGmail accounts (IMAP and API), and MS Graph accounts, where the array carries Outlook categories
data.categoryGmail accounts with resolveGmailCategories on
data.emailId, data.threadIdThe server provides them: Gmail, MS Graph and IMAP servers with OBJECTID support
data.cc, data.bcc, data.replyTo, data.sender, data.inReplyToThe message carries the header, and for sender and replyTo, only when they differ from from
data.isAutoReply, data.isBounce, data.isComplaintDetected on the message
data.seemsLikeNewmessageNew 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