daimon.email

Core Concepts

Understanding the building blocks of daimon.email

Overview

daimon.email is built around a few core concepts that work together to provide email infrastructure for AI agents. Understanding these concepts will help you build more effective autonomous systems.

Info

All API responses use the format { result: T, next_steps: string[] } to guide agent behavior. Error responses use { error: string, next_steps: string[] }.

Accounts

An account is the top-level entity that represents a user or organization using daimon.email.

Key Properties

  • ID: Unique identifier (e.g., acc_abc123)
  • API Key: Account-level key for account-wide operations (e.g., dm_free_xyz789 or dm_live_xyz789)
  • Tier: Subscription level (free, developer, growth, enterprise)
  • Status: Account state (active, suspended, under_review)

Tier Differences

TierReceivingSendingCustom DomainsWebhooksPrice
Free✅ Unlimited$0
Developer✅ Unlimited✅ 10k/month$9/mo
Growth✅ Unlimited✅ 100k/month✅ 3 domains$49/mo
Enterprise✅ Unlimited✅ Custom✅ UnlimitedCustom

Account API Key

Account API keys are used for account-wide operations:

# Get account capabilities
curl https://api.daimon.email/v1/capabilities \
  -H "Authorization: Bearer dm_free_xyz789"

# Create a new inbox
curl -X POST https://api.daimon.email/v1/inboxes \
  -H "Authorization: Bearer dm_free_xyz789" \
  -d '{"username": "my-agent"}'

# Generate magic upgrade link
curl -X POST https://api.daimon.email/v1/upgrade-link \
  -H "Authorization: Bearer dm_free_xyz789"

Note

The account API key is returned when you create your first inbox via the unauthenticated POST /v1/inboxes endpoint. Store this key securely - it grants full control over the account.

Inboxes

An inbox is an email address created under an account. Each inbox can send and receive emails independently.

Key Properties

  • ID: Unique identifier (e.g., inb_abc123)
  • Address: Full email address (e.g., my-agent@daimon.email)
  • Username: The part before @ (e.g., my-agent)
  • API Key: Inbox-scoped key (e.g., dm_free_abc123...)
  • Status: Inbox state (active, paused, deleted)
  • View URL: Human-readable inbox viewer for debugging

Creating Inboxes

Free tier users can create inboxes without authentication:

# No auth required for free tier
curl -X POST https://api.daimon.email/v1/inboxes \
  -H "Content-Type: application/json" \
  -d '{
    "username": "my-agent",
    "client_id": "unique-123"
  }'

Paid tier users use their account API key:

# Paid tier requires account API key
curl -X POST https://api.daimon.email/v1/inboxes \
  -H "Authorization: Bearer dm_live_xyz789" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "support",
    "domain_id": "dom_abc123"
  }'

Inbox API Key

Each inbox has its own scoped API key for inbox-specific operations:

# List messages in this inbox
curl https://api.daimon.email/v1/inboxes/inb_abc123/messages \
  -H "Authorization: Bearer dm_free_abc123..."

# Send email from this inbox (paid tier only)
curl -X POST https://api.daimon.email/v1/inboxes/inb_abc123/send \
  -H "Authorization: Bearer dm_live_abc123..." \
  -d '{
    "to": "user@example.com",
    "subject": "Hello",
    "body": "Email body"
  }'

Info

Use inbox API keys when you want to isolate access to a single inbox. This is safer than sharing the account API key, which has broader permissions.

Messages

A message represents an inbound or outbound email associated with an inbox.

Key Properties

  • ID: Unique identifier (e.g., msg_abc123)
  • Inbox ID: Parent inbox
  • Direction: inbound or outbound
  • From: Sender email address
  • To: Recipient email address(es)
  • Subject: Email subject line
  • Body Text: Plain text version
  • Body HTML: HTML version (if available)
  • Reply Body: Extracted reply text without quoted history (inbound only)
  • Links: All URLs found in the email
  • CTA Links: Auto-detected call-to-action links (confirmation, verify, etc.)
  • Headers: Full email headers as JSON
  • Thread ID: Parent thread (if part of a conversation)
  • Message ID Header: Original Message-ID from email headers
  • Created At: When the message was received/sent

Reply Extraction

daimon.email automatically extracts the actual reply text from inbound emails, removing all quoted history:

