All posts
Architecture·By Deepak··8 min read

AI Email Agent: Give Your Agent Its Own Address & Inbox

TL;DR

An AI email agent needs three things: a dedicated address with proper DNS records, an inbound pipeline that parses and routes messages to your agent logic, and authenticated SMTP or API-based sending. Get all three right and your agent can participate in real email threads autonomously.

TL;DR: An AI email agent needs three things: a dedicated address with proper DNS records, an inbound pipeline that parses and routes messages to your agent logic, and authenticated SMTP or API-based sending. Get all three right and your agent can participate in real email threads autonomously.

AI Email Agent: Give Your Agent Its Own Address & Inbox

An AI email agent that can only send outbound messages is half an agent. To handle replies, route requests, and act on inbound messages, your agent needs a complete email identity: a dedicated address, a working inbox with parsed delivery, and authenticated send capability. This post covers the exact mechanics of provisioning all three.

Why a dedicated address matters

Give your agent its own address — not a shared support@ or a forwarded alias — and you get clean separation of authentication scope, reputation, and deliverability. A shared address means shared SPF/DKIM alignment, which makes it impossible to tune your agent's sender reputation independently without affecting everyone else on the domain.

The typical pattern is a subdomain: agent.yourcompany.com or ops.yourcompany.com. This isolates DNS records and IP reputation. If your agent sends high-frequency automated mail and gets a complaint rate spike, it won't drag down your transactional domain. Google's Postmaster Tools documentation explicitly recommends separate sending identities for senders who mix marketing, transactional, and automated traffic.

Address naming conventions that work well:

  • agent@ops.yourcompany.com — generic, readable
  • assistant@yourcompany.com — fine for customer-facing agents
  • noreply+{contextid}@agent.yourcompany.com — encodes routing context in the local part
  • {uuid}@inbound.yourcompany.com — unique per thread or task

That last pattern is useful when your agent manages many parallel conversations and you want per-thread routing via the address itself rather than a header.

DNS setup: SPF, DKIM, and DMARC

Authentication isn't optional. Without it, your agent's mail will be rejected or junked by any major provider. You need the following per sending domain.

SPF authorizes which IPs or services can send on behalf of your domain. A minimal SPF record for an agent using a third-party API:

TXT agent.yourcompany.com "v=spf1 include:_spf.mailsai.net ~all"

Use ~all (softfail) during testing. Switch to -all (hard fail) once you're confident no other source is sending from that domain.

DKIM signs outbound messages cryptographically. Your sending provider generates a keypair; you publish the public key as a TXT record:

TXT mails1._domainkey.agent.yourcompany.com  "v=DKIM1; k=rsa; p=MIIBIjANBgk..."

The selector (mails1) is arbitrary but must match what the provider uses when signing. Most APIs handle key rotation if you've set up the CNAME delegation method — they update the key, your CNAME always resolves to the current one.

DMARC ties SPF and DKIM together and tells receivers what to do with messages that fail alignment. Minimum viable record:

TXT _dmarc.agent.yourcompany.com  "v=DMARC1; p=none; rua=mailto:dmarc-reports@yourcompany.com"

Start at p=none to collect aggregate reports without rejecting mail. After a week of clean data, move to p=quarantine, then p=reject. This ramp matters — jumping straight to p=reject before you've verified all legitimate senders will silently drop your agent's mail.

MX records point inbound mail to your processing infrastructure:

MX agent.yourcompany.com  10 inbound.mails.ai

Without MX records, the address can send but not receive. The MX record is what routes replies back into your agent's pipeline.

Provisioning the inbox: inbound parsing

An inbox for an AI agent isn't a mailbox you log into — it's a webhook endpoint that receives parsed message data. The flow is: sender → MX → inbound processor → HTTP POST to your agent.

Here's what a production inbound pipeline looks like:

sequenceDiagram
    participant Sender
    participant MXServer as MX Server
    participant Parser as Inbound Parser
    participant Webhook as Your Agent Webhook
    participant LLM as Agent Logic

    Sender->>MXServer: SMTP delivery
    MXServer->>Parser: Raw MIME message
    Parser->>Parser: Extract headers body attachments
    Parser->>Webhook: POST parsed JSON payload
    Webhook->>LLM: Pass structured context
    LLM->>Webhook: Action or reply

The inbound parser handles MIME complexity so your agent code receives clean JSON. A minimal payload looks like:

