daimon.email
Webhooks

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

EventDescriptionAvailability
message.receivedNew email received in an inboxAll tiers
message.bouncedOutbound email bounced (permanent failure)Paid tiers
message.complaintRecipient marked email as spamPaid tiers
account.upgradedAccount upgraded to paid tierAll tiers
webhook.unhealthyWebhook endpoint failing health checksAll 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:

FieldTypeDescription
eventstringEvent type (e.g., message.received)
event_idstringUnique event identifier for deduplication
timestampstringISO 8601 timestamp when the event occurred
account_idstringAccount that triggered the event
inbox_idstringInbox 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

FieldTypeDescription
message.idstringUnique message identifier
message.fromstringSender email address
message.tostring[]Recipient email addresses
message.subjectstringEmail subject line
message.bodystringFull email body (includes quoted text)
message.reply_bodystringExtracted reply text (excludes quoted history) using TalonJS
message.html_bodystring | nullHTML version of the email (if available)
message.plain_bodystringPlain text version of the email
message.attachmentsobject[]Array of attachment objects
message.linksstring[]All URLs found in the email body
message.cta_linksstring[]Detected CTA links (e.g., "Verify Email", "Reset Password")
message.spam_scorenumberSpam score (0-10, lower is better)
message.received_atstringISO 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

FieldTypeDescription
bounce.typestringpermanent (hard bounce) or temporary (soft bounce)
bounce.recipientstringEmail address that bounced
bounce.reasonstringHuman-readable bounce reason
bounce.smtp_codenumberSMTP status code (e.g., 550, 554)
bounce.diagnostic_codestringFull 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

FieldTypeDescription
complaint.recipientstringEmail address that marked message as spam
complaint.feedback_typestringType of complaint (abuse, fraud, virus, other)
complaint.user_agentstringEmail 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

FieldTypeDescription
upgrade.from_tierstringPrevious tier (free, developer, growth)
upgrade.to_tierstringNew tier (developer, growth, enterprise)
upgrade.new_limitsobjectUpdated 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

FieldTypeDescription
webhook.failure_countnumberNumber of consecutive failures
webhook.last_errorstringMost recent error message
webhook.last_success_atstringTimestamp 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.


Next Steps