{
  "result": {
    "id": "msg_abc123",
    "subject": "Re: Order Confirmation",
    "body_text": "Thanks for confirming!\n\n> On Mar 12, 2024 at 3:45 PM, you wrote:\n> Your order has been confirmed.",
    "reply_body": "Thanks for confirming!",
    "from": "customer@example.com"
  }
}

The reply_body field contains only the new content, making it easy for agents to parse responses.

Note

Reply extraction uses TalonJS under the hood, the same library used by Mailgun and other major email providers.

CTA Detection

daimon.email automatically detects common call-to-action links in emails:

{
  "result": {
    "id": "msg_abc123",
    "subject": "Verify your email address",
    "cta_links": [
      {
        "url": "https://service.com/verify?token=xyz",
        "text": "Verify Email",
        "confidence": "high",
        "type": "verification"
      }
    ],
    "links": [
      "https://service.com/verify?token=xyz",
      "https://service.com/help",
      "https://service.com/unsubscribe"
    ]
  }
}

CTA detection looks for:

  • Verification/confirmation links
  • Password reset links
  • Activation links
  • Approval/rejection links
  • Primary action buttons

This makes it trivial for agents to find and act on important links.

Listing Messages

# Get all messages (paginated)
curl https://api.daimon.email/v1/inboxes/inb_abc123/messages \
  -H "Authorization: Bearer dm_free_abc123..."

# Filter by thread
curl https://api.daimon.email/v1/inboxes/inb_abc123/messages?thread_id=thd_xyz789 \
  -H "Authorization: Bearer dm_free_abc123..."

# Only unread messages
curl https://api.daimon.email/v1/inboxes/inb_abc123/messages?read=false \
  -H "Authorization: Bearer dm_free_abc123..."

Threads

A thread represents a conversation - a collection of related messages grouped together.

Key Properties

  • ID: Unique identifier (e.g., thd_abc123)
  • Inbox ID: Parent inbox
  • Subject: Thread subject (from first message)
  • Participants: Email addresses involved in the conversation
  • Message Count: Number of messages in thread
  • Latest Message At: Timestamp of most recent message
  • Status: active or archived

How Threading Works

daimon.email automatically groups messages into threads using standard email headers:

  1. Inbound email arrives
  2. Extract headers: Message-ID, In-Reply-To, References
  3. Match against existing threads: Look for In-Reply-To in the messages.message_id_header field
  4. Create or attach:
    • If match found: attach to existing thread
    • If no match: create new thread
{
  "result": {
    "id": "thd_abc123",
    "inbox_id": "inb_abc123",
    "subject": "Order #12345 Confirmation",
    "participants": [
      "my-agent@daimon.email",
      "customer@example.com"
    ],
    "message_count": 5,
    "latest_message_at": "2024-03-12T15:45:00Z"
  }
}

Using Threads

# Get all threads
curl https://api.daimon.email/v1/inboxes/inb_abc123/threads \
  -H "Authorization: Bearer dm_free_abc123..."

# Get messages in a specific thread
curl https://api.daimon.email/v1/inboxes/inb_abc123/messages?thread_id=thd_abc123 \
  -H "Authorization: Bearer dm_free_abc123..."

Info

Threads make it easy for agents to maintain context across multi-turn email conversations, just like humans do with email clients.

Drafts

A draft is an unsent message that can be edited, scheduled, or sent later.

Key Properties

  • ID: Unique identifier (e.g., dft_abc123)
  • Inbox ID: Parent inbox
  • To: Recipient email address(es)
  • Subject: Email subject
  • Body: Email body (text or HTML)
  • Send At: Optional scheduled send time
  • Status: draft, scheduled, or sent

Creating and Scheduling Drafts

# Create a draft
curl -X POST https://api.daimon.email/v1/inboxes/inb_abc123/drafts \
  -H "Authorization: Bearer dm_live_abc123..." \
  -d '{
    "to": "customer@example.com",
    "subject": "Follow-up",
    "body": "Thanks for your inquiry..."
  }'

# Schedule for future delivery
curl -X POST https://api.daimon.email/v1/inboxes/inb_abc123/drafts \
  -H "Authorization: Bearer dm_live_abc123..." \
  -d '{
    "to": "customer@example.com",
    "subject": "Reminder",
    "body": "Don't forget...",
    "send_at": "2024-03-15T10:00:00Z"
  }'

# Send a draft immediately
curl -X POST https://api.daimon.email/v1/drafts/dft_abc123/send \
  -H "Authorization: Bearer dm_live_abc123..."

Note

