Webhook Events Reference
Complete reference for all webhook event types in EmailEngine. Each event includes detailed payload structure, field types, conditional fields, and provider-specific features.
Event Structure
All webhook events follow this common structure:
{
"serviceUrl": "https://emailengine.example.com",
"event": "eventName",
"account": "account-id",
"date": "2025-01-15T10:30:00.000Z",
"data": {
/* event-specific payload */
}
}
Note: The eventId is NOT included in the JSON payload. It's sent as the HTTP header X-EE-Wh-Event-Id.
Universal Fields
These fields appear in every webhook event JSON payload:
| Field | Type | Description |
|---|---|---|
serviceUrl | string | Base URL of the EmailEngine instance that generated the event |
event | string | Event type identifier (e.g., "messageNew", "messageSent") |
account | string | Account identifier that triggered the event |
date | string | ISO 8601 timestamp when event occurred |
data | object | Event-specific payload data |
Optional Universal Fields
| Field | Type | Description |
|---|---|---|
path | string | Mailbox path where the event occurred (message and mailbox events) |
specialUse | string | Special-use flag of the folder (e.g., "\All", "\Inbox", "\Sent") |
_route | object | Present when event is delivered through a Webhook Router, contains _route.id |
Webhook Headers
EmailEngine includes diagnostic headers in every webhook HTTP request:
| Header | Type | Example | Description |
|---|---|---|---|
X-EE-Wh-Event-Id | string | af8435d9-ceee-4715-be71-08ac9d2dc04a | Unique event identifier (UUID). Use for idempotency - all retries share the same ID. This is the ONLY place eventId is available - it's NOT in the JSON payload. |
X-EE-Wh-Id | string | 907889 | Internal BullMQ job ID of the queued webhook entry |
X-EE-Wh-Attempts-Made | string | 0 | Delivery attempt counter (starts at 0, increases with retries) |
X-EE-Wh-Queued-Time | string | 5s | Time the event spent in queue before delivery |
X-EE-Wh-Custom-Route | string | AAABiL8tBKsAAAAG | Identifier of the custom webhook route (only present for webhook routes) |
X-EE-Wh-Signature | string | dGhpcyBpcyBh... | HMAC-SHA256 signature (base64url) of the JSON body using EENGINE_SECRET |
Content-Type | string | application/json | Always application/json |
User-Agent | string | emailengine/2.x.x (+https://emailengine.app) | EmailEngine version and homepage |
Webhook Signature Verification
The X-EE-Wh-Signature header contains an HMAC-SHA256 signature of the request body:
const crypto = require('crypto');
function verifyWebhook(req, secret) {
const signature = req.headers['x-ee-wh-signature'];
const body = JSON.stringify(req.body);
const hmac = crypto.createHmac('sha256', secret);
hmac.update(body);
const expected = hmac.digest('base64url');
return signature === expected;
}
// Usage
if (!verifyWebhook(req, process.env.EENGINE_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
You can also configure custom headers via webhooksCustomHeaders in Settings or Configuration → Webhooks.
Account Events
Events related to account connection and status changes.
accountAdded
Triggered when a new account is registered in EmailEngine.
Payload:
{
"serviceUrl": "https://emailengine.example.com",
"event": "accountAdded",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"account": "user@example.com"
}
}
Fields:
data.account(string) - Account identifier
To get the account's display name, email address and connection state after this event, query the account API endpoint (GET /v1/account/:account).
Use Cases:
- Send welcome notification
- Initialize account-specific resources
- Log account creation
- Start onboarding flow
accountDeleted
Triggered when an account is removed from EmailEngine.
Payload:
{
"serviceUrl": "https://emailengine.example.com",
"event": "accountDeleted",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"account": "user@example.com"
}
}
Fields:
data.account(string) - Deleted account identifier
Use Cases:
- Clean up account-related resources
- Remove from billing system
- Archive account data
- Send farewell notification
accountInitialized
Triggered when an account successfully connects for the first time and completes initial mailbox synchronization.
Payload:
{
"serviceUrl": "https://emailengine.example.com",
"event": "accountInitialized",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"initialized": true
}
}
Fields:
data.initialized(boolean) - Alwaystrue, indicates account has completed initialization
Use Cases:
- Notify user that account is ready
- Start background processing
- Enable account features
- Trigger initial data import
Note: To get full account details after initialization, query the account API endpoint (GET /v1/account/:account) which returns the account state, connection status, and other metadata.
authenticationError
Triggered when account authentication fails.
Payload:
{
"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"
}
}
Fields:
data.response(string) - Human-readable error description (often the server's response text)data.serverResponseCode(string, optional) - Server or error response code (for exampleAUTHENTICATIONFAILED, orOauthRenewErrorfor OAuth2 accounts)data.tokenRequest(object, optional) - OAuth2 token-renewal details when the failure occurred while refreshing an access token (includesgrant,provider,status,clientId,scopes)
Use Cases:
- Prompt user to re-authenticate
- Revoke access tokens
- Send security alerts
- Log authentication failures
authenticationSuccess
Triggered when account authenticates successfully.
Payload:
{
"serviceUrl": "https://emailengine.example.com",
"event": "authenticationSuccess",
"account": "user@example.com",
"date": "2025-01-15T10:30:00.000Z",
"data": {
"user": "user@example.com"
}
}
Fields:
data.user(string) - The username (login) that authenticated successfully
Use Cases:
- Clear authentication error flags
- Resume account operations
- Log successful authentications
- Update account status
connectError
Triggered when connection to the email server fails.
Payload:
{
"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"
}
}
Fields:
data.response(string) - Human-readable connection error descriptiondata.serverResponseCode(string, optional) - Connection error code (for exampleECONNREFUSED,ETIMEDOUT,ENOTFOUND)
Use Cases:
- Monitor server availability
- Trigger network diagnostics
- Alert administrators
- Log connectivity issues
Message Events
Events related to message operations and changes.
messageNew
Triggered when a new message arrives in any mailbox. Also triggered when messages are moved, copied, or uploaded to folders.
Note: IMAP does not distinguish between incoming messages and messages inserted by other means.
Payload:
{
"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": ["\\Seen"],
"labels": ["\\Important", "\\Inbox"],
"category": "primary",
"unseen": false,
"flagged": false,
"answered": false,
"draft": false,
"size": 8271,
"subject": "Important Message",
"from": {
"name": "John Doe",
"address": "john@example.com"
},
"sender": {
"name": "John Doe",
"address": "john@example.com"
},
"replyTo": [
{
"name": "John Doe",
"address": "john@example.com"
}
],
"to": [
{
"name": "Jane Smith",
"address": "jane@example.com"
}
],
"cc": [
{
"name": "Bob Johnson",
"address": "bob@example.com"
}
],
"bcc": [
{
"name": "Alice Williams",
"address": "alice@example.com"
}
],
"messageId": "<abc123@example.com>",
"inReplyTo": "<previous@example.com>",
"headers": {
"list-id": "<mailinglist.example.com>",
"x-custom-header": ["value1", "value2"]
},
"text": {
"id": "text_123",
"encodedSize": {
"plain": 1535,
"html": 1630
},
"plain": "Message content...",
"html": "<p>Message content...</p>",
"webSafe": "<p>Sanitized HTML...</p>",
"hasMore": false
},
"attachments": [
{
"id": "att_456",
"contentType": "application/pdf",
"filename": "document.pdf",
"encodedSize": 52341,
"embedded": false,
"inline": false,
"contentId": "<part1.abc@example.com>"
}
],
"messageSpecialUse": "\\Inbox",
"seemsLikeNew": true,
"isAutoReply": false,
"isBounce": false,
"isComplaint": false
}
}
Core Fields:
data.id(string) - EmailEngine message ID (use for API operations)data.uid(number) - IMAP UIDdata.path(string) - Mailbox pathdata.emailId(string, optional) - RFC 8474 Email ID (Gmail, modern IMAP servers)data.threadId(string, optional) - Thread/conversation ID (Gmail, modern IMAP servers)data.date(string) - Message Date header (ISO 8601)data.flags(array of strings) - IMAP flags (e.g., "\Seen", "\Flagged", "\Answered", "\Draft")data.unseen(boolean) - Message is unread (no \Seen flag)data.flagged(boolean) - Message is flaggeddata.answered(boolean) - Message has been replied todata.draft(boolean) - Message is a draftdata.size(number) - Full RFC 822 message size in bytesdata.subject(string) - Email subject linedata.messageId(string) - RFC 5322 Message-ID headerdata.inReplyTo(string, optional) - Message-ID of the message being replied to
Address Fields:
Each address object contains:
name(string) - Display nameaddress(string) - Email address
Fields:
data.from(object) - Sender addressdata.sender(object, optional) - Actual sender (when different from From)data.replyTo(array of objects, optional) - Reply-To addressesdata.to(array of objects) - Recipientsdata.cc(array of objects, optional) - CC recipientsdata.bcc(array of objects, optional) - BCC recipients
Content Fields (Conditional):
Included when Configuration → Webhooks → Text content is enabled (notifyText: true):
data.text(object, optional) - Message text contentdata.text.id(string) - Text content identifierdata.text.encodedSize(object) - Size informationdata.text.encodedSize.plain(number) - Plain text size in bytesdata.text.encodedSize.html(number) - HTML size in bytes
data.text.plain(string) - Plain text content (up tonotifyTextSizelimit)data.text.html(string) - HTML content (up tonotifyTextSizelimit)data.text.webSafe(string, optional) - Sanitized HTML (whennotifyWebSafeHtml: true)data.text.hasMore(boolean) - Content was truncated
Attachment Fields (Conditional):
Included when Configuration → Webhooks → Attachments is enabled (notifyAttachments: true):
data.attachments(array of objects, optional) - Attachment metadataid(string) - Attachment identifiercontentType(string) - MIME typefilename(string) - FilenameencodedSize(number) - Size of the attachment as stored in the email (base64 encoded); the decoded file size is approximately 75% of this valueembedded(boolean, optional) - Is embedded imageinline(boolean, optional) - Is inline attachmentencodedInMessage(boolean, optional) - Whether the attachment belongs to an enclosed message/rfc822 part rather than the top-level messagecontentId(string, optional) - Content-ID header valuemethod(string, optional) - Calendar method (REQUEST, REPLY, CANCEL, etc.) for iCalendar attachments
Gmail-Specific Fields:
data.labels(array of strings, optional) - Gmail labels (e.g., "\Important", "\Inbox", "\Starred")data.category(string, optional) - Gmail category tab ("primary", "social", "promotions", "updates", "forums")- Requires Configuration → Service → Labs → Resolve Gmail categories enabled
data.messageSpecialUse(string, optional) - Special-use flag that best classifies the message (prefer over top-levelspecialUse)
Header Fields (Conditional):
Included when headers are specified in notifyHeaders setting:
data.headers(object, optional) - Requested email headers- Keys are lowercase header names
- Values are strings or arrays for multi-value headers
Metadata Fields:
data.seemsLikeNew(boolean, optional) - EmailEngine has no prior record of this message (~99% accuracy)data.isAutoReply(boolean, optional) - Message appears to be an auto-replydata.isBounce(boolean, optional) - Message appears to be a bouncedata.isComplaint(boolean, optional) - Message appears to be an abuse complaint
AI Feature Fields (Conditional):
Included when AI features are enabled:
data.summary(object, optional) - AI-generated analysis (whengenerateEmailSummary: true). Containssummary(text),sentiment,shouldReply(boolean),events(array),actions(array), plus generation metadata (id,tokens,model)data.embeddings(array of numbers, optional) - Vector embeddings (whenopenAiGenerateEmbeddings: true)data.riskAssessment(object, optional) - AI risk analysisdata.riskAssessment.risk(integer) - Risk score where a higher value indicates higher riskdata.riskAssessment.assessment(string) - Human-readable explanation of the risk scoredata.riskAssessment.id(string, optional) - Generation identifierdata.riskAssessment.tokens(number, optional) - Tokens consumed generating the assessment
Use Cases:
- Real-time email notifications
- Auto-reply systems
- Email-to-ticket conversion
- Message classification
- Attachment processing
- Email analytics
- Spam filtering
- Thread management
Example Integration:
app.post('/webhook', async (req, res) => {
const event = req.body;
const eventId = req.headers['x-ee-wh-event-id'];
if (event.event === 'messageNew') {
const { data } = event;
// Check idempotency using header
if (await isProcessed(eventId)) {
return res.json({ success: true }); // Already handled
}
// Notify user
await sendPushNotification({
title: `New email from ${data.from.address}`,
body: data.subject
});
// Process attachments
if (data.attachments?.length > 0) {
await processAttachments(data.id, data.attachments);
}
// Auto-classify
if (data.subject?.includes('invoice')) {
await moveToFolder(data.id, 'Invoices');
}
// Mark as processed
await markProcessed(eventId);
res.json({ success: true });
}
});
messageDeleted
Triggered when a message is deleted from a mailbox or moved to another folder.
Payload:
{
"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
}
}
Fields:
path(string) - Mailbox path the message was deleted from (top-level field, alongsideaccount)specialUse(string, optional) - Special-use flag of that mailbox (top-level field)data.id(string) - EmailEngine message IDdata.uid(number) - IMAP UID (no longer valid)
For IMAP accounts the data object contains only id and uid. Gmail API and Microsoft Graph accounts include provider-specific fields (for example Gmail adds threadId and the last known labels). See messageDeleted for the per-provider payloads.
Use Cases:
- Sync deletions to local database
- Track deleted messages
- Compliance logging
- Undo deletion feature
- Analytics (deletion patterns)