Skip to main content

messageNew

The messageNew webhook event is triggered when EmailEngine detects a new email in a monitored mailbox folder. This is the most commonly used webhook event, and the one whose payload is shaped by the most settings.

When This Event is Triggered

The messageNew event fires when:

  • A new email arrives in a synced mailbox (INBOX, Sent, or any other monitored folder)
  • An existing email is moved or copied into a monitored folder from another folder
  • On Gmail API and MS Graph accounts, a change notification reports a message EmailEngine has not seen before

The event is triggered after EmailEngine has fetched and parsed the message metadata from the mail server. On IMAP accounts a message that the server does not return on the first fetch is retried three times before a messageMissing event is sent instead.

Nothing is sent for messages dated before the account's notifyFrom (IMAP), for accounts whose webhookEvents allowlist does not include messageNew, or, with inboxNewOnly set, for messages outside the Inbox. Webhook routes apply their own filters instead of the last two.

The initial sync does not replay the mailbox

A newly connected IMAP account only reports messages received after notifyFrom, which defaults to the moment the account was registered. The rest of the mailbox is indexed silently, and is reachable through the API without ever producing an event. Set notifyFrom to an earlier date, when registering the account or on a flush, to have older messages reported as well. Gmail API and MS Graph accounts ignore the field and never replay history.

By default the event is sent for every monitored folder. Set inboxNewOnly to limit the default webhook to messages in the Inbox.

When the new message is itself a bounce or an ARF complaint, this event is sent first, with isBounce or isComplaint set, and a messageBounce or messageComplaint event follows it.

Common Use Cases

  • Support ticket creation - Create tickets from incoming support emails
  • Lead capture - Process inquiry emails and add contacts to your CRM
  • Order processing - Parse order confirmation emails
  • AI analysis - Feed incoming emails to language models for classification or summarization
  • Email archival - Store emails in external databases or document management systems
  • Notification forwarding - Send alerts via Slack, SMS, or other channels

Payload Schema

Top-Level Fields

FieldTypeRequiredDescription
serviceUrlstring or nullYesThe configured EmailEngine service URL, null if not set
accountstringYesAccount ID that received the message
datestringYesISO 8601 timestamp when the webhook was generated
pathstringYesFolder the message was found in. IMAP and MS Graph accounts report the folder path (for example INBOX). Gmail API accounts always report \All, because every message lives in All Mail; use data.labels or data.messageSpecialUse for its folder
specialUsestringNoSpecial use flag of the folder, for example \Inbox, \Sent or \Trash. Gmail API accounts report \All
eventstringYesAlways messageNew
dataobjectYesMessage data object (see below)

The unique event identifier is sent as the HTTP header X-EE-Wh-Event-Id, not in the JSON payload.

Message Data Fields (data object)

