What is programmable email? A developer's definition
Programmable email is a category of receive-side email infrastructure exposed through an API: create an inbox, wait for a message, parse the result. Here's the full definition.
Programmable email is receive-side email infrastructure exposed through an API. You create an inbox with one call, you wait for a message with another, and you get a parsed JSON object back — with the OTP, the magic link, the headers, and the body all extracted as structured fields. There are no MIME trees to walk, no IMAP loops to write, no polling intervals to tune. It is the same shape that Stripe gives payments and Twilio gives SMS, applied to the receive half of email.
That definition is two paragraphs longer than it should be because the term is contested. “Programmable email” is sometimes confused with disposable email (browser-based burner inboxes), with transactional sending (the Resend / Postmark / SES side of the wire), or with email aliasing (privacy-forwarding services like SimpleLogin). It is none of those. This post is the precise definition, the three primitives that make up the category, what programmable email is not, why the category exists in 2026, and a complete code example.
The three primitives
Programmable email is built on three operations: create, wait, and parse. Every other feature is sugar on top of these three.
Create
A create_inbox call returns a fully-qualified email address that your code can hand to any signup form, transactional sender, or agent. The address is on a real domain with proper MX, DKIM, and SPF records — not a *@disposablemail.example wildcard. The inbox is yours; the API key that created it is the only thing that can read from it.
What create replaces: a couple of decades of “find a Gmail to use for tests,” “register qa+1@yourcompany.com and hope no one merges it,” and “build an SES inbound rule by hand.” The act of getting an addressable inbox is now a single REST call.
const inbox = await otp.inboxes.create({
mode: 'ephemeral',
ttlMinutes: 10,
});
// inbox.address: "abc123@inbox.catchotp.com"
// inbox.id: "ibx_01H..."
The shape that matters: the inbox has a TTL or is persistent (your choice), it carries a stable ID for the lifecycle of the test or task, and it does not require DNS, domain verification, or any infrastructure on your side. You did not provision anything — you reserved an address.
Mode flags are the second-order concern. Ephemeral inboxes auto-delete after the TTL fires; persistent inboxes survive until you delete them; pattern inboxes accept multiple local-parts under a shared prefix. All three are common; all three are one parameter on the same call.
Wait
A wait_for_otp or wait_for_message call holds the connection open until the matching message arrives or the timeout expires. It is a true long-poll — a single HTTP request with a server-side condition — not a client-side polling loop. The latency floor is “as fast as the message arrives at our SMTP receive,” not “as fast as your polling interval permits.”
What wait replaces: every sleep(10) in every flaky CI test that ever depended on email. Every “did the email arrive yet” loop. Every IMAP connection that times out three minutes into a 12-minute test run. The wait is the primitive that makes email-driven tests reliable.
const code = await otp.inboxes.waitForOtp(inbox.id, {
timeoutSeconds: 30,
});
// code: "493021"
The shape that matters: the call returns when the next matching message arrives, the call resolves immediately if a matching message is already in the inbox (with a since parameter to disambiguate), and the timeout is a real ceiling not a “polling budget.” Behind the scenes the implementation typically uses event-bus rules per waiter — we covered the EventBridge architecture for catchotp in detail — but the API consumer never sees that.
p50 latency from email send to wait resolution is around 600ms in practice on a managed implementation. p95 is under 1.2 seconds. Those numbers are what a passing test feels like; without them, email-driven tests are flaky by default.
Parse
A parsed message is a JSON object with the headers, the text body, the HTML body, the links extracted as an array, the attachments as a list of metadata-plus-presigned-URLs, and an otp field already populated for the common code formats. The MIME parsing happens on the server; what you read in your test is structured data.
What parse replaces: a small library of regexes, a cheerio install for HTML stripping, an email-parser install for MIME, and the bug that occurs every six months when a transactional provider changes their template and your regex stops matching. The parsing is centralized and updated as new formats appear.
const message = await otp.messages.waitFor(inbox.id, { timeoutSeconds: 30 });
// message.subject: "Verify your email"
// message.from: [{ address: "noreply@example.com", name: "Example" }]
// message.text: "Your code is 493021..."
// message.html: "<html>..."
// message.links: [{ url: "https://example.com/verify/...", text: "Verify" }]
// message.otp: "493021"
// message.attachments: []
The shape that matters: the same call that waits for a message returns it parsed. There is no second round-trip to fetch the body. There is no need to discover whether the OTP is in text or html — the parser walks both. There is no MIME library in your dependency tree.
A working parser handles roughly a dozen common OTP formats (digits with whitespace, hyphenated digits, digits in HTML table cells, digits with zero-width-space separators, the works). When the parser misses, you pass a regex override:
const code = await otp.inboxes.waitForOtp(inbox.id, {
pattern: /verification\s+code:?\s*([a-z0-9]{8})/i,
timeoutSeconds: 30,
});
Three primitives. Everything else — webhooks, audit logs, custom domains, MCP servers, CLI tools, framework plugins — is built on top.
What programmable email is not
Three categories adjacent to programmable email get conflated with it constantly. They are different products solving different problems.
Not disposable email
Disposable email is the consumer-facing category: browser-based burner inboxes on 10minutemail-style sites. You visit a page, you get an address, you read incoming mail in a webmail UI, the inbox expires. There is no API, no programmability, no audit trail, and the domains are usually on signup blocklists. We covered the full disposable-email problem in 2026 — the short version is that disposable email and programmable email solve different problems and the SEO confusion does both products a disservice.
Programmable email’s audience is a developer, an agent, or a CI pipeline. Disposable email’s audience is a human with a browser. The fact that both involve “an inbox you do not own forever” is the only thing they have in common.
Not transactional sending
Transactional sending is the send-side: SES, Resend, Postmark, Mailgun, Sendgrid. Your application generates an email, hands it to a sending API, and the recipient gets it. That is one half of email infrastructure.
Programmable email is the other half: receive-side. Your application gets an inbox, hands the address to something else, and reads the messages that arrive there. The two are complementary. A typical signup-flow test uses a transactional sender to deliver the verification email and a programmable inbox to receive it. They are different products from different vendors most of the time, and that is fine.
A useful frame: send-side is to programmable email what console.log is to process.stdin. Both are essential. They do different things.
Not email aliasing
Email aliasing is the privacy-forwarding category: SimpleLogin, AnonAddy, Apple Hide My Email, Firefox Relay, DuckDuckGo Email Protection. A user generates an alias and uses it instead of their real address; mail sent to the alias forwards to the user’s real Gmail or iCloud. The alias is “burner” in that the user can revoke it, but the underlying inbox is a real person’s real inbox.
Programmable email does not forward. The inbox is the destination, not a hop. Mail sent to a catchotp address terminates at our infrastructure and is exposed to your code via the API. There is no privacy-protection use case here — programmable email is for software, not for humans hiding their real address.
If your job-to-be-done is “I want to sign up for a newsletter without exposing my Gmail,” the right tool is an aliasing service. If your job is “my CI test needs to read the OTP from a verification email,” the right tool is programmable email.
Why this category exists now
Programmable email is not a 2026-only category — receive-side APIs have existed for a decade — but it has hit category-shaped traction in the last two years. Three forces.
Signup flows have gotten more email-dependent. A signup that did not require email verification in 2018 does in 2026. Magic-link login has displaced password-only flows. Most B2B SaaS gates the trial on a verified domain. Most consumer apps require an email-confirmed account before any meaningful action. The receive side of email is on the critical path of nearly every user journey, which means it is on the critical path of every test of every user journey.
AI agents need to operate independently. This is the force that catalyzed the category. An autonomous agent needs to register accounts on services it does not have credentials for. It cannot use the human operator’s Gmail (security, scale, and discoverability all break). It needs a programmable inbox per task, and it needs that inbox to be addressable, scoped, and parsed. We covered the agent-side patterns for email handling in a separate post. The TL;DR is that agents need this primitive at a scale humans never did.
CI test reliability requirements have risen. “Our auth tests are flaky, we skip them in CI” is a 2018 sentence; in 2026 it is a sign of a team that is not shipping to production. Email-driven tests need to be as reliable as any other test, which means the receive primitive needs to be deterministic — long-poll, not poll-and-pray.
The combination is what produced the category. None of the three forces alone was enough to support a full vendor space; together they are.
A complete create-wait-parse cycle
End-to-end TypeScript example. Sign up to a service, wait for the verification email, extract the OTP, complete signup, return the resulting account address. The signup endpoint is fictional but the flow is the real shape.
import { CatchOTP } from '@catchotp/sdk';
const otp = new CatchOTP({ apiKey: process.env.CATCHOTP_KEY! });
async function signupAndVerify(): Promise<{ address: string; accountId: string }> {
// Create
const inbox = await otp.inboxes.create({
mode: 'ephemeral',
ttlMinutes: 30,
});
// Trigger the signup that sends the verification email
const signupRes = await fetch('https://example.com/api/signup', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ email: inbox.address }),
});
if (!signupRes.ok) {
throw new Error(`Signup failed: ${signupRes.status}`);
}
// Wait
const code = await otp.inboxes.waitForOtp(inbox.id, {
timeoutSeconds: 30,
});
// Submit the code to complete verification
const verifyRes = await fetch('https://example.com/api/verify', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ email: inbox.address, code }),
});
if (!verifyRes.ok) {
throw new Error(`Verify failed: ${verifyRes.status}`);
}
const { accountId } = await verifyRes.json();
// Optional: clean up. The TTL would handle it anyway.
await otp.inboxes.delete(inbox.id);
return { address: inbox.address, accountId };
}
Three primitives, four HTTP calls, no MIME parsing, no polling loop. That is the full integration. Multiply by 50 for parallel signups (we covered the agent-at-scale patterns separately) and the shape stays the same — fan-out the create, fan-out the wait, collect the codes.
Who uses programmable email
The four audiences who get the most leverage from this primitive map onto the catchotp ICPs directly.
Individual developers building products that have signup, magic-link login, or password reset flows. The pain is “I do not want to use my Gmail for test signups, and Mailinator does not let me parse the email programmatically.” Programmable email replaces the workaround with a real primitive. Free-tier usage is typical; Pro upgrade happens when CI parallelism caps bite.
QA engineers and SDETs at mid-sized companies running CI pipelines that need to exercise auth flows reliably. The pain is “our auth tests sleep for 30 seconds hoping the email arrives, and they fail in CI half the time anyway.” Programmable email turns those flaky sleep-based tests into deterministic long-poll waits. The most common adoption pattern here is a single QA engineer puts the API into the team’s tests-core library and the rest of the org picks it up organically.
Platform and SRE engineers at growth-stage companies who own shared testing infrastructure. The pain is “every team reinvents test email; we want one tool, org-wide quotas, and an audit trail.” Programmable email’s tiered model — individual keys for devs, an org tier for shared usage — fits that shape. The buying motion is usually a developer or QA brought it in first, then platform formalized it.
AI-agent and MCP-driven builders who need email as a programmable resource for autonomous workflows. The pain is “my agent needs to sign up for 20 services to evaluate them; I cannot use my personal email.” Programmable email is the only viable primitive for this — disposable inboxes are unscriptable and personal email does not scale. The agent-builder ICP is also where MCP-server ergonomics matter most. We covered both the agent flow patterns and the scale patterns in dedicated posts.
FAQ
Is programmable email the same as a webhook?
No, but they are related. A webhook is one of the delivery mechanisms a programmable-email service can offer — when a message arrives, fire an HTTP POST at the URL you registered. The wait API is the other delivery mechanism — block on a long-poll request until the message arrives. Most services offer both. Webhooks are useful when your code already runs as a webhook receiver (a serverless function, a queue worker); the wait API is useful when your code is a test or an agent that wants to block on a specific event.
Do I need DNS for programmable email?
No, not for the basic case. Inboxes on the provider’s shared domain (*@inbox.catchotp.com for catchotp) work without any DNS on your side. If you want addresses on your own domain — for branded testing, or because your transactional sender allow-lists specific recipient domains — you point an MX record at the provider’s inbound and they manage DKIM signing for you. That is a Team-tier feature on most providers.
How is this different from email aliasing services like SimpleLogin?
Aliasing services forward to a real inbox you own. A SimpleLogin alias sends the message to your Gmail; a programmable-email inbox terminates the message at the provider’s infrastructure and exposes it via API. Aliasing is for human privacy use cases (sign up without exposing your real address); programmable email is for code that needs to read the message. The two products live in different parts of the stack and rarely substitute for each other.
Can I use programmable email to send mail?
No. Programmable email is receive-side only. To send mail you use a transactional sender like Resend, Postmark, or AWS SES. Most teams using programmable email also have a transactional sender for the send half — they are complementary, not competitive.
What does “long-poll” actually mean?
Long-poll is an HTTP technique where the server holds the connection open until a condition is met or a timeout fires, instead of returning immediately. From the client’s perspective it looks like one HTTP request that takes 0 to 30 seconds to resolve. From the server’s perspective it is an event-driven wait — the connection is parked, and when the condition fires (a matching message arrives), the server completes the response. This is faster and more efficient than client-side polling, which fires a new request every N seconds and pays the round-trip cost on each one.
Are programmable-email addresses on disposable-domain blocklists?
Generally no. The standard disposable-email-domains list and similar feeds catalog ad-supported consumer-facing burner inboxes (Mailinator, 10MinuteMail, etc.). Programmable-email providers operate on a different shape — account-scoped, audit-logged, abuse-controlled — and are typically not blocklisted by default. There is no guarantee for any specific signup flow, but in practice catchotp domains pass most signup flows that block disposable-email categories.
What is the latency target for a wait?
The implementation-specific answer for catchotp is p50 around 600ms and p95 under 1.2 seconds, measured from the moment the SMTP receive ends to the moment the long-poll resolves on the client. Other providers will have different numbers; the right question to ask is “is this a true long-poll or a client-side polling loop with a timeout budget” — the architecture choice determines the latency floor.
How is this different from running my own SES inbound?
You can absolutely run SES inbound yourself. We did. Two weeks in, you own a managed SMTP receive, a Lambda parser, an S3 bucket for raw MIME, a DynamoDB table for parsed messages, an EventBridge bus, a long-poll Lambda, the rate-limit logic, the abuse-control logic, the DKIM/DMARC posture, and an on-call rotation for when SES throttles you. That is real engineering work. Programmable email is the choice to outsource it. Both are reasonable; the ROI math depends on your team size and what else is on your plate. The catchotp pipeline post walks through the architecture honestly so you can decide.
Try the primitive. Free tier with 5 inboxes and 1,000 messages a month, no credit card. Start free or read the pricing page for the Pro and Team details.
Related reading
- Programmable Email vs Disposable Email — the side-by-side that decides which category your problem lives in.
- How AI Agents Handle Email — what programmable email looks like in an agent loop.
- Building a programmable email API — the architecture under the API.
The shorter version: programmable email is create, wait, parse. Three primitives, exposed as an API, applied to receive-side email. Everything else is sugar.