Webhooks
Webhooks are the primary mechanism for receiving real-time notifications from EmailEngine about mailbox events, message changes, and delivery status. Instead of repeatedly polling for updates, EmailEngine pushes notifications to your application as events occur.
Why Use Webhooks?
Real-Time Updates
- Instant notifications when events occur
- No polling delays or missed events
- Process messages as they arrive
Efficient
- Eliminates the need for constant polling
- Reduces API calls and server load
- Lower latency for time-sensitive operations
Comprehensive Event Coverage
- Message lifecycle (new, updated, deleted)
- Delivery status (sent, failed, bounced)
- Account status (connected, disconnected, errors)
- User interactions (opens, clicks, unsubscribes)
Scalable
- Handle high message volumes effortlessly
- Process events asynchronously
- Built on BullMQ for reliability
Setting Up Webhooks
1. Configure Webhook URL
Set your webhook endpoint URL in EmailEngine:
Via Web UI:
- Navigate to Configuration → Webhooks
- Check Enable webhooks
- Enter your Webhook URL:
https://your-app.com/webhooks/emailengine - Select which events to receive
- Click Update Settings
Via API:
Use the settings API to configure webhooks:
curl -X POST "https://your-emailengine.com/v1/settings" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"webhooks": "https://your-app.com/webhooks/emailengine",
"webhooksEnabled": true,
"notifyHeaders": ["List-ID", "X-Priority"],
"notifyTextSize": 65536,
"notifyWebSafeHtml": true,
"notifyCalendarEvents": true
}'
For more advanced scenarios, you can configure multiple webhook routes to send different events to different endpoints based on account, event type, or custom filtering logic. Webhook routes also support pre-processing functions to filter or transform payloads before delivery.
See Webhooks API for route management and Pre-Processing Functions for custom filters.
2. Create Webhook Handler
Your webhook endpoint must:
- Accept HTTP POST requests
- Return a 2xx status code quickly (within 5 seconds)
- Process events asynchronously
- Node.js
- Python
- PHP
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhooks/emailengine', async (req, res) => {
const event = req.body;
// Acknowledge receipt immediately
res.status(200).json({ success: true });
// Process asynchronously
processEvent(event).catch(err => {
console.error('Webhook processing error:', err);
});
});
async function processEvent(event) {
console.log(`Received ${event.event} event for account ${event.account}`);
switch (event.event) {
case 'messageNew':
await handleNewMessage(event);
break;
case 'messageSent':
await handleMessageSent(event);
break;
case 'messageFailed':
await handleMessageFailed(event);
break;
// Handle other events...
}
}
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});
from flask import Flask, request, jsonify
import asyncio
app = Flask(__name__)
@app.route('/webhooks/emailengine', methods=['POST'])
def webhook_handler():
event = request.get_json()
# Acknowledge immediately
asyncio.create_task(process_event(event))
return jsonify({'success': True}), 200
async def process_event(event):
event_type = event.get('event')
account = event.get('account')
print(f"Processing {event_type} for {account}")
if event_type == 'messageNew':
await handle_new_message(event)
elif event_type == 'messageSent':
await handle_message_sent(event)
# Handle other events...
if __name__ == '__main__':
app.run(port=3000)
<?php
// webhook.php
// Read the webhook payload
$payload = file_get_contents('php://input');
$event = json_decode($payload, true);
// Respond immediately
http_response_code(200);
header('Content-Type: application/json');
echo json_encode(['success' => true]);
// Close connection and process asynchronously
fastcgi_finish_request();
// Process the event
processEvent($event);
function processEvent($event) {
$eventType = $event['event'] ?? '';
$account = $event['account'] ?? '';
error_log("Processing $eventType for $account");
switch ($eventType) {
case 'messageNew':
handleNewMessage($event);
break;
case 'messageSent':
handleMessageSent($event);
break;
// Handle other events...
}
}
?>
Webhook Events
EmailEngine sends different types of events organized into categories:
Message Events
Events related to emails in monitored mailbox folders. These webhooks notify you when messages arrive, are modified, or are removed from the mailbox.
messageNew
Triggered when a new message is detected in a mailbox folder. This is one of the most commonly used webhook events, enabling real-time processing of incoming emails.
See full messageNew reference →
messageDeleted
Triggered when a previously tracked email has been removed from a mailbox folder. Helps keep external systems synchronized with mailbox state changes.
See full messageDeleted reference →
messageUpdated
Triggered when EmailEngine detects that the flags or labels on a message have changed, enabling real-time synchronization of message state changes with external systems.
See full messageUpdated reference →
messageMissing
Triggered when EmailEngine detects that a message it expected to find on the mail server is not available. This event indicates a potential synchronization issue and helps handle edge cases in message processing.
See full messageMissing reference →
Delivery Events
Events related to outgoing email delivery. These webhooks track the lifecycle of messages sent through EmailEngine, from successful delivery to failures, bounces, and spam complaints.
messageSent
Triggered when a queued message is successfully accepted by the SMTP server or email API (Gmail API, Microsoft Graph API). This event confirms that the message has been handed off to the mail transfer agent for delivery.
See full messageSent reference →
messageDeliveryError
Triggered when EmailEngine fails to deliver an email to the SMTP server. This event indicates a temporary failure that may be retried automatically based on the configured retry policy.
See full messageDeliveryError reference
messageFailed
Triggered when EmailEngine permanently fails to deliver a queued email after all retry attempts have been exhausted. This is a terminal event indicating that the message will not be delivered.
See full messageFailed reference
messageBounce
Triggered when a bounce notification (Delivery Status Notification) is received in a monitored mailbox. EmailEngine parses the bounce message to extract delivery failure information including the failed recipient, SMTP error codes, and details about the original message.
See full messageBounce reference
messageComplaint
Triggered when a feedback loop (FBL) complaint is detected. EmailEngine parses ARF (Abuse Reporting Format) complaint messages to extract information about the complainant and the original message that was reported as spam.
See full messageComplaint reference
Mailbox Events
Events related to mailbox folder changes. These webhooks notify you when folders are created, deleted, or reset on the mail server.
mailboxNew
Triggered when a new folder is discovered on the mail server during synchronization.
mailboxDeleted
Triggered when a previously tracked folder is no longer found on the mail server.
See full mailboxDeleted reference
mailboxReset
Triggered when a folder's UIDVALIDITY changes, indicating a mailbox reset. This is a rare but significant event that invalidates all previously tracked message UIDs in the folder.
See full mailboxReset reference
Account Events
Events related to email account lifecycle and connection status. These webhooks track account registration, initialization, authentication, and connection health.
accountAdded
Triggered when a new email account is registered with EmailEngine. This is the first webhook in the account lifecycle, fired before authentication is attempted.
See full accountAdded reference
accountInitialized
Triggered when an email account completes its initial mailbox synchronization. This marks the point at which the account is fully operational and ready for use.
See full accountInitialized reference
accountDeleted
Triggered when an email account is removed from EmailEngine. This is the final webhook event in the account lifecycle.
See full accountDeleted reference
authenticationSuccess
Triggered when EmailEngine successfully authenticates an email account for the first time or after recovering from an error state.
See full authenticationSuccess reference
authenticationError
Triggered when EmailEngine fails to authenticate an email account due to invalid credentials, expired OAuth2 tokens, or API authentication errors.
See full authenticationError reference
connectError
Triggered when EmailEngine fails to establish a connection to an email server due to network issues, server unavailability, or TLS/SSL problems. This is distinct from authentication errors which occur after connection is established.
See full connectError reference
Tracking Events
Events related to email engagement tracking. These webhooks notify you when recipients open emails, click links, or manage their subscription preferences.
trackOpen
Triggered when a recipient opens an email that has open tracking enabled. The tracking works by embedding a 1x1 pixel image in the email's HTML body that is loaded when the email is viewed.
trackClick
Triggered when a recipient clicks a tracked link in an email that has click tracking enabled. EmailEngine rewrites links in outgoing HTML emails to redirect through a tracking endpoint, capturing click events before redirecting recipients to the original destination.
listUnsubscribe
Triggered when a recipient uses the one-click unsubscribe mechanism to remove themselves from a mailing list. EmailEngine adds the recipient to the suppression list and fires this webhook.
See full listUnsubscribe reference
listSubscribe
Triggered when a recipient re-subscribes to a mailing list after previously unsubscribing. This event enables you to restore subscriptions and keep your mailing lists synchronized.
See full listSubscribe reference
Testing Webhooks
Using webhook.site
The easiest way to test webhooks is using a temporary webhook inspector:
- Visit https://webhook.site/
- Copy your unique webhook URL
- Set it as the webhook URL in EmailEngine
- Trigger an event (send a test email, add an account, etc.)
- View the webhook payload in real-time
Tailing Webhooks to a Log File
For ongoing webhook monitoring, you can log all webhooks to a file and tail them:
Step 1: Create log file
sudo touch /var/log/emailengine-webhooks.log
sudo chown www-data /var/log/emailengine-webhooks.log # Adjust user as needed
Step 2: Create PHP webhook logger
<?php
// webhook-logger.php
$logFile = '/var/log/emailengine-webhooks.log';
// Read webhook payload
$payload = file_get_contents('php://input');
$headers = getallheaders();
// Create log entry
$logEntry = [
'timestamp' => date('c'),
'method' => $_SERVER['REQUEST_METHOD'],
'headers' => $headers,
'request' => json_decode($payload, true)
];
// Append to log file
file_put_contents($logFile, json_encode($logEntry) . "\n", FILE_APPEND);
// Respond
http_response_code(200);
header('Content-Type: application/json');
echo json_encode(['success' => true]);
?>
Step 3: Tail the log with jq
# Install jq if needed
sudo apt update && sudo apt install -y jq
# Tail and pretty-print webhooks
tail -f /var/log/emailengine-webhooks.log | jq
This gives you a real-time, pretty-printed view of all incoming webhooks.
Send Test Webhook
EmailEngine allows you to send a test webhook from the UI:
- Go to Configuration → Webhooks
- Click Send test webhook
- Check your webhook endpoint receives the test
Debugging Webhooks
If webhooks aren't working as expected, follow this diagnostic process:
1. Verify External Connectivity
Test with webhook.site:
- Set webhook URL to https://webhook.site/your-unique-id
- Trigger an event in EmailEngine
- Check if webhook.site receives the request
If no request appears:
- Check firewall rules
- Verify DNS resolution
- Ensure EmailEngine can make outbound HTTPS requests
- Check for typos in webhook URL
2. Monitor Webhook Queue
EmailEngine uses BullMQ to manage webhook delivery. To inspect webhook jobs:
- Go to Tools → Bull Board
- Select Webhooks queue
- Check these tabs:
- Active: Currently processing
- Delayed: Failed but will retry
- Failed: Exceeded retry limit
- Completed: Successfully delivered (if retention enabled)
Enable Job Retention:
To keep failed jobs for inspection:
- Go to Configuration → Service
- Set Job History Limit to 100
- Save changes
Now failed webhooks remain visible in Bull Board with full error details.
3. Inspect Failed Jobs
Click on a failed job in Bull Board to see:
- Complete error stack trace
- Request headers sent
- Payload data
- Response from your server
- Retry attempts
Common errors:
- Connection timeout: Your server is unreachable
- SSL/TLS error: Certificate issues
- 4xx status: Your server rejected the webhook
- 5xx status: Your server had an internal error
- JSON parse error: Your server returned invalid JSON
4. Verify Event Generation
Test if events are being generated at all:
Add a new account:
- Should trigger
accountAddedandaccountInitializedevents
Send test email to an account:
- Should trigger
messageNewwithin 10-60 seconds - If not, verify message arrived (check via webmail)
- Check message is visible via API:
curl "https://your-emailengine.com/v1/account/ACCOUNT_ID/messages?path=INBOX" \
-H "Authorization: Bearer TOKEN"
If message is missing:
- Wrong account credentials
- OAuth token lacks required scopes
- Message filtered to different folder
5. Special Requirements for API Backends
Gmail API + Cloud Pub/Sub
If using Gmail API (not IMAP):
- Go to Configuration → OAuth2
- Select your Gmail OAuth app
- Scroll to Cloud Pub/Sub configuration
- Verify all show Created (in green):
- Topic
- Subscription
- Gmail bindings
If not created:
- Google Cloud service account missing IAM roles
- Pub/Sub API not enabled
- Invalid credentials
Microsoft Graph API
If using MS Graph (not IMAP):
- Go to Email Accounts
- Select the account
- Scroll to Change subscription
- Verify status is Created and expiration is in future
If not created:
- EmailEngine not reachable from Microsoft servers
- TLS certificate invalid
- Service URL not configured correctly
- OAuth app missing required scopes
Microsoft Graph requires these endpoints to be publicly accessible:
https://YOUR-EMAILENGINE-HOST/oauth/msg/lifecycle
https://YOUR-EMAILENGINE-HOST/oauth/msg/notification
Webhook Security
Verify Webhook Authenticity
EmailEngine can sign webhooks using HMAC:
1. Set a service secret using the settings API:
curl -X POST "https://your-emailengine.com/v1/settings" \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{"serviceSecret": "your-secret-key-here"}'
2. Verify signature in your handler:
The signature is computed on the raw request body using HMAC-SHA256 and encoded as base64url.
const crypto = require('crypto');
function verifyWebhookSignature(rawBody, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('base64url');
return signature === expectedSignature;
}
// Important: Use raw body parser to get the exact bytes for signature verification
app.post('/webhooks/emailengine', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-ee-wh-signature'];
const secret = process.env.SERVICE_SECRET;
if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Parse body after verification
const event = JSON.parse(req.body.toString());
// Process webhook...
res.json({ success: true });
});
Use HTTPS
Always use HTTPS for webhook URLs to prevent:
- Man-in-the-middle attacks
- Credential exposure
- Data tampering