FieldTypeRequiredDescription
idstringYesEmailEngine message ID. Base64url-packed folder and UID for IMAP, the provider's message ID for Gmail API and MS Graph
uidnumberIMAP onlyIMAP UID of the message within the folder
pathstringNoFolder path. IMAP accounts only. Gmail API and MS Graph accounts report the folder in the top-level path instead
emailIdstringNoGlobally unique message ID: IMAP EMAILID where the server supports OBJECTID, the Gmail message ID, or the Graph message ID
threadIdstringNoThread identifier, when the server or provider supplies one
datestringYesMessage date from headers (ISO 8601)
flagsarrayNoIMAP flags (for example ["\\Seen", "\\Flagged"]). \Recent is never included
labelsarrayNoGmail labels, on Gmail API accounts and on Gmail over IMAP. System labels use their IMAP special-use names (\Inbox, \Sent, \Trash, \Drafts, \Junk); other labels are reported by Gmail label ID on API accounts and by name over IMAP
unseenbooleanNotrue if the message has not been read. Absent when read
flaggedbooleanNotrue if the message is flagged or starred. Absent otherwise
answeredbooleanNotrue if the message has been replied to. Absent otherwise
draftbooleanNotrue if the message is a draft. Absent otherwise
sizenumberNoMessage size in bytes
subjectstringNoDecoded subject line
fromobjectNoSender address object
replyToarrayNoReply-To addresses
senderobjectNoSender header (if different from From)
toarrayNoRecipient addresses
ccarrayNoCC recipient addresses
bccarrayNoBCC recipient addresses (rarely available)
messageIdstringNoMessage-ID header value
inReplyTostringNoIn-Reply-To header for threading
attachmentsarrayNoList of attachment objects
headersobjectNoSelected email headers. Only present when notifyHeaders is configured (see below)
textobjectNoText content object (see below)
previewstringNoShort body preview supplied by the provider. Gmail API and MS Graph accounts only
bouncesarrayNoBounces previously recorded against this message's Message-ID. IMAP accounts only (see below)
deliveryReportobjectNoParsed delivery status notification, set when the message is a "delivered" or "delayed" DSN (see below)
isAutoReplybooleanNotrue when the message looks like an automatic reply. The subject decides it when it begins with Auto reply, Automatic reply, Automatic response, Out of Office, Out of the Office, OOF: or OOO:, or with Auto: on a message that also has an In-Reply-To header. Otherwise an Auto-Submitted: auto-replied, a Precedence: auto-reply, or any X-Auto-Response-Suppress, X-Autoresponder, X-Autorespond or X-Autoreply header decides it
isBouncebooleanNotrue when the message was recognized as a bounce. A messageBounce event follows this one
isComplaintbooleanNotrue when the message was recognized as an ARF complaint. A messageComplaint event follows this one
relatedMessageIdstringNoMessage-ID of the original message a bounce or complaint refers to. Set together with isBounce or isComplaint
seemsLikeNewbooleanYestrue if EmailEngine has not seen this message on the account before, so it is probably new rather than moved or copied. The check is an approximate one, over emailId where the server supplies one and the Message-ID otherwise. Always false for messages in the Sent folder. Always true on Gmail API accounts, where moves are reported as label changes instead
categorystringNoGmail inbox tab. Gmail API accounts report primary, social, promotions, updates or forums, derived from the message labels. Gmail over IMAP resolves it with a server-side search when resolveGmailCategories is enabled, and can additionally report reservations and purchases
messageSpecialUsestringNoSpecial use of the folder the message belongs to, for example \Inbox, \Sent or \Junk. Derived from the folder or, on Gmail, from the labels
missingRetriesnumberNoIMAP only. How many extra fetch attempts were needed before the server returned the message
missingDelaynumberNoIMAP only. Milliseconds spent waiting between those attempts
calendarEventsarrayNoParsed calendar event data. Only when notifyCalendarEvents is enabled
summaryobjectNoAI-generated summary. Only when generateEmailSummary is enabled
riskAssessmentobjectNoAI-generated risk assessment. Only when generateEmailSummary is enabled
embeddingsobjectNoAI-generated embeddings. Only when openAiGenerateEmbeddings is enabled

Delivery Report Structure

When an incoming message is a delivery status notification (RFC 3464) reporting a successful delivery or a delay, EmailEngine parses its message/delivery-status part into data.deliveryReport. Failures are not reported here - those raise a separate messageBounce event instead. Only messages in the Inbox are checked.

Every field of the report is passed through with its name camelCased, so the exact set of keys depends on what the reporting server sent. A value that starts with an address type, such as rfc822; user@example.com, is split into a label and a value, and Arrival-Date is normalized to an ISO 8601 timestamp:

{
"deliveryReport": {
"reportingMta": { "label": "dns", "value": "mx.example.com" },
"arrivalDate": "2026-08-17T09:12:44.000Z",
"finalRecipient": { "label": "rfc822", "value": "user@example.com" },
"action": "delayed",
"status": "4.4.1",
"diagnosticCode": { "label": "smtp", "value": "451 4.4.1 Connection timed out" }
}
}

A notification that reports on several recipients is described one recipient at a time, so action, status, and diagnosticCode always belong together rather than being mixed across recipients. This shape was introduced in v2.78.0; earlier versions reported only a fixed subset of the fields.

Bounce List Structure

On IMAP accounts, when EmailEngine has previously processed a bounce that referred to this message's Message-ID, data.bounces lists what it recorded. Gmail API and MS Graph accounts do not carry this field. Each entry contains:

FieldTypeDescription
messagestringEmailEngine message ID of the bounce notification
recipientstringAddress that bounced
actionstringBounce action, typically failed
responseobjectmessage and status from the bounce, when known
datestringWhen the bounce was recorded (ISO 8601)

Address Object Structure

The from, sender, replyTo, to, cc, and bcc fields contain address objects:

FieldTypeDescription
namestringDisplay name (may be empty)
addressstringEmail address

Attachment Object Structure

Each attachment in the attachments array contains:

FieldTypeDescription
idstringAttachment ID for downloading via the API
contentTypestringMIME type (for example application/pdf)
encodedSizenumberSize in bytes as transferred (encoded)
filenamestringFilename (if provided)
contentIdstringContent-ID for inline attachments
embeddedbooleantrue if part of a multipart/related group
inlinebooleantrue if the part is marked for inline display
methodstringCalendar method (for text/calendar parts)
contentstringBase64-encoded content. Only when notifyAttachments is enabled, and only for attachments within notifyAttachmentSize

