AuthonAuthon Blog
debugging7 min read

Why Your AI Agent's Emails Land in Spam (And How to Fix It)

AI agents sending email often land in spam. Here's how to fix SPF, DKIM, and DMARC issues and build reliable programmatic email delivery.

AW
Alan West
Authon Team
Why Your AI Agent's Emails Land in Spam (And How to Fix It)

You finally got your AI agent doing something useful — scheduling meetings, sending reports, responding to support tickets. Then you check the logs and realize half its emails are bouncing or rotting in spam folders. Sound familiar?

I spent the better part of last month debugging this exact problem for an automated workflow system, and the root causes are surprisingly consistent. Let me walk you through why programmatic email is harder than it looks and how to actually fix it.

The Core Problem: Email Wasn't Built for Machines

SMTP was designed in 1982 for humans sending messages to other humans. Four decades later, we're asking AI agents to participate in email conversations, and the entire anti-spam ecosystem is designed to make that difficult.

When your agent sends an email, it has to pass through multiple layers of validation that were specifically built to stop automated senders:

  • SPF (Sender Policy Framework) — verifies the sending IP is authorized
  • DKIM (DomainKeys Identified Mail) — cryptographically signs messages
  • DMARC (Domain-based Message Authentication) — ties SPF and DKIM together with a policy

Miss any one of these, and your agent's carefully crafted email goes straight to junk. Or worse, silently disappears.

Step 1: Set Up DNS Records Properly

This is where most people trip up. You spin up a mail-sending service, fire off a test email, it works in dev, and you move on. Then production traffic starts and deliverability craters.

Here's what your DNS needs at minimum:

dns
; SPF record — authorize your sending IPs
; Replace with your actual mail server IPs
yourdomain.com.  IN TXT  "v=spf1 ip4:203.0.113.10 ip4:198.51.100.0/24 -all"

; DKIM selector record — public key for signature verification
mail._domainkey.yourdomain.com.  IN TXT  "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBA..."

; DMARC policy — what to do when checks fail
_dmarc.yourdomain.com.  IN TXT  "v=DMARC1; p=reject; rua=mailto:dmarc-reports@yourdomain.com; pct=100"

The -all in the SPF record is important. I see people use ~all (soft fail) thinking it's safer. It is — for getting your emails flagged. Use -all and explicitly list every IP that sends on your behalf.

For the DMARC policy, start with p=none while you're monitoring, then graduate to p=reject once you're confident your alignment is correct. The rua tag sends you aggregate reports so you can see what's passing and failing.

Step 2: Generate and Rotate DKIM Keys

DKIM is the one that trips up automated systems the most. You need to generate a key pair, configure your mail server to sign outgoing messages with the private key, and publish the public key in DNS.

python
import dkim
import smtplib
from email.mime.text import MIMEText

def send_signed_email(sender, recipient, subject, body):
    msg = MIMEText(body)
    msg["From"] = sender
    msg["To"] = recipient
    msg["Subject"] = subject

    # Convert to bytes for DKIM signing
    msg_bytes = msg.as_bytes()

    # Sign with your private key
    # selector and domain must match your DNS records
    with open("/etc/dkim/private.key", "rb") as f:
        private_key = f.read()

    signature = dkim.sign(
        msg_bytes,
        b"mail",                    # DKIM selector
        b"yourdomain.com",          # signing domain
        private_key,
        include_headers=[           # headers to include in signature
            b"From", b"To", b"Subject", b"Date", b"Message-ID"
        ]
    )

    # Prepend the signature header to the message
    signed_msg = signature + msg_bytes

    with smtplib.SMTP("localhost", 587) as server:
        server.starttls()
        server.send_message(msg)  # send the signed version

    return True

One gotcha I ran into: rotate your DKIM keys regularly. If a key is compromised or if you've been using the same 1024-bit key since 2019, upgrade to 2048-bit and set up rotation every 6-12 months. You can run two selectors simultaneously during rotation to avoid downtime.

Step 3: Handle Inbound Email for Agent Responses

Sending is only half the battle. If your agent needs to receive replies (and most useful agents do), you need to handle inbound email processing.

The most reliable approach I've found is using MX records pointed at a server that parses incoming messages and routes them to your agent:

python
import email
from email import policy
import asyncio
from aiosmtpd.controller import Controller

