<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Spruik Co</title>
    <description>The latest articles on DEV Community by Spruik Co (@spruikco).</description>
    <link>https://dev.to/spruikco</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4025842%2Fea730177-aaa3-450d-95d6-bf2e79965ddf.jpg</url>
      <title>DEV Community: Spruik Co</title>
      <link>https://dev.to/spruikco</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/spruikco"/>
    <language>en</language>
    <item>
      <title>Making an email API agent-native: llms.txt, a /api/me endpoint, and a zero-DNS sandbox sender</title>
      <dc:creator>Spruik Co</dc:creator>
      <pubDate>Sun, 12 Jul 2026 08:21:15 +0000</pubDate>
      <link>https://dev.to/spruikco/making-an-email-api-agent-native-llmstxt-a-apime-endpoint-and-a-zero-dns-sandbox-sender-446</link>
      <guid>https://dev.to/spruikco/making-an-email-api-agent-native-llmstxt-a-apime-endpoint-and-a-zero-dns-sandbox-sender-446</guid>
      <description>&lt;p&gt;Most APIs are "agent-compatible": an agent &lt;em&gt;can&lt;/em&gt; call them. Far fewer are &lt;em&gt;agent-native&lt;/em&gt;: an agent can accomplish the whole job on its own, with no human un-sticking it halfway. We spent a few weeks turning &lt;a href="https://send16.com" rel="noopener noreferrer"&gt;Send16&lt;/a&gt; (an email platform) from the first into the second, and the gap turned out to be smaller and weirder than expected. Here's the whole thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The wall: onboarding, not the API
&lt;/h2&gt;

&lt;p&gt;Our send endpoint was fine. An agent could form a request. But it couldn't &lt;em&gt;succeed&lt;/em&gt;, because like almost every email API we required domain verification — SPF, DKIM, DMARC — before we'd send a single message. That's a reasonable anti-abuse default for humans. For an unattended agent it's fatal: step one is "edit your DNS," which the agent can't do. So the first send always failed, and a failed first action is where agent workflows die.&lt;/p&gt;

&lt;p&gt;The lesson: &lt;strong&gt;for an agent, onboarding &lt;em&gt;is&lt;/em&gt; the API.&lt;/strong&gt; The happy path has to include a first success that needs no out-of-band human step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 1 — a zero-DNS sandbox sender
&lt;/h2&gt;

&lt;p&gt;We added a sandbox sender. If you send from &lt;code&gt;onboarding@send16.com&lt;/code&gt;, Send16 delivers it — with no domain, no DNS, no verification — but &lt;strong&gt;only to your own account owner's email&lt;/strong&gt;. Verify a domain later to send to anyone else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.send16.com/api/transactional/api/send &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sk_live_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"from":"onboarding@send16.com","to":"you@example.com","subject":"It works","html":"&amp;lt;p&amp;gt;Hello from an agent&amp;lt;/p&amp;gt;"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That "only to yourself" constraint is the whole trick: it's a real, deliverable send (the agent sees the full lifecycle — queued, sent, delivered) but it can't be abused to spam strangers, because the only reachable address is one you already control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 2 — an identity endpoint the agent calls first
&lt;/h2&gt;

&lt;p&gt;An agent dropped into a workspace doesn't know who it is or what it's allowed to do. So we added:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/me
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which returns the workspace, the plan/quota, the account owner, and the &lt;code&gt;sandbox_recipient&lt;/code&gt; — the exact address the sandbox can reach. One call and the agent is oriented. Our docs literally say "call this first."&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 3 — an llms.txt for the runtimes that can't npm install
&lt;/h2&gt;

&lt;p&gt;Not every agent runtime can install an SDK. So &lt;code&gt;https://send16.com/llms.txt&lt;/code&gt; documents the raw HTTP contract: the send endpoint, how to read delivery status by &lt;code&gt;log_id&lt;/code&gt;, the sandbox trick, and the &lt;code&gt;/api/me&lt;/code&gt; bootstrap. It's the same information a human skims from the docs, arranged for a model.&lt;/p&gt;