Text Object Structure

The text object describes the message body. Without notifyText it carries only id and encodedSize:

FieldTypeDescription
idstringText part ID for fetching the full content through the API
encodedSizeobjectObject with plain and html size values in bytes
plainstringPlain text body. Requires notifyText
htmlstringHTML body. Requires notifyText. Holds the web-safe version if notifyWebSafeHtml is enabled
webSafebooleantrue if the html value was processed for web display
hasMorebooleantrue if plain or html was truncated to notifyTextSize

Calendar Event Object Structure

If notifyCalendarEvents is enabled and the message contains text/calendar or application/ics attachments, each distinct event UID appears once in calendarEvents. Empty values are omitted:

FieldTypeDescription
eventIdstringCalendar event UID
attachmentstringID of the attachment the event was parsed from
methodstringiCalendar method (REQUEST, CANCEL, and so on)
summarystringEvent title
descriptionstringEvent description
timezonestringTime zone ID from the embedded VTIMEZONE
startDatestringEvent start (ISO 8601)
endDatestringEvent end (ISO 8601)
organizerstringEvent organizer
filenamestringAttachment filename. Defaults to invite.ics for REQUEST and CANCEL, otherwise event.ics
contentTypestringMIME type of the attachment
encodingstringAlways base64
contentstringBase64-encoded iCalendar data

AI Fields

summary, riskAssessment and embeddings are produced by the OpenAI integration and their contents depend on the configured prompt. See AI and ChatGPT Integration for the fields each of them carries.

Example Payload

An IMAP account with notifyText and notifyHeaders enabled:

{
"serviceUrl": "https://emailengine.example.com",
"account": "user123",
"date": "2025-10-17T06:42:25.056Z",
"path": "INBOX",
"specialUse": "\\Inbox",
"event": "messageNew",
"data": {
"id": "AAAADAAABy4",
"uid": 1838,
"path": "INBOX",
"date": "2025-10-17T06:42:07.000Z",
"flags": [],
"unseen": true,
"size": 549725,
"subject": "Quarterly Report Review",
"from": {
"name": "John Smith",
"address": "john.smith@example.com"
},
"replyTo": [
{
"name": "John Smith",
"address": "john.smith@example.com"
}
],
"sender": {
"name": "John Smith",
"address": "john.smith@example.com"
},
"to": [
{
"name": "Jane Doe",
"address": "jane.doe@company.com"
}
],
"attachments": [
{
"id": "AAAADAAABy4y",
"contentType": "application/pdf",
"encodedSize": 546048,
"filename": "Q3-Report.pdf",
"embedded": false,
"inline": false
}
],
"messageId": "<abc123@mail.example.com>",
"headers": {
"return-path": ["<john.smith@example.com>"],
"delivered-to": ["jane.doe@company.com"],
"mime-version": ["1.0"],
"from": ["John Smith <john.smith@example.com>"],
"date": ["Thu, 17 Oct 2025 09:42:07 +0300"],
"message-id": ["<abc123@mail.example.com>"],
"subject": ["Quarterly Report Review"],
"to": ["Jane Doe <jane.doe@company.com>"],
"content-type": ["multipart/mixed; boundary=\"----=_Part_123\""]
},
"text": {
"id": "AAAADAAABy6TkaMxLjGRozEuMpA",
"encodedSize": {
"plain": 1250,
"html": 2840
},
"plain": "Hi Jane,\n\nPlease find attached the Q3 report for your review.\n\nBest regards,\nJohn",
"html": "<div>Hi Jane,<br><br>Please find attached the Q3 report for your review.<br><br>Best regards,<br>John</div>"
},
"seemsLikeNew": true,
"messageSpecialUse": "\\Inbox"
}
}

Example: Gmail API Account

On a Gmail API account path and specialUse are \All, the folder is expressed through labels and messageSpecialUse, and category names the inbox tab:

{
"serviceUrl": "https://emailengine.example.com",
"account": "gmail-user",
"date": "2025-10-17T07:15:00.000Z",
"path": "\\All",
"specialUse": "\\All",
"event": "messageNew",
"data": {
"id": "18b5c7d8e9f01234",
"emailId": "18b5c7d8e9f01234",
"threadId": "18b5c7d8e9f01234",
"date": "2025-10-17T07:14:30.000Z",
"flags": [],
"labels": ["\\Inbox"],
"category": "primary",
"unseen": true,
"size": 8500,
"subject": "Team Meeting Reminder - Friday 3 PM",
"from": {
"name": "Sarah Johnson",
"address": "sarah@company.com"
},
"to": [
{
"name": "Team",
"address": "team@company.com"
}
],
"messageId": "<meeting-reminder@company.com>",
"text": {
"id": "AAAADAAABz0TkaMx",
"encodedSize": {
"plain": 450,
"html": 920
}
},
"preview": "Hi team, reminder that we have our weekly sync meeting this Friday at 3 PM.",
"seemsLikeNew": true,
"messageSpecialUse": "\\Inbox"
}
}