{
  "message_id": "<CABc123xyz@mail.gmail.com>",
  "from": { "address": "user@example.com", "name": "Alice" },
  "to": [{ "address": "agent@ops.yourcompany.com" }],
  "subject": "Re: Your invoice #4821",
  "in_reply_to": "<agent-msg-98f3@ops.yourcompany.com>",
  "references": "<agent-thread-001@ops.yourcompany.com> <agent-msg-98f3@ops.yourcompany.com>",
  "text": "The amounts look wrong — can you check line 3?",
  "html": "<p>The amounts look wrong...</p>",
  "attachments": []
}

The in_reply_to and references headers are critical for threading. They tell your agent which conversation a message belongs to. If you're correlating against a database of open tasks, index on message_id and cross-reference in_reply_to to reconstruct the thread. See the inbound email parsing documentation for how structured delivery works end-to-end.

Webhook security: always validate that inbound POST requests come from your provider, not arbitrary senders. Options:

  • HMAC-SHA256 signature on the payload (best — provider signs, you verify)
  • IP allowlisting (fragile — provider IPs can change)
  • Shared secret in a header (acceptable if HTTPS is enforced)

Authenticated sending: SMTP vs API

Your agent needs to send mail that looks like it came from its address, passes DKIM signing, and maintains thread continuity via RFC 5322 headers. Two paths.

SMTP with AUTH

SMTP with AUTH LOGIN or AUTH PLAIN over TLS (port 587 with STARTTLS, or port 465 with implicit TLS). Python example using smtplib:

import smtplib
from email.message import EmailMessage

msg = EmailMessage()
msg['From'] = 'agent@ops.yourcompany.com'
msg['To'] = 'user@example.com'
msg['Subject'] = 'Re: Your invoice #4821'
msg['In-Reply-To'] = '<CABc123xyz@mail.gmail.com>'
msg['References'] = '<agent-thread-001@ops.yourcompany.com> <CABc123xyz@mail.gmail.com>'
msg['Message-ID'] = '<agent-reply-001@ops.yourcompany.com>'
msg.set_content('The line 3 amount is correct — here is the breakdown...')

with smtplib.SMTP_SSL('smtp.mails.ai', 465) as server:
    server.login('agent@ops.yourcompany.com', 'YOUR_SMTP_PASSWORD')
    server.send_message(msg)

The In-Reply-To and References headers are mandatory if you want this reply to thread correctly in Gmail, Outlook, and Apple Mail. Strip them and your agent's reply surfaces as a new conversation.

HTTP API

For agents running in serverless or container environments, an HTTP API is more practical than managing SMTP connections. TypeScript example with @mailsai/sdk:

import { MailsAI } from '@mailsai/sdk';

const client = new MailsAI({ apiKey: process.env.MAILS_API_KEY });

await client.send({
  from: 'agent@ops.yourcompany.com',
  to: 'user@example.com',
  subject: 'Re: Your invoice #4821',
  text: 'The line 3 amount is correct — here is the breakdown...',
  headers: {
    'In-Reply-To': '<CABc123xyz@mail.gmail.com>',
    'References': '<agent-thread-001@ops.yourcompany.com> <CABc123xyz@mail.gmail.com>',
    'Message-ID': '<agent-reply-001@ops.yourcompany.com>'
  }
});

For Python, use plain HTTPS until the Python SDK ships:

import requests

requests.post(
    'https://api.mails.ai/v1/send',
    headers={'Authorization': 'Bearer YOUR_API_KEY'},
    json={
        'from': 'agent@ops.yourcompany.com',
        'to': 'user@example.com',
        'subject': 'Re: Your invoice #4821',
        'text': 'The line 3 amount is correct — here is the breakdown...',
        'headers': {
            'In-Reply-To': '<CABc123xyz@mail.gmail.com>',
            'References': '<agent-thread-001@ops.yourcompany.com> <CABc123xyz@mail.gmail.com>',
            'Message-ID': '<agent-reply-001@ops.yourcompany.com>'
        }
    }
)

Thread state management

An agent with an inbox needs to correlate inbound messages to open tasks. The naive approach is storing thread state keyed by In-Reply-To. That breaks when a sender starts a new thread about the same topic. A more reliable schema:

CREATE TABLE agent_threads (
  thread_id       UUID PRIMARY KEY,
  root_message_id TEXT NOT NULL,      -- first Message-ID in the chain
  task_id         UUID,               -- reference to your task/ticket
  participant     TEXT NOT NULL,      -- sender email
  last_seen_at    TIMESTAMPTZ,
  status          TEXT DEFAULT 'open'
);