class AgentMailHandler:
    async def handle_RCPT(self, server, session, envelope, address, rcpt_options):
        # Only accept mail for your domain
        if not address.endswith("@yourdomain.com"):
            return "550 not relaying to that domain"
        envelope.rcpt_tos.append(address)
        return "250 OK"

    async def handle_DATA(self, server, session, envelope):
        msg = email.message_from_bytes(
            envelope.content,
            policy=policy.default
        )

        # Extract the plain text body
        body = msg.get_body(preferencelist=("plain",))
        text_content = body.get_content() if body else ""

        # Route to your agent based on the recipient address
        # e.g., support-agent@yourdomain.com -> support handler
        recipient = envelope.rcpt_tos[0]
        agent_id = recipient.split("@")[0]

        await route_to_agent(agent_id, {
            "from": envelope.mail_from,
            "subject": msg["subject"],
            "body": text_content,
            "message_id": msg["message-id"],
            "in_reply_to": msg.get("in-reply-to"),  # for threading
        })

        return "250 Message accepted"

# Start the SMTP server
controller = Controller(AgentMailHandler(), hostname="0.0.0.0", port=25)
controller.start()

The In-Reply-To and References headers are critical for maintaining conversation threading. Without them, every reply from your agent starts a new thread in the recipient's inbox, which is confusing and unprofessional.

Step 4: Implement Rate Limiting and Warm-Up

This is the part nobody tells you about until you're already in trouble. If your agent starts sending 500 emails a day from a fresh domain, every major email provider will throttle or block you.

IP warm-up schedule (roughly):
  • Week 1: 50 emails/day
  • Week 2: 100 emails/day
  • Week 3: 250 emails/day
  • Week 4: 500 emails/day
  • Increase by ~50% each week after that

Build rate limiting directly into your agent's email-sending logic:

python
import time
from collections import defaultdict

class EmailRateLimiter:
    def __init__(self, max_per_hour=100, max_per_day=500):
        self.max_per_hour = max_per_hour
        self.max_per_day = max_per_day
        self.hourly_counts = defaultdict(int)
        self.daily_counts = defaultdict(int)

    def can_send(self, domain: str) -> bool:
        current_hour = int(time.time() // 3600)
        current_day = int(time.time() // 86400)

        if self.hourly_counts[current_hour] >= self.max_per_hour:
            return False
        if self.daily_counts[current_day] >= self.max_per_day:
            return False
        return True

    def record_send(self):
        current_hour = int(time.time() // 3600)
        current_day = int(time.time() // 86400)
        self.hourly_counts[current_hour] += 1
        self.daily_counts[current_day] += 1

Also: always include a List-Unsubscribe header for any automated email. It's not just polite — major email providers will penalize your deliverability if bulk-ish email doesn't include one. Google and Yahoo both started enforcing this aggressively in 2024.

Step 5: Monitor Bounce Rates and Feedback Loops

Set up monitoring from day one. You need to track:

  • Hard bounces — invalid addresses, remove them immediately
  • Soft bounces — temporary failures, retry with exponential backoff
  • Spam complaints — if this exceeds 0.1%, you have a serious problem
  • DMARC reports — parse the aggregate reports to catch alignment issues early

Sign up for feedback loops (FBLs) with major providers. When someone marks your agent's email as spam, the FBL notifies you so you can suppress that address.

Prevention: Things I Wish I'd Done From the Start

  • Use a subdomain for agent-generated email (agent.yourdomain.com). This isolates your agent's reputation from your main domain. If deliverability tanks, it doesn't drag down your human team's email.
  • Set up BIMI (Brand Indicators for Message Identification) if you have a verified trademark. It puts your logo next to your emails in supporting clients, which builds trust.
  • Log everything. Every SMTP response code, every bounce, every delivery confirmation. When deliverability drops (and it will), you need the data to diagnose why.
  • Test before you ship. Tools like mail-tester.com will score your setup and flag issues. Run your agent's emails through it during development, not after you've already annoyed 1,000 recipients.

The Bigger Picture

As AI agents become more autonomous, email is becoming a critical interface for them to interact with the world. But the email ecosystem has decades of anti-abuse infrastructure that assumes senders are either humans or spammers. The agents that succeed at email will be the ones that play by the rules — proper authentication, gradual warm-up, and genuine respect for recipients' inboxes.

Get the fundamentals right, and your agent's emails will actually get read. Skip them, and you're just building an elaborate system for generating spam complaints.

Why Your AI Agent's Emails Land in Spam (And How to Fix It) | Authon Blog