Configuration Options

Several settings shape the messageNew payload. All of them are set through the Settings API or under Configuration > Webhooks.

Text Content Options

SettingDescription
notifyTextInclude text.plain and text.html. Set to true at first start, so it is on unless switched off
notifyTextSizeMaximum size in bytes of each text value. Set to 2097152 (2 MB) at first start. Longer content is truncated and text.hasMore is set
notifyWebSafeHtmlReplace text.html with a sanitized web-safe rendering and set text.webSafe. Quoted thread history is folded into a collapsed block, and the plain text body is rendered when the message has no HTML part. cid: references to inline images are left as they are in the webhook payload. Requires notifyText

Header Options

SettingDescription
notifyHeadersHeader names to include in data.headers. Use ["*"] for all headers. Names are compared lowercase, so pass them in lowercase when setting this through the API (["list-id", "x-priority"]); the admin UI lowercases them for you

Regardless of this setting, EmailEngine always fetches the headers it needs for its own auto-reply and bounce detection. They appear in the payload only when notifyHeaders names them.

Attachment Options

SettingDescription
notifyAttachmentsInclude attachment content, base64-encoded, in attachments[].content
notifyAttachmentSizeMaximum size in bytes of an attachment to include. Larger attachments keep their metadata but no content

Calendar Options

SettingDescription
notifyCalendarEventsParse text/calendar and application/ics attachments into calendarEvents

AI Options

SettingDescription
generateEmailSummaryAdd summary and riskAssessment to Inbox messages
openAiGenerateEmbeddingsAdd embeddings to Inbox messages

Both need openAiAPIKey to be set as well, and both apply only to messages in the Inbox. See AI and ChatGPT Integration for what the generated fields contain.

Handling the Event

Basic Handler

async function handleMessageNew(event) {
const { account, data } = event;

console.log(`New email for ${account}:`);
console.log(` From: ${data.from?.name} <${data.from?.address}>`);
console.log(` Subject: ${data.subject}`);
console.log(` Message ID: ${data.id}`);

if (data.attachments?.length > 0) {
console.log(` Attachments: ${data.attachments.length}`);
}
}

Fetching Full Message Content

If text content is not included in the webhook, fetch it via the API:

async function getMessageContent(account, messageId) {
const response = await fetch(
`https://emailengine.example.com/v1/account/${account}/message/${messageId}`,
{
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
}
}
);
return response.json();
}

Downloading Attachments

async function downloadAttachment(account, attachmentId) {
const response = await fetch(
`https://emailengine.example.com/v1/account/${account}/attachment/${attachmentId}`,
{
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN'
}
}
);
return response.arrayBuffer();
}

Filtering New Messages

Using seemsLikeNew

The seemsLikeNew field helps distinguish genuinely new messages from moved or copied ones:

async function handleMessageNew(event) {
if (!event.data.seemsLikeNew) {
console.log('Skipping moved or copied message');
return;
}

await processNewEmail(event.data);
}

Filtering by Folder

messageSpecialUse works for every account type, including Gmail API accounts where path is always \All:

async function handleMessageNew(event) {
if (event.data.messageSpecialUse !== '\\Inbox') {
return;
}

await processInboxEmail(event.data);
}

Filtering Auto-Replies

async function handleMessageNew(event) {
if (event.data.isAutoReply) {
console.log('Skipping auto-reply');
return;
}

await processEmail(event.data);
}

Best Practices

  1. Respond quickly - Return a 2xx status before the delivery times out (30 seconds by default) to prevent retries
  2. Process asynchronously - Queue events for processing after acknowledging receipt
  3. Handle duplicates - Deduplicate on the X-EE-Wh-Event-Id request header, which is stable across retries of the same delivery
  4. Check seemsLikeNew - Filter out moved and copied messages when appropriate
  5. Use message IDs - Fetch additional data via the API using data.id when needed
  6. Switch notifyText off if you do not need bodies - Body content makes payloads large, and notifyTextSize bounds them
  7. Limit header exposure - Only request the headers you need via notifyHeaders

See Also