Drafts require a paid tier. The free tier can only receive emails, not send them.

Webhooks

Webhooks provide real-time notifications when events occur in your inbox.

Key Properties

  • ID: Unique identifier (e.g., wh_abc123)
  • Endpoint URL: Your server's webhook endpoint
  • Events: Array of event types to subscribe to
  • Secret: HMAC secret for signature verification
  • Inbox ID: Optional inbox filter (if omitted, receives all account events)
  • Status: active, paused, or unhealthy

Available Events

  • message.received: New inbound email
  • message.sent: Outbound email successfully delivered
  • message.bounced: Outbound email bounced
  • thread.created: New thread started
  • inbox.created: New inbox created
  • inbox.deleted: Inbox deleted

Creating Webhooks

curl -X POST https://api.daimon.email/v1/webhooks \
  -H "Authorization: Bearer dm_free_abc123..." \
  -d '{
    "endpoint_url": "https://your-agent.com/webhook",
    "events": ["message.received", "message.sent"],
    "inbox_id": "inb_abc123"
  }'

Webhook Payload Format

All webhook payloads follow this structure:

{
  "event": "message.received",
  "timestamp": "2024-03-12T15:45:00Z",
  "inbox_id": "inb_abc123",
  "message": {
    "id": "msg_abc123",
    "from": "customer@example.com",
    "subject": "Question about order",
    "body_text": "When will my order ship?",
    "reply_body": "When will my order ship?",
    "cta_links": []
  }
}

Verifying Webhook Signatures

All webhooks are signed with HMAC-SHA256:

import crypto from 'crypto';

function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean {
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(payload);
  const computed = hmac.digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(computed)
  );
}

// In your webhook handler
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-daimon-signature'];
  const payload = JSON.stringify(req.body);

  if (!verifyWebhookSignature(payload, signature, webhookSecret)) {
    return res.status(401).send('Invalid signature');
  }

  // Process the event
  const { event, message } = req.body;

  res.status(200).send('OK');
});

Info

Always verify webhook signatures to prevent spoofed events from malicious actors.

Webhook Health

daimon.email monitors webhook health:

  • If your endpoint returns 5xx errors repeatedly, the webhook is marked unhealthy
  • If marked unhealthy, an error is returned: WEBHOOK_ENDPOINT_UNHEALTHY
  • Fix your endpoint and the webhook auto-recovers

Lists (Allowlist/Blocklist)

Lists control which senders can deliver email to your inbox.

Types

  • Allowlist: Only these senders can deliver (all others blocked)
  • Blocklist: These senders are blocked (all others allowed)

Key Properties

  • ID: Unique identifier
  • Inbox ID: Parent inbox (or account-wide if no inbox specified)
  • Type: allowlist or blocklist
  • Pattern: Email pattern (supports wildcards)
  • Status: active or disabled

Pattern Matching

# Block a specific sender
curl -X POST https://api.daimon.email/v1/inboxes/inb_abc123/lists \
  -H "Authorization: Bearer dm_free_abc123..." \
  -d '{
    "type": "blocklist",
    "pattern": "spam@evil.com"
  }'

# Block an entire domain
curl -X POST https://api.daimon.email/v1/inboxes/inb_abc123/lists \
  -H "Authorization: Bearer dm_free_abc123..." \
  -d '{
    "type": "blocklist",
    "pattern": "*@evil.com"
  }'

# Only allow specific domain
curl -X POST https://api.daimon.email/v1/inboxes/inb_abc123/lists \
  -H "Authorization: Bearer dm_free_abc123..." \
  -d '{
    "type": "allowlist",
    "pattern": "*@trusted-service.com"
  }'

Note

If any allowlist entries exist, only senders matching those patterns can deliver. Use allowlists carefully - they override all other filtering.

Authentication

daimon.email uses Bearer token authentication with two levels of access.

Account API Key

Format: dm_free_* or dm_live_* (32 hex characters)

Used for account-wide operations:

  • Creating inboxes
  • Managing webhooks across all inboxes
  • Generating upgrade links
  • Checking capabilities
curl https://api.daimon.email/v1/capabilities \
  -H "Authorization: Bearer dm_free_xyz789abc..."

Inbox API Key

Format: dm_free_* or dm_live_* (32 hex characters)

Used for inbox-scoped operations:

  • Listing messages
  • Sending emails
  • Managing drafts
  • Creating inbox-specific webhooks
