Email API
Create a disposable inbox from your code, then read the mail that lands in it. Built for the test that signs up, waits for a verification code and types it back in.
Looking for an inbox to read in your browser? That's the free temp mail, or a permanent address plan. This page is the API for software.
What it's for
Every signup flow needs an email address, so every test of that flow needs one too — a fresh one each time, or the second run collides with the first. Teams usually end up with a catch-all mailbox, an IMAP client in the test suite, and a slow, flaky test that nobody trusts.
This replaces all of that with two HTTP calls: ask for an address, then read what arrives at it.
A complete example
Signing up and pulling the verification code back out, in the order a test would do it:
curl -X POST https://api.mailfornet.com/v1/inboxes \
-H "Authorization: Bearer mf_live_your_key" \
-H "Content-Type: application/json" \
-d '{"ttlMinutes": 30}'
{"address":"k3f9xq2m@mailfornet.com","expiresAt":"2026-09-19T18:24:00Z"}
Sign up on your app with that address, then ask for the mail. Add wait and the request
holds open until something arrives, so you never write a polling loop:
curl "https://api.mailfornet.com/v1/inboxes/k3f9xq2m@mailfornet.com/messages?wait=30" \
-H "Authorization: Bearer mf_live_your_key"
{"address":"k3f9xq2m@mailfornet.com","messages":[
{"id":"m_8821","from":"noreply@yourapp.com","subject":"Confirm your email","receivedAt":"..."}
]}
Then read the message itself and pull the code out of it:
curl "https://api.mailfornet.com/v1/messages/m_8821?address=k3f9xq2m@mailfornet.com" \
-H "Authorization: Bearer mf_live_your_key"
In a test
const api = (path, init) => fetch(`https://api.mailfornet.com/v1${path}`, {
...init,
headers: { Authorization: `Bearer ${process.env.MAILFORNET_KEY}`, ...init?.headers },
}).then((r) => r.json());
// A fresh address for this test run
const { address } = await api('/inboxes', { method: 'POST' });
await signUp(address);
// One call: it returns as soon as the mail lands, or after 30s if it never does
const { messages } = await api(`/inboxes/${address}/messages?wait=30`);
const mail = await api(`/messages/${messages[0].id}?address=${address}`);
await enterCode(mail.text.match(/\d{6}/)[0]);
Endpoints
| Method | Path | What it does |
|---|---|---|
| POST | /v1/inboxes | Create an inbox. Optional username, domain, ttlMinutes (1–1440, default 60). |
| GET | /v1/inboxes/{address}/messages | List what has arrived. Optional limit (1–100), wait (0–60 seconds), since (unix ms), from, subject. |
| GET | /v1/inboxes/{address}/code | Wait for a verification code or confirm link and return just that. Optional wait (0–60), since, from, subject. 404 code_not_found if none arrives. |
| GET | /v1/messages/{id}?address= | One message, with its text and HTML, plus the extracted code, every links entry and the likely verificationLink. |
| DELETE | /v1/inboxes/{address} | Delete an inbox and its mail now, without waiting for it to expire. |
| GET | /v1/usage | Requests used this month, and what's left. |
| GET | /v1/domains | Domains you can create addresses on. |
| POST | /v1/webhooks | Register an endpoint. Returns the signing secret once. |
| GET | /v1/webhooks | List your endpoints. |
| GET | /v1/webhooks/{id}/deliveries | The last 20 attempts, with the status we got back. |
| POST | /v1/webhooks/{id}/test | Send a sample event, to check your endpoint and signature code. |
| DELETE | /v1/webhooks/{id} | Remove an endpoint. |
Waiting for one particular email
An inbox in a test often receives more than the message you care about. from and
subject match on a substring, and combine with wait, so you can hold out for
exactly the mail your test is about rather than whatever arrived first:
curl "https://api.mailfornet.com/v1/inboxes/$ADDRESS/messages?wait=30&subject=verify" \
-H "Authorization: Bearer mf_live_your_key"
Other languages
Python:
import os, requests
API = "https://api.mailfornet.com/v1"
headers = {"Authorization": f"Bearer {os.environ['MAILFORNET_KEY']}"}
address = requests.post(f"{API}/inboxes", headers=headers).json()["address"]
sign_up(address)
# Returns as soon as the mail lands
mail = requests.get(
f"{API}/inboxes/{address}/messages",
params={"wait": 30, "subject": "verify"},
headers=headers,
).json()["messages"][0]
body = requests.get(f"{API}/messages/{mail['id']}", params={"address": address}, headers=headers).json()
code = re.search(r"\d{6}", body["text"]).group()
PHP:
$key = getenv('MAILFORNET_KEY');
$ctx = ['http' => ['header' => "Authorization: Bearer $key\r\n"]];
$inbox = json_decode(file_get_contents(
'https://api.mailfornet.com/v1/inboxes',
false,
stream_context_create(['http' => ['method' => 'POST', 'header' => "Authorization: Bearer $key\r\n"]])
), true);
$messages = json_decode(file_get_contents(
"https://api.mailfornet.com/v1/inboxes/{$inbox['address']}/messages?wait=30",
false,
stream_context_create($ctx)
), true);
Webhooks
Rather than asking us whether mail has arrived, you can be told. Register an https endpoint and we POST to it the moment a message reaches any of your addresses:
{
"event": "message.received",
"createdAt": "2026-09-19T18:05:26Z",
"data": {
"address": "k3f9xq2m@mailfornet.com",
"from": "noreply@yourapp.com",
"subject": "Confirm your email",
"intro": "Your code is 493021",
"receivedAt": "2026-09-19T18:05:26Z"
}
}
Three headers come with it: Mailfornet-Event, Mailfornet-Timestamp and
Mailfornet-Signature. The signature is an HMAC-SHA256 of
{timestamp}.{raw body} using the secret you were given when you registered the endpoint.
Verify it before trusting the request — otherwise anyone who guesses your URL can forge one:
import crypto from 'node:crypto';
const expected = crypto
.createHmac('sha256', process.env.MAILFORNET_WEBHOOK_SECRET)
.update(`${req.headers['mailfornet-timestamp']}.${rawBody}`)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers['mailfornet-signature']))) {
return res.status(400).end();
}
Reply with a 2xx. Anything else counts as a failure, and an endpoint that fails 20 times in a row is
switched off — check /v1/webhooks/{id}/deliveries to see what we got back from yours.
You don't have to send yourself a real email to check any of this. Send test in
your account (or POST /v1/webhooks/{id}/test) delivers a sample
webhook.test event, signed exactly like a real one, and tells you what your endpoint
answered.
Retrying safely
Send an Idempotency-Key header on a POST and a repeat of that request returns the
original response instead of doing the work twice. A runner that times out and retries then ends up
with one inbox, not two:
curl -X POST https://api.mailfornet.com/v1/inboxes \
-H "Authorization: Bearer mf_live_your_key" \
-H "Idempotency-Key: signup-test-run-8821"
A replayed response carries Idempotent-Replayed: true.
OpenAPI
The whole API is described at /openapi.json (OpenAPI 3.1). Point your generator at it and get a typed client in your own language, rather than writing one from this page.
Authentication
Every request carries your key as a bearer token:
Authorization: Bearer mf_live_...
Create keys in your account. A key is shown once, when you create it, and only a hash of it is stored here — so if you lose it, revoke it and make another. Keep keys out of your repository and in your CI secrets.
Limits
Every response tells you where you stand, so a client can slow down before it is throttled:
X-RateLimit-Limit: 10000
X-RateLimit-Remaining: 9863
X-RateLimit-Reset: 1790812800
Requests are counted per calendar month and reset on the 1st. Inboxes you create belong to your account and never count against your permanent-address allowance.
Errors
Every response carries a Mailfornet-Request-Id, and every failure repeats it in the body.
Quote it to support and we can find the exact request.
{
"error": {
"type": "invalid_request_error",
"code": "address_taken",
"message": "That address is already taken",
"docUrl": "https://mailfornet.com/api"
},
"requestId": "req_dLD-T3vzY1TUJ8kX"
}
Branch on code, not on the message — the wording may change, the code will not.
| Status | Code | Means |
|---|---|---|
| 401 | missing_api_key, invalid_api_key | No key, or a key that has been revoked. |
| 402 | plan_expired | The subscription lapsed. Renew it. |
| 403 | api_not_in_plan | The plan has no API access. |
| 404 | inbox_not_found, message_not_found | Not yours, or never existed. |
| 409 | address_taken | Someone holds that username. |
| 410 | inbox_expired | The inbox's lifetime ran out. |
| 402 | insufficient_balance | Balance too low to create an inbox. Top up; reading mail still works. |
| 429 | quota_exceeded | Free requests used up and no balance added yet. |
Pricing
Every plan includes unlimited inboxes from the API, long-polling, webhooks and usernames you choose. Inboxes you create expire on their own and never count against a plan's permanent addresses.
After the free requests, pay as you go
Add balance from $5, any amount you like. Each new inbox costs $0.01;
listing messages, waiting for mail and pulling out codes cost nothing. The balance never expires, and
nothing is ever charged automatically: when it runs out, creating inboxes stops with
insufficient_balance until you top up.
While the balance is paying, every response carries X-Balance-Cents, so your own code can tell when it is running low.
Need far more than this, an invoice or your own domain? Tell us what you need and we'll quote it.
Fair use
The API is for testing your own software. It isn't for creating accounts in bulk on services you don't own, and our Terms of Service apply to it exactly as they do to the rest of Mailfornet.
Common questions
What is the Mailfornet email API for?
Automated tests that need a real email address. Your test creates an inbox, signs up with that address, then reads the verification code out of the inbox — all from code, with no mailbox to maintain.
How much does the email API cost?
There is a free tier of 250 requests a month with no card. After that it is pay as you go: add balance from $5 and each new inbox costs $0.01, while reading mail, waiting for it and pulling codes are free. The balance never expires and nothing is ever charged automatically. Enterprise is priced on request.
How fast do emails arrive?
Mail is stored the moment it reaches our servers, usually within a second or two of being sent. Add wait=30 to the messages endpoint and the request returns as soon as it lands, or register a webhook and we push it to you.
How long do API inboxes last?
You choose, from 1 minute to 24 hours. They expire on their own, so a test run never leaves anything behind.