&lt;p&gt;A subtle but important detail we got wrong first: &lt;strong&gt;delivery status.&lt;/strong&gt; The obvious-looking &lt;code&gt;/api/messages/:id&lt;/code&gt; was plan-gated (it's the inbox log). Agents kept hitting 403. The right endpoint is &lt;code&gt;GET /api/emails/:id&lt;/code&gt; with the &lt;code&gt;log_id&lt;/code&gt; from the send response — ungated, returns &lt;code&gt;sent -&amp;gt; delivered -&amp;gt; opened&lt;/code&gt;. If your llms.txt points an agent at a gated endpoint, it'll loop on a permission error forever. Point it at the ungated one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 4 — an MCP server (and why tools/list must work keyless)
&lt;/h2&gt;

&lt;p&gt;For MCP clients (Claude Desktop, Cursor, Claude Code) we ship &lt;a href="https://www.npmjs.com/package/send16-mcp" rel="noopener noreferrer"&gt;&lt;code&gt;send16-mcp&lt;/code&gt;&lt;/a&gt; — 79 tools over stdio, plus a hosted Streamable-HTTP endpoint for remote clients.&lt;/p&gt;

&lt;p&gt;Two things worth stealing if you build one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multi-tenant key threading.&lt;/strong&gt; In hosted HTTP mode each request carries its own &lt;code&gt;Authorization: Bearer sk_live_...&lt;/code&gt;. We thread it through the whole request with &lt;code&gt;AsyncLocalStorage&lt;/code&gt;, so one process safely serves many users and every tool call runs against the connecting user's workspace.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;tools/list&lt;/code&gt; must succeed without a key.&lt;/strong&gt; Registries and clients &lt;em&gt;introspect&lt;/em&gt; your server before anyone authenticates — they start it and ask for the tool list. If your server hard-exits on a missing API key at startup, it fails every registry's health check. Validate the key lazily, at &lt;code&gt;tools/call&lt;/code&gt; time, not at boot.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Proving it: the cold-agent test
&lt;/h2&gt;

&lt;p&gt;The honest way to know if you succeeded is to stop reasoning about it and run a &lt;em&gt;cold&lt;/em&gt; agent: fresh context, no prior knowledge of your product, one instruction — "send an email with Send16." Then watch.&lt;/p&gt;

&lt;p&gt;Ours found the docs, called &lt;code&gt;/api/me&lt;/code&gt;, sent from the sandbox, and confirmed delivery — roughly four calls, no human intervention. The first time it worked end-to-end without us feeding it anything was the moment "agent-native" stopped being a slogan.&lt;/p&gt;

&lt;p&gt;If you own an API and want to know whether an agent can really use it: give a cold agent your public docs and one goal, and don't help. Where it gets stuck is your actual onboarding backlog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways (portable to any API)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Onboarding is the API.&lt;/strong&gt; Guarantee a first success with no out-of-band human step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give the agent a &lt;code&gt;whoami&lt;/code&gt;.&lt;/strong&gt; One call that returns identity + capabilities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish an llms.txt&lt;/strong&gt; pointing at &lt;em&gt;ungated&lt;/em&gt; endpoints; verify each one an agent will hit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make introspection keyless.&lt;/strong&gt; &lt;code&gt;tools/list&lt;/code&gt; (or your schema/discovery route) must not require auth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test cold.&lt;/strong&gt; A blind agent + one goal is the only benchmark that doesn't lie.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Send16 is an email platform for developers and marketers. The MCP server is open source: &lt;a href="https://github.com/spruikco/send16-mcp" rel="noopener noreferrer"&gt;github.com/spruikco/send16-mcp&lt;/a&gt; · &lt;code&gt;npx send16-mcp&lt;/code&gt; · &lt;a href="https://send16.com/mcp" rel="noopener noreferrer"&gt;send16.com/mcp&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>email</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