curl https://api.daimon.email/v1/inboxes/inb_abc123/messages \
  -H "Authorization: Bearer dm_free_abc123def..."

Key Differences

OperationAccount KeyInbox Key
Create inbox
List all inboxes
List messages in inbox
Send email from inbox
Create webhook (all inboxes)
Create webhook (specific inbox)
Get capabilities

Info

Both keys are returned when you create an inbox. The account key is in result.account_api_key, and the inbox key is in result.api_key.

Response Format

All daimon.email API responses follow a consistent structure designed for agent clarity.

Success Responses

{
  "result": {
    // The actual data requested
  },
  "next_steps": [
    "Human-readable action the agent should take next",
    "Another suggested action"
  ]
}

Example:

{
  "result": {
    "id": "inb_abc123",
    "address": "my-agent@daimon.email",
    "api_key": "dm_free_xyz..."
  },
  "next_steps": [
    "Use the api_key to authenticate future requests",
    "Check /v1/inboxes/inb_abc123/messages to poll for incoming mail"
  ]
}

Error Responses

{
  "error": "ERROR_CODE",
  "message": "Human-readable error description",
  "next_steps": [
    "How to resolve this error"
  ]
}

Example:

{
  "error": "SEND_REQUIRES_PAID",
  "message": "Sending email requires a paid tier subscription",
  "next_steps": [
    "Call POST /v1/upgrade-link to generate an upgrade URL",
    "Present the URL to your operator with context"
  ]
}

Tier-Limit Errors

When an agent hits a tier limit, the error includes upgrade_context:

{
  "error": "SEND_REQUIRES_PAID",
  "message": "Sending email requires a paid tier subscription",
  "next_steps": [
    "Call POST /v1/upgrade-link to generate a magic upgrade link",
    "Present the link to your operator"
  ],
  "upgrade_context": {
    "operator_action_url": "https://daimon.email/upgrade?token=jwt...",
    "operator_action_label": "Add a payment method to enable sending",
    "agent_script": "Tell your operator: I need sending access. Here's a direct upgrade link: {url}"
  }
}

The agent can use agent_script to communicate with its operator:

if (error.code === 'SEND_REQUIRES_PAID') {
  console.log(error.upgradeContext.agentScript);
  // Outputs: "Tell your operator: I need sending access. Here's a direct upgrade link: https://..."
}

Upgrade Trigger Codes

Common tier-limit error codes:

  • SEND_REQUIRES_PAID: Sending requires paid tier
  • SEND_LIMIT_EXCEEDED: Monthly send quota exceeded
  • DOMAIN_REQUIRES_PAID: Custom domains require paid tier
  • INBOX_LIMIT_EXCEEDED: Too many inboxes for current tier
  • SMTP_REQUIRES_PAID: Direct SMTP access requires paid tier
  • WEBHOOK_ENDPOINT_UNHEALTHY: Webhook endpoint is failing
  • ACCOUNT_UNDER_REVIEW: Account flagged for review

Idempotency

All creation and send operations support idempotency via the client_id parameter.

Why Idempotency Matters

Agents may retry operations due to network failures, crashes, or restarts. Without idempotency, this causes duplicates:

  • Creating the same inbox twice
  • Sending the same email twice
  • Creating duplicate webhooks

How It Works

Pass a client_id (unique per operation) in creation requests:

# First call - creates inbox
curl -X POST https://api.daimon.email/v1/inboxes \
  -d '{
    "username": "my-agent",
    "client_id": "deployment-prod-1"
  }'

# Retry (network failure, agent restart, etc.) - returns existing inbox
curl -X POST https://api.daimon.email/v1/inboxes \
  -d '{
    "username": "my-agent",
    "client_id": "deployment-prod-1"
  }'

The second call returns the existing inbox with a 200 OK status, not a 409 Conflict.

Idempotency Key Requirements

  • Unique per operation within an account
  • Up to 255 characters
  • Use format: {deployment_id}-{operation}-{unique_suffix}

Example keys:

  • prod-1-inbox-primary
  • staging-2-webhook-message-received
  • deployment-abc-send-confirmation-user123

Supported Operations

EndpointIdempotency Key
POST /v1/inboxesclient_id
POST /v1/inboxes/{id}/sendclient_id
POST /v1/draftsclient_id
POST /v1/webhooksclient_id

Info

Use idempotency keys liberally in production agents. They prevent duplicates and make your system resilient to failures.

Next Steps

Now that you understand the core concepts, explore these guides: