Webhook Event Reference
All webhook event types and their payloads
Webhook Event Reference
This page documents all webhook event types available in daimon.email, including complete payload examples and field descriptions.
Event Types
| Event | Description | Availability |
|---|---|---|
message.received | New email received in an inbox | All tiers |
message.bounced | Outbound email bounced (permanent failure) | Paid tiers |
message.complaint | Recipient marked email as spam | Paid tiers |
account.upgraded | Account upgraded to paid tier | All tiers |
webhook.unhealthy | Webhook endpoint failing health checks | All tiers |
Note
All webhook payloads include a timestamp field (ISO 8601) and event_id field (unique identifier for deduplication).
Common Fields
Every webhook event includes these fields:
| Field | Type | Description |
|---|---|---|
event | string | Event type (e.g., message.received) |
event_id | string | Unique event identifier for deduplication |
timestamp | string | ISO 8601 timestamp when the event occurred |
account_id | string | Account that triggered the event |
inbox_id | string | Inbox that triggered the event (if applicable) |
message.received
Fired when a new email is received in an inbox.
Payload
Response
{
"event": "message.received",
"event_id": "evt_abc123xyz789",
"timestamp": "2026-03-16T10:30:45.123Z",
"account_id": "acc_xyz789",
"inbox_id": "inbox_abc123",
"message": {
"id": "msg_def456",
"inbox_id": "inbox_abc123",
"thread_id": "thread_ghi789",
"from": "user@example.com",
"to": ["agent@daimon.email"],
"cc": [],
"bcc": [],
"subject": "Request for analysis",
"body": "Hi Agent,\n\nPlease analyze the attached data.\n\nThanks,\nUser",
"html_body": "<p>Hi Agent,</p><p>Please analyze the attached data.</p><p>Thanks,<br>User</p>",
"reply_body": "Please analyze the attached data.",
"plain_body": "Hi Agent,\n\nPlease analyze the attached data.\n\nThanks,\nUser",
"attachments": [
{
"id": "att_jkl012",
"filename": "data.csv",
"content_type": "text/csv",
"size": 1024,
"url": "https://api.daimon.email/v1/messages/msg_def456/attachments/att_jkl012"
}
],
"links": [
"https://example.com/data-source"
],
"cta_links": [],
"labels": [],
"read": false,
"headers": {
"message-id": "<abc123@mail.example.com>",
"in-reply-to": null,
"references": null,
"date": "2026-03-16T10:30:42Z",
"return-path": "user@example.com"
},
"spam_score": 0.1,
"received_at": "2026-03-16T10:30:45Z",
"created_at": "2026-03-16T10:30:45Z"
}
}Field Descriptions
| Field | Type | Description |
|---|---|---|
message.id | string | Unique message identifier |
message.from | string | Sender email address |
message.to | string[] | Recipient email addresses |
message.subject | string | Email subject line |
message.body | string | Full email body (includes quoted text) |
message.reply_body | string | Extracted reply text (excludes quoted history) using TalonJS |
message.html_body | string | null | HTML version of the email (if available) |
message.plain_body | string | Plain text version of the email |
message.attachments | object[] | Array of attachment objects |
message.links | string[] | All URLs found in the email body |
message.cta_links | string[] | Detected CTA links (e.g., "Verify Email", "Reset Password") |
message.spam_score | number | Spam score (0-10, lower is better) |
message.received_at | string | ISO 8601 timestamp when email was received |
Use Case
Process new emails immediately:
app.post('/webhook', (req, res) => {
const event = req.body;
if (event.event === 'message.received') {
const message = event.message;
console.log(`New email from ${message.from}: ${message.subject}`);
// Extract reply text (without quoted history)
const replyText = message.reply_body;
// Process CTA links (e.g., verification links)
if (message.cta_links.length > 0) {
console.log('Found verification link:', message.cta_links[0]);
}
// Download attachments
for (const attachment of message.attachments) {
downloadAttachment(attachment.url);
}
}
res.status(200).send('OK');
});message.bounced
Fired when an outbound email bounces (permanent failure).
Payload
Response
{
"event": "message.bounced",
"event_id": "evt_bounce123",
"timestamp": "2026-03-16T10:35:12.456Z",
"account_id": "acc_xyz789",
"inbox_id": "inbox_abc123",
"message_id": "msg_original789",
"bounce": {
"type": "permanent",
"recipient": "invalid@nonexistent-domain.com",
"reason": "550 5.1.1 Recipient address rejected: User unknown in virtual mailbox table",
"smtp_code": 550,
"bounced_at": "2026-03-16T10:35:10Z",
"diagnostic_code": "smtp; 550 5.1.1 Recipient address rejected: User unknown in virtual mailbox table"
},
"original_message": {
"id": "msg_original789",
"to": "invalid@nonexistent-domain.com",
"subject": "Hello",
"sent_at": "2026-03-16T10:30:00Z"
}
}Field Descriptions
| Field | Type | Description |
|---|---|---|
bounce.type | string | permanent (hard bounce) or temporary (soft bounce) |
bounce.recipient | string | Email address that bounced |
bounce.reason | string | Human-readable bounce reason |
bounce.smtp_code | number | SMTP status code (e.g., 550, 554) |
bounce.diagnostic_code | string | Full SMTP diagnostic message |
Bounce Types
- Permanent bounce (
permanent): Mailbox doesn't exist or domain is invalid. Do not retry. - Temporary bounce (
temporary): Temporary failure (e.g., mailbox full, server down). Retry may succeed.
Use Case
Track bounce rates and clean email lists:
app.post('/webhook', (req, res) => {
const event = req.body;
if (event.event === 'message.bounced') {
const bounce = event.bounce;
console.log(`Bounce from ${bounce.recipient}: ${bounce.reason}`);
if (bounce.type === 'permanent') {
// Remove from contact list
removeFromContactList(bounce.recipient);
// Alert operator
sendAlert(`Permanent bounce: ${bounce.recipient}`);
} else if (bounce.type === 'temporary') {
// Retry later
scheduleRetry(event.message_id, bounce.recipient);
}
}
res.status(200).send('OK');
});message.complaint
Fired when a recipient marks your email as spam.
Payload
Response
{
"event": "message.complaint",
"event_id": "evt_complaint456",
"timestamp": "2026-03-16T11:00:00.000Z",
"account_id": "acc_xyz789",
"inbox_id": "inbox_abc123",
"message_id": "msg_original789",
"complaint": {
"recipient": "user@example.com",
"feedback_type": "abuse",
"complaint_date": "2026-03-16T10:58:00Z",
"user_agent": "Yahoo! Mail"
},
"original_message": {
"id": "msg_original789",
"to": "user@example.com",
"subject": "Newsletter",
"sent_at": "2026-03-16T09:00:00Z"
}
}Field Descriptions
| Field | Type | Description |
|---|---|---|
complaint.recipient | string | Email address that marked message as spam |
complaint.feedback_type | string | Type of complaint (abuse, fraud, virus, other) |
complaint.user_agent | string | Email client that reported the complaint |
Use Case
Monitor spam complaints and pause sending to affected recipients:
app.post('/webhook', (req, res) => {
const event = req.body;
if (event.event === 'message.complaint') {
const complaint = event.complaint;
console.log(`Complaint from ${complaint.recipient}: ${complaint.feedback_type}`);
// Immediately unsubscribe recipient
unsubscribe(complaint.recipient);
// Alert operator if complaint rate is high
const complaintRate = calculateComplaintRate();
if (complaintRate > 0.001) { // 0.1% threshold
sendAlert(`High complaint rate: ${complaintRate * 100}%`);
}
}
res.status(200).send('OK');
});Warning
Spam complaints damage your domain reputation. Keep complaint rate below 0.1% (1 complaint per 1000 emails). Above this threshold, email providers may block your domain.
account.upgraded
Fired when an account upgrades from free to paid tier.
Payload
Response
{
"event": "account.upgraded",
"event_id": "evt_upgrade123",
"timestamp": "2026-03-16T11:15:00.000Z",
"account_id": "acc_xyz789",
"upgrade": {
"from_tier": "free",
"to_tier": "developer",
"upgraded_at": "2026-03-16T11:15:00Z",
"stripe_subscription_id": "sub_abc123",
"new_limits": {
"max_inboxes": -1,
"max_sends_per_day": 1000,
"webhooks_enabled": true,
"custom_domains_enabled": true,
"max_custom_domains": 5
}
}
}Field Descriptions
| Field | Type | Description |
|---|---|---|
upgrade.from_tier | string | Previous tier (free, developer, growth) |
upgrade.to_tier | string | New tier (developer, growth, enterprise) |
upgrade.new_limits | object | Updated account limits |
Use Case
Enable new features when an account upgrades:
app.post('/webhook', (req, res) => {
const event = req.body;
if (event.event === 'account.upgraded') {
const upgrade = event.upgrade;
console.log(`Account upgraded: ${upgrade.from_tier} → ${upgrade.to_tier}`);
// Enable sending capability for all agents
if (upgrade.new_limits.max_sends_per_day > 0) {
enableSendingForAllAgents(event.account_id);
}
// Enable webhooks
if (upgrade.new_limits.webhooks_enabled) {
createWebhooks(event.account_id);
}
// Notify operator
sendEmail({
to: 'operator@yourcompany.com',
subject: 'Account upgraded',
body: `Your daimon.email account has been upgraded to ${upgrade.to_tier}.`
});
}
res.status(200).send('OK');
});webhook.unhealthy
Fired when a webhook endpoint fails consecutive health checks.
Payload
Response
{
"event": "webhook.unhealthy",
"event_id": "evt_unhealthy789",
"timestamp": "2026-03-16T11:30:00.000Z",
"account_id": "acc_xyz789",
"webhook_id": "wh_abc123",
"webhook": {
"id": "wh_abc123",
"url": "https://your-platform.com/webhook/daimon",
"events": ["message.received"],
"status": "unhealthy",
"failure_count": 5,
"last_error": "Connection timeout after 5000ms",
"last_success_at": "2026-03-16T10:00:00Z",
"last_failure_at": "2026-03-16T11:29:55Z"
}
}Field Descriptions
| Field | Type | Description |
|---|---|---|
webhook.failure_count | number | Number of consecutive failures |
webhook.last_error | string | Most recent error message |
webhook.last_success_at | string | Timestamp of last successful delivery |
Use Case
Monitor webhook health and automatically fix issues:
app.post('/webhook', (req, res) => {
const event = req.body;
if (event.event === 'webhook.unhealthy') {
const webhook = event.webhook;
console.log(`Webhook unhealthy: ${webhook.url}`);
console.log(`Failure count: ${webhook.failure_count}`);
console.log(`Last error: ${webhook.last_error}`);
// Alert operator
sendAlert({
title: 'Webhook unhealthy',
body: `${webhook.url} has failed ${webhook.failure_count} times. Last error: ${webhook.last_error}`
});
// Attempt to fix (e.g., restart webhook server)
restartWebhookServer();
// Re-enable webhook after fix
setTimeout(async () => {
await client.webhooks.enable(webhook.id);
console.log('Webhook re-enabled');
}, 60000); // Wait 1 minute before re-enabling
}
res.status(200).send('OK');
});Event Filtering
Filter by Inbox
When creating a webhook, specify inbox_id to receive events only for that inbox:
curl -X POST https://api.daimon.email/v1/webhooks \
-H "Authorization: Bearer dm_live_account123..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-platform.com/webhook/daimon",
"events": ["message.received"],
"inbox_id": "inbox_abc123"
}'Without inbox_id, the webhook receives events for all inboxes in your account.
Filter by Event Type
Subscribe only to specific event types:
curl -X POST https://api.daimon.email/v1/webhooks \
-H "Authorization: Bearer dm_live_account123..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-platform.com/webhook/daimon",
"events": ["message.received", "message.bounced"]
}'This webhook will not receive message.complaint, account.upgraded, or webhook.unhealthy events.
Best Practices
Verify Signatures
Always verify HMAC signatures. See Verifying HMAC.
Use event_id for Deduplication
Store event_id in your database to prevent duplicate processing if webhooks are retried.
Return 200 Quickly
Acknowledge receipt immediately. Process events asynchronously in a background job.
Monitor webhook.unhealthy Events
Set up alerts for webhook.unhealthy events to catch endpoint failures quickly.
Testing Events
Send test events to your webhook endpoint:
curl -X POST https://api.daimon.email/v1/webhooks/wh_abc123/test \
-H "Authorization: Bearer dm_live_account123..."This sends a sample message.received event to verify your endpoint is working.