Skip to main content

messageBounce

The messageBounce webhook event is triggered when EmailEngine detects a bounce response email in a monitored mailbox. Bounce emails are generated by mail servers when they cannot deliver a message to the intended recipient. This event helps you track email deliverability by identifying messages that failed to reach their recipients.

When This Event is Triggered

The messageBounce event fires when:

  • A bounce response email is received in a monitored mailbox
  • EmailEngine successfully parses the bounce and extracts delivery failure information
  • The bounce contains identifiable information about the failed recipient and original message

EmailEngine analyzes incoming messages for bounce patterns from various email providers including:

  • Standard RFC 3464 delivery status notifications
  • Amazon WorkMail bounce notifications
  • Gmail bounce messages
  • Microsoft Exchange bounce reports
  • Postfix mailer-daemon responses
  • Zoho Mail bounce notifications
  • Generic SMTP server bounces

Common Use Cases

  • Deliverability monitoring - Track bounce rates across your email campaigns
  • List hygiene - Automatically remove or flag invalid email addresses
  • Reputation management - Identify and address delivery issues before they impact sender reputation
  • Customer notification - Alert users when their messages fail to deliver
  • Analytics - Build dashboards showing delivery success rates
  • Retry logic - Implement custom retry strategies for soft bounces

Payload Schema

Top-Level Fields

FieldTypeRequiredDescription
serviceUrlstringNoThe configured EmailEngine service URL
accountstringYesAccount ID that received the bounce message
datestringYesISO 8601 timestamp when the webhook was generated
eventstringYesEvent type, always "messageBounce" for this event
eventIdstringYesUnique identifier for this webhook delivery
dataobjectYesBounce data object (see below)

Bounce Data Fields (data object)

FieldTypeRequiredDescription
idstringNoEmailEngine message ID of the original bounced message (if found)
bounceMessagestringYesEmailEngine message ID of the bounce notification email itself
recipientstringYesEmail address that bounced
actionstringYesBounce action type (typically "failed" for hard bounces, "delayed" for soft bounces)
responseobjectNoSMTP response details from the receiving server
mtastringNoMail Transfer Agent (server) that reported the bounce
queueIdstringNoQueue ID from the sending MTA (e.g., Postfix queue ID)
messageIdstringNoMessage-ID header of the original message that bounced
messageHeadersobjectNoHeaders from the original bounced message

Response Object Structure

The response object contains details about the SMTP error and ML-powered classification:

FieldTypeDescription
sourcestringSource of the diagnostic code (typically "smtp")
messagestringThe full SMTP error message from the receiving server
statusstringEnhanced status code (e.g., "5.1.1" for invalid mailbox, "5.2.2" for mailbox full)
categorystringML-classified bounce category (see Bounce Categories below)
recommendedActionstringSuggested action: "remove", "retry", "review", "fix_configuration", "retry_different_ip", or "remove_content"
blocklistobjectPresent when bounce indicates a blocklist issue. Contains name (string) and type (string: "ip" or "domain")
retryAfternumberSuggested retry delay in seconds (only when timing information is found in the error message)

Message Headers Object Structure

The messageHeaders object contains headers from the original bounced message (when available):

FieldTypeDescription
return-patharrayReturn-Path header values
receivedarrayReceived header chain
dkim-signaturearrayDKIM signature headers
content-typearrayContent-Type header
fromarrayFrom header
toarrayTo header
subjectarraySubject header
message-idarrayMessage-ID header
datearrayDate header
mime-versionarrayMIME-Version header

Example Payload

{
"serviceUrl": "https://emailengine.example.com",
"account": "user123",
"date": "2025-10-17T06:46:29.436Z",
"event": "messageBounce",
"data": {
"id": "AAAAAQAAAeE",
"bounceMessage": "AAAAAQAABy8",
"recipient": "missing@example.com",
"action": "failed",
"response": {
"source": "smtp",
"message": "550 5.1.1 The email account that you tried to reach does not exist",
"status": "5.1.1",
"category": "user_unknown",
"recommendedAction": "remove"
},
"mta": "mx.example.com",
"queueId": "9441D8220E",
"messageId": "<305eabf4-9538-2747-acec-dc32cb651a0e@example.com>",
"messageHeaders": {
"return-path": ["<sender@example.com>"],
"from": ["Sender Name <sender@example.com>"],
"to": ["Recipient <missing@example.com>"],
"subject": ["Your original message subject"],
"message-id": ["<305eabf4-9538-2747-acec-dc32cb651a0e@example.com>"],
"date": ["Mon, 17 Oct 2025 09:46:25 +0300"],
"mime-version": ["1.0"]
}
}
}

Understanding Bounce Types

Hard Bounces (Permanent Failures)

Hard bounces indicate permanent delivery failures. The action field will be "failed" and status codes typically start with "5":

Status CodeMeaning
5.1.1Invalid mailbox / User unknown
5.1.2Invalid domain
5.2.1Mailbox disabled
5.2.2Mailbox full (can also be temporary)
5.4.1No answer from host
5.7.1Delivery not authorized

Soft Bounces (Temporary Failures)

Soft bounces are temporary and may succeed on retry. The action field may be "delayed":

Status CodeMeaning
4.2.2Mailbox full (temporary)
4.4.1Connection timeout
4.4.2Connection dropped
4.7.1Temporary authentication failure

Bounce Categories

EmailEngine uses machine learning to classify bounce messages into specific categories. The category field in the response object provides a detailed classification beyond basic hard/soft bounce distinction.