CREATE TABLE agent_messages (
  id              UUID PRIMARY KEY,
  thread_id       UUID REFERENCES agent_threads,
  message_id      TEXT UNIQUE NOT NULL,
  direction       TEXT CHECK (direction IN ('inbound', 'outbound')),
  received_at     TIMESTAMPTZ DEFAULT NOW(),
  raw_payload     JSONB
);

On inbound: extract message_id, look up in_reply_to against agent_messages.message_id to find the thread, then pass the thread context to your agent logic. On send: insert the outbound message so future inbound replies can trace back to it.

Classification and routing

Not every message your agent receives needs the same response path. A catch-all inbox for agent@ops.yourcompany.com might receive invoice questions, meeting requests, OOO autoreplies, and spam. Classifying before your LLM call is cheap and saves real compute.

A simple pre-classification layer:

def route_inbound(msg: dict) -> str:
    subject = msg.get('subject', '').lower()
    from_addr = msg.get('from', {}).get('address', '')
    
    # Hard-coded fast path for known patterns
    if 'auto-submitted' in msg.get('headers', {}):
        return 'autoreply_sink'
    if subject.startswith('out of office'):
        return 'autoreply_sink'
    if from_addr.endswith('@mailer-daemon.example.com'):
        return 'bounce_handler'
    
    # Fall through to LLM classification
    return 'llm_classify'

For higher-volume deployments, a dedicated email classification step with structured output (intent, urgency, requires_reply) before the full agent call reduces latency and cost significantly.

Deliverability for automated senders

Agents send differently than humans. High frequency, low engagement, repetitive content structure — all of these trigger spam filters tuned for bulk abuse. Concrete mitigations:

  • Warm up a dedicated IP before sending at scale. Start at 50–100/day, double weekly, monitor bounce and complaint rates. A dedicated IP isolates your agent's reputation from other tenants.
  • Set a realistic List-Unsubscribe header even on agent-initiated outbound. Some filters penalize its absence on automated mail.
  • Vary your sending rate rather than bursting. 10 messages/minute is treated differently than 600/hour at the same total volume.
  • Monitor bounce rates per recipient domain. If @yahoo.com bounces spike, pause and investigate before Yahoo's feedback loop flags you.

Frequently Asked Questions

Do I need a separate domain or can I use a subdomain?

A subdomain is sufficient and usually preferable. Using agent.yourcompany.com rather than yourcompany.com isolates your agent's SPF/DKIM/DMARC records and IP reputation. If your agent's deliverability degrades, it won't affect your main domain's inbox placement.

How do I handle bounce-back and OOO replies to my agent?

Filter on the Auto-Submitted header (value: auto-replied or auto-generated) and the presence of a List-Id or Precedence: bulk header. Route these to a sink rather than your LLM. For hard bounces, check the smtp-status-code in the delivery failure body — codes 5xx are permanent and you should suppress that address from future sends.

What Message-ID format should my agent use?

RFC 5322 specifies <local-part@domain>. A reliable pattern: <agent-{uuid}@ops.yourcompany.com>. The UUID ensures global uniqueness; the domain must be a sending domain you control. Never reuse Message-IDs — duplicate IDs break threading in some clients and can trigger spam filters.

Can my agent handle multiple simultaneous conversations?

Yes — thread identity lives in headers, not connections. Your agent can maintain thousands of parallel threads as long as your inbound webhook correctly maps in_reply_to to thread state in your database. The limiting factor is usually your agent's context window and your database read latency, not email infrastructure.

How do I prevent my agent from replying to its own messages in a loop?

Check the From address on inbound messages against your agent's own addresses before routing to agent logic. Also check for X-Loop headers — add one with a unique value to every outbound message and reject any inbound that carries it. This is the standard loop prevention mechanism used by mailing list software.

What's the minimum infrastructure to get an agent email identity working?

You need: (1) a domain you control with DNS access, (2) MX records pointing to an inbound processor, (3) SPF/DKIM records for your sending provider, (4) a webhook endpoint your agent logic runs on, (5) API or SMTP credentials to send. The Mails.ai platform handles the MX routing, inbound parsing, DKIM signing, and send API as a single integrated service — useful if you want to avoid stitching together multiple vendors.

Start sending with Mails.ai

Live now

Ship agent email in ~6 lines.

Free tier, no card. Mint a key and drop the SDK into your agent.

Get your API key
Live now

Built for agents.
Self-serve in minutes.

The API is live and self-serve. Drop ~6 lines into your agent and ship.

npmpnpmbunnpx
$ npm install @mailsai/sdk
Live on npm today · @mailsai/sdk + @mailsai/mcp-server