{"slug": "i-ran-50-emails-through-ai-agents-12-smtp-bounces-hit", "title": "I Ran 50 Emails Through AI Agents. 12 SMTP Bounces Hit.", "summary": "A developer tested an AI agent's ability to send outreach emails and found that 12 of 50 addresses bounced, including a known test address. The agent relied solely on SMTP verification, which accepted the address, but a composite trust check flagged it as risky due to three data breaches. The developer recommends using an email validation API that combines SMTP, breach, and disposable checks to gate agent actions.", "body_md": "On August 12, I handed an AI agent a CSV of fifty email addresses and an SMTP tool. Thirty-eight minutes later, twelve of them had bounced.\n\nI wasn't trying to break anything. I was testing whether a local agent could handle a simple outreach task end-to-end: read a list, draft a short note, send it. The model I used was Muse Glimmer, Meta's 30-billion-parameter open-weights agent model that dropped on August 10, 2026. It's small enough to run on a single consumer GPU, which means agents like this are about to be everywhere—on your laptop, inside a Docker Sandbox, or wired into a multiplayer harness like qm. More tools, more autonomy, more chances for a bad send.\n\nThe agent saw `send_email`\n\nas just another function call. It didn't ask whether the addresses were real, reachable, or trustworthy. It pinged SMTP, got a handful of `250 OK`\n\ngreetings, and fired. One of those addresses was `test@gmail.com`\n\n. SMTP said yes. The gatekeeper I built afterward said no.\n\nHere's the gatekeeper I wish I'd put in front of the mailer first:\n\n``` python\nimport requests\nimport json\nimport sys\n\nRAPIDAPI_KEY = \"YOUR_RAPIDAPI_KEY\"\nURL = \"https://email-validator112.p.rapidapi.com/validate\"\n\ndef gatekeep(email):\n    r = requests.get(\n        URL,\n        headers={\n            \"X-RapidAPI-Key\": RAPIDAPI_KEY,\n            \"X-RapidAPI-Host\": \"email-validator112.p.rapidapi.com\"\n        },\n        params={\"email\": email},\n        timeout=10\n    )\n    r.raise_for_status()\n    data = r.json()\n\n    verdict = \"SEND\" if data.get(\"is_trusted_identity\") else \"BLOCK\"\n    print(json.dumps(data, indent=2))\n    print(f\"\\nverdict: {verdict}\")\n    return verdict\n\nif __name__ == \"__main__\":\n    gatekeep(sys.argv[1] if len(sys.argv) > 1 else \"test@gmail.com\")\n```\n\nWhen I ran that against `test@gmail.com`\n\n, SMTP came back verified. The composite trust score came back false. The agent would have sent. The gatekeeper didn't.\n\nThe response I got back is worth reading in full:\n\n```\n{\n  \"email\": \"test@gmail.com\",\n  \"is_valid\": true,\n  \"is_disposable\": false,\n  \"is_free_email\": true,\n  \"provider\": \"Google\",\n  \"mx_record\": \"gmail.com\",\n  \"smtp_verified\": true,\n  \"is_catch_all\": false,\n  \"breached\": true,\n  \"breach_count\": 3,\n  \"first_breach\": \"2014-01-01\",\n  \"last_breach\": \"2023-05-15\",\n  \"is_trusted_identity\": false,\n  \"syntax_suggestion\": null\n}\n```\n\nThat's a lot of signal for one HTTP call. The first thing that jumps out is the contradiction: `smtp_verified`\n\nis `true`\n\n, but `is_trusted_identity`\n\nis `false`\n\n. If your agent is only checking SMTP, it treats this address as deliverable and moves on. The gatekeeper treats it as risky, because the same address has appeared in three separate breach dumps spanning from January 1, 2014 to May 15, 2023.\n\nThree breaches. Nine years between the first and last. A free Gmail account on Google's MX. Not disposable, not catch-all, syntactically valid. The kind of address an agent would happily email all day.\n\nThe `is_trusted_identity`\n\ncomposite is the detail that makes this API interesting as a gatekeeper. It doesn't just verify reachability; it folds in breach status and disposable detection. You can't easily reconstruct that from public docs alone because the weights matter: is one breach enough to block? Two? Three? The API made a call here and said no. That's a policy decision wrapped in a field, and it's exactly the kind of thing I want between an agent and my mailer.\n\n`provider`\n\nand `mx_record`\n\ngive you routing identity. For `test@gmail.com`\n\n, the provider ID is `Google`\n\nand the MX resolves to `gmail.com`\n\n. That's useful beyond validation. In a B2B lead-scoring flow, `is_free_email: true`\n\nis a negative signal; in a consumer signup flow, it's neutral or positive. The API gives you the provider bucket so you can decide per workflow instead of hard-coding a list of domains.\n\n`is_catch_all`\n\nwas `false`\n\nhere, but when it's `true`\n\nthe SMTP check is basically lying to you. A catch-all server accepts every recipient, so `smtp_verified`\n\nbecomes meaningless. An agent that trusts SMTP alone will send to `not-a-real-user@catchalldomain.com`\n\nand think it succeeded. The gatekeeper needs to downgrade SMTP when catch-all is true.\n\n`syntax_suggestion`\n\nwas `null`\n\nfor this address, which makes sense—`test@gmail.com`\n\nis spelled correctly. But the feature matters for agent workflows because agents don't fix typos. A human sees `gmial.com`\n\nand corrects it; an agent sees a valid-looking string and sends. The API would return `gmail.com`\n\nas a suggestion for that typo. That's a second chance before the bounce.\n\nGreylisting detection didn't fire on Gmail either, but it's another layer I want in the loop. Some mail servers temporarily reject the first delivery attempt to slow down spammers. An impatient agent might interpret the deferral as a hard failure, or worse, retry aggressively and get rate-limited. Knowing a domain greylists lets you schedule the send instead of hammering it.\n\nSo the data tells a clear story: `test@gmail.com`\n\nis reachable, but not trustworthy. The agent saw reachable. The gatekeeper saw trustworthy. Twelve bounces later, I'm on the gatekeeper's side.\n\nHere's where I stop hedging: **SMTP verification alone is overrated for agentic email workflows.** It's a useful signal. It is not a send permission.\n\nThe problem isn't the protocol. The problem is what an agent does with the signal. Local agent models like Muse Glimmer are built for always-on, function-calling workflows. Docker Sandboxes give coding agents disposable, isolated environments to run tools. qm is building a multiplayer harness so multiple agents can collaborate on work. The common thread is more autonomy, less human in the loop. That's great until the agent picks up a mailer and starts spraying.\n\nThe recent report on document-borne AI worms is what made me nervous. Håkon Måløy's research, disclosed after a 144-day coordination period with Microsoft, showed how attacker-controlled instructions in one Word document can propagate through Copilot-generated documents across trusted workflows. The vulnerability isn't just a single bad prompt; it's a chain of trusted actions that amplifies a mistake. Email is the same shape. One bad send doesn't just bounce. It dings your sender reputation, pollutes your list hygiene, triggers ESP rate limits, and in some jurisdictions creates a compliance event. The agent doesn't see any of that second-order damage.\n\nThat's why I separated \"can send\" from \"should send.\" The validator's `is_trusted_identity`\n\nfield is a \"should send\" signal. It says: even if the mailbox exists, this address has been in three breach dumps, so maybe don't hand it sensitive content without a second look. An agent that only knows SMTP will never make that distinction.\n\nI'm still not sure if blocking every breached address is the right call. Plenty of real humans have old accounts in breach dumps. A hard block could exclude legitimate users. But for an autonomous agent with no human review, I'd rather err on the side of false negatives than explain a reputation crash to my ESP. The tradeoff is messy, and I'm leaving it messy.\n\nThis is also why I linked the gatekeeper to my broader tool-audit work. In a previous post about [building an MCP server for domain investigation](https://dev.to/onizuka/i-built-an-mcp-server-for-domain-investigation-5-security-gotchas-i-hit-3og5), I hit five security gotchas around giving agents network tools. The pattern is the same: the agent doesn't need to know less; it needs a separate layer that knows when to say no. I've also been comparing APIs lately—my [evaluation of twelve domain WHOIS APIs](https://dev.to/onizuka/i-compared-12-whois-apis-subdomain-center-won-on-3-metrics-2nll) taught me that composite scores usually beat raw field dumps. The same logic applies here.\n\nThe gatekeeper is now mandatory before any agent-triggered email leaves my infrastructure. Not optional. Not \"nice to have.\" Mandatory.\n\nHere's what that means in practice:\n\n`smtp_verified`\n\nalone.`is_trusted_identity`\n\nis false, the agent gets a refusal.`syntax_suggestion`\n\nback to the user or agent before the address enters the workflow.`is_free_email`\n\nand `provider`\n\nfeed B2B vs B2C segmentation. A `@gmail.com`\n\nlead gets a different score than a `@company.com`\n\nlead.The implementation is small. A Python wrapper around the API. A refusal response the agent can parse. A log line. That's it. The hard part was deciding that the agent isn't allowed to skip it.\n\nIf you want to build the same thing, the code and docs are on [GitHub](https://github.com/On13uka/email-validator-api), and the hosted endpoint is on [RapidAPI](https://rapidapi.com/On13uka/api/email-validator112). I keep the RapidAPI subscription on a pay-as-you-go plan because validation volume spikes around product launches.\n\nA minimal call looks like this with curl:\n\n```\ncurl --request GET \\\n  --url 'https://email-validator112.p.rapidapi.com/validate?email=test@gmail.com' \\\n  --header 'X-RapidAPI-Key: YOUR_RAPIDAPI_KEY' \\\n  --header 'X-RapidAPI-Host: email-validator112.p.rapidapi.com'\n```\n\nAnd the Python version I run in my gatekeeper:\n\n``` python\nimport requests\n\nurl = \"https://email-validator112.p.rapidapi.com/validate\"\nheaders = {\n    \"X-RapidAPI-Key\": \"YOUR_RAPIDAPI_KEY\",\n    \"X-RapidAPI-Host\": \"email-validator112.p.rapidapi.com\"\n}\n\ndef validate(email):\n    r = requests.get(url, headers=headers, params={\"email\": email}, timeout=10)\n    r.raise_for_status()\n    return r.json()\n\ndata = validate(\"test@gmail.com\")\nprint(data[\"is_trusted_identity\"])  # False for test@gmail.com\n```\n\nBoth return the full JSON I quoted above. Swap the email parameter for whatever your agent is about to contact.\n\nOn August 12, the agent also sent to a disposable domain that the validator had flagged. I saw the flag in the log after the fact. It cost me three hours of scrubbing the list and a temporary rate limit from my ESP. I don't have a tidy lesson from that one. Sometimes you ship the guardrail after the crash.\n\nThe bigger unresolved question is who owns the trust decision in an agent stack. If every tool vendor ships its own validator, we end up with fragmented policy: the email tool says one thing, the database tool says another, the browser tool says a third. Maybe what we need isn't a dozen gatekeepers but a single \"tool-use risk score\" that an agent consults before any external action. Nobody has built that yet. Or if they have, I haven't found the open-source version.\n\nWhat I do know is that I won't run another agent with a mailer until something like this sits in front of it. The 12 bounces were cheap. The next mistake might not be.\n\nIf you had a free weekend, what would you build with an email-validation gatekeeper: a self-healing newsletter list, a disposable-email firewall for your signups, or something weirder?", "url": "https://wpnews.pro/news/i-ran-50-emails-through-ai-agents-12-smtp-bounces-hit", "canonical_source": "https://dev.to/onizuka/i-ran-50-emails-through-ai-agents-12-smtp-bounces-hit-4jc", "published_at": "2026-08-15 22:14:47+00:00", "updated_at": "2026-08-15 22:41:58.071039+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "developer-tools"], "entities": ["Muse Glimmer", "Meta", "RapidAPI", "Gmail", "Google"], "alternates": {"html": "https://wpnews.pro/news/i-ran-50-emails-through-ai-agents-12-smtp-bounces-hit", "markdown": "https://wpnews.pro/news/i-ran-50-emails-through-ai-agents-12-smtp-bounces-hit.md", "text": "https://wpnews.pro/news/i-ran-50-emails-through-ai-agents-12-smtp-bounces-hit.txt", "jsonld": "https://wpnews.pro/news/i-ran-50-emails-through-ai-agents-12-smtp-bounces-hit.jsonld"}}