CategoryDescriptionRecommended Action
user_unknownRecipient email address does not existRemove from list
invalid_addressBad email syntax or domain not foundRemove from list
mailbox_disabledAccount suspended or disabledRemove from list
mailbox_fullOver quota, storage exceededRetry later
greylistingTemporary rejection, retry laterRetry after delay
rate_limitedToo many connections or messagesRetry after delay
server_errorTimeout or connection failedRetry later
ip_blacklistedSender IP on a blocklist (RBL)Retry from different IP
domain_blacklistedSender domain on a blocklistFix DNS/configuration
auth_failureDMARC, SPF, or DKIM failureFix authentication config
relay_deniedRelaying not permittedFix configuration
spam_blockedMessage detected as spamReview content
policy_blockedLocal policy rejectionReview and contact admin
virus_detectedInfected content detectedRemove malicious content
geo_blockedGeographic or country-based rejectionRetry from different IP
unknownUnclassified bounce typeReview manually

The recommendedAction field suggests how to handle each bounce:

ActionDescription
removePermanently remove email from mailing lists
retryRetry delivery after a delay
reviewManual review required
fix_configurationFix sender DNS, authentication, or relay settings
retry_different_ipRetry from a different sending IP address
remove_contentRemove problematic content and resend

Blocklist Detection

When a bounce indicates a blocklist issue, the blocklist object provides details:

{
"response": {
"message": "550 blocked using zen.spamhaus.org",
"category": "ip_blacklisted",
"recommendedAction": "retry_different_ip",
"blocklist": {
"name": "Spamhaus ZEN",
"type": "ip"
}
}
}

Retry Timing

When the bounce message contains timing information (e.g., "try again in 5 minutes"), the retryAfter field provides the suggested delay in seconds:

{
"response": {
"message": "450 Greylisted, try again in 5 minutes",
"category": "greylisting",
"recommendedAction": "retry",
"retryAfter": 300
}
}

Handling the Event

Basic Handler

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

console.log(`Bounce detected for ${account}:`);
console.log(` Recipient: ${data.recipient}`);
console.log(` Action: ${data.action}`);
console.log(` Original Message ID: ${data.messageId}`);

if (data.response) {
console.log(` Status: ${data.response.status}`);
console.log(` Message: ${data.response.message}`);
console.log(` Category: ${data.response.category}`);
console.log(` Recommended Action: ${data.response.recommendedAction}`);

// Check for blocklist issues
if (data.response.blocklist) {
console.log(` Blocklist: ${data.response.blocklist.name} (${data.response.blocklist.type})`);
}
}

// Process based on recommended action
const action = data.response?.recommendedAction ||
(data.action === 'failed' ? 'remove' : 'retry');

switch (action) {
case 'remove':
await removeFromMailingList(data.recipient);
break;
case 'retry':
const delay = data.response?.retryAfter || 3600;
await scheduleRetry(data.recipient, delay);
break;
case 'fix_configuration':
await alertAdminConfigIssue(data);
break;
case 'retry_different_ip':
await retryWithDifferentIP(data);
break;
default:
await flagForReview(data);
}
}

Updating Email Lists

async function handleHardBounce(bounceData) {
const { recipient, response } = bounceData;

// Mark email as invalid in your database
await db.contacts.update(
{ email: recipient },
{
$set: {
emailValid: false,
bounceReason: response?.message,
bounceCode: response?.status,
bounceCategory: response?.category,
recommendedAction: response?.recommendedAction,
bouncedAt: new Date()
}
}
);

// Optionally notify the sender
if (bounceData.id) {
await notifySender(bounceData);
}
}

Tracking Bounce Metrics

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

// Use ML-classified category (preferred) or fall back to status code
const category = data.response?.category || 'unknown';
const recommendedAction = data.response?.recommendedAction || 'review';
const isHardBounce = ['remove', 'remove_content'].includes(recommendedAction);

await metrics.increment('email.bounces', {
account,
type: isHardBounce ? 'hard' : 'soft',
category,
recommendedAction,
mta: data.mta || 'unknown',
blocklisted: data.response?.blocklist ? 'yes' : 'no'
});

// Track blocklist issues separately
if (data.response?.blocklist) {
await metrics.increment('email.blocklist_bounces', {
account,
blocklistName: data.response.blocklist.name,
blocklistType: data.response.blocklist.type
});
}
}

Key Differences from messageFailed

The messageBounce and messageFailed events serve different purposes:

AspectmessageBouncemessageFailed
TriggerBounce email received in mailboxEmailEngine fails to deliver after all retries
SourceRemote mail server's bounce notificationEmailEngine's delivery system
TimingCan be minutes to days after sendingImmediate after final retry failure
DetectionRequires parsing bounce email formatDirect SMTP error response

Use messageBounce when you need to:

  • Track bounces from emails sent through other systems
  • Get detailed bounce information from the receiving server's perspective
  • Monitor mailboxes that receive bounce notifications

Use messageFailed when you need to:

  • Track delivery failures for emails sent through EmailEngine's queue
  • Get immediate feedback on delivery attempts
  • Handle failures before bounce notifications arrive

Best Practices

  1. Track both events - Use both messageBounce and messageFailed for complete deliverability monitoring
  2. Deduplicate bounces - The same delivery failure may trigger both events; use messageId to correlate
  3. Handle missing fields - Not all bounces include complete information; validate fields before use
  4. Distinguish bounce types - Hard bounces require different handling than soft bounces
  5. Update promptly - Remove hard-bounced addresses from mailing lists immediately
  6. Log for analysis - Store bounce data for deliverability trend analysis
  7. Monitor the MTA field - Track which receiving servers generate the most bounces

See Also