DevMail API Documentation
Browse the public API, then use Swagger UI for complete schemas and interactive requests.
Open Swagger UIPublic API Endpoints
All public endpoints require an API key and are available under /api/v1.
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/info | Read API version, inbox address, and mailbox usage. |
GET | /api/v1/messages | List and search messages with filters and pagination. |
GET | /api/v1/messages/count | Count matching messages and their attachments. |
GET | /api/v1/message/:id | Read a message body and attachment metadata. |
GET | /api/v1/message/:id/headers | Read message headers only. |
GET | /api/v1/message/:id/attachment/:partId | Download an attachment. |
DELETE | /api/v1/message/:id | Delete one message. |
Message Filters
Use these query parameters with GET /api/v1/messages. Combined filters must all
match.
| Parameter | Use | Behavior |
|---|---|---|
from | Sender | Case-insensitive literal partial match. |
to | Recipient or plus-address alias | Case-insensitive literal partial match; encode + with URLSearchParams. |
subject | Subject | Case-insensitive literal partial match. |
since | Start of date range | Inclusive ISO 8601 timestamp; required with sinceId. |
before | End of date range | Inclusive ISO 8601 timestamp. |
has | Attachments | Use has=attachment. |
limit | Page size | 1–20; defaults to 20. |
start | Page offset | Defaults to 0; must remain 0 with sinceId. |
sinceId | Polling cursor | Requires since; cannot be combined with search filters, before, or has. |
API Key Authentication
Authenticate to the API using API keys generated in the web UI
Authentication Methods
Use your API key with Public API requests in the Authorization header:
API keys are only valid for public API endpoints under /api/v1/*.
Authorization Header
Include your API key in the Authorization header using the Bearer scheme:
Authorization: Bearer mlb_your_api_key_hereUsing DevMail Instead of other providers
Use your generated DevMail inbox and an API key to fetch inbound messages from /api/v1/messages and /api/v1/message/:id, without overhead with
OAuth or two-factor authentication.
DevMail's test-focused inbox and direct API workflow help keep automated email checks faster and more stable.
Searching and Paginating Messages
Filters apply to the whole inbox before start and limit select a page.
Search results are newest first, with descending ID breaking timestamp ties.
- Text filters match literal fragments without case sensitivity. Characters such as
%,_, and\are not wildcards. totalcounts all matching messages before pagination.countcounts messages on the current page.
For example, if 3 of your 67 messages match, limit=2&start=0 returns total: 3, count: 2. The next page, start=2, returns total: 3, count: 1. An offset beyond the last result returns an empty page with
the same matching total.
GET /api/v1/messages/count counts emails and attachments from at most the 20 newest matching messages. Use total from the list endpoint for
the full number of matches.
Parallel CI Runs
Use a unique plus-address alias for every CI test run, then include that alias in the to filter when retrieving messages. This keeps parallel jobs on one persistent inbox
from reading one another's email; do not select the newest unfiltered message.
The public query parameter is to (it searches the stored recipient address).
Because text filters are partial matches, add a UUID to the alias tag for an effectively
unique recipient. Use URLSearchParams so the + is encoded correctly.
Incremental Polling
Combining since with sinceId selects a separate polling mode,
ordered oldest first. Pass both values from nextSince to fetch the next batch after
that timestamp and ID. An empty batch keeps the cursor unchanged.
This mode supports limit and start=0, but rejects a nonzero start or search filters with HTTP 400. sinceId requires since, and polling keeps the whole-inbox total. Using since alone uses ordinary filtered pagination without nextSince.
Example HTTP Requests
Playwright (APIRequestContext)
Use these real-usecase flows in Playwright tests against the DevMail public API.
Verify Email: Extract URL From Message Body
import { test, expect } from '@playwright/test';
test('verifies a new account', async ({ page, request }) => {
const apiUrl = 'https://api.devmail.space/api/v1';
const headers = { Authorization: 'Bearer mlb_your_api_key_here' };
const email = '[email protected]';
// Create the account with `email` before this point.
const listResponse = await request.get(`${apiUrl}/messages`, {
headers,
params: {
to: email,
subject: 'Email Address Verification',
limit: 1,
},
});
await expect(listResponse).toBeOK();
const { messages } = await listResponse.json();
expect(messages).toHaveLength(1);
const messageResponse = await request.get(`${apiUrl}/message/${messages[0].id}`, {
headers,
});
await expect(messageResponse).toBeOK();
const { body } = await messageResponse.json();
const emailText = body.text || body.html || '';
const verificationUrl = emailText.match(
/Confirm Email Address\s+\((https?:\/\/[^\s)]+)\)/
)?.[1];
expect(verificationUrl).toBeTruthy();
await page.goto(verificationUrl!);
});Use a unique plus-address alias for every CI run. If delivery is asynchronous, wrap the message lookup in your test's retry helper.
Company Flow: Count Emails And Attachments
This example counts at most the 20 newest matches, including their attachments.
import { test, expect } from '@playwright/test';
test('count company messages and attachments', async ({ request }) => {
// Base API configuration and filter values.
const API_BASE_URL = 'https://api.devmail.space/api/v1';
const API_KEY = 'mlb_your_api_key_here';
const SINCE_ISO = '2026-03-06T10:00:00.000Z';
const COMPANY_SUBJECT = 'DevMail Corporation Test';
// Reuse auth headers across requests.
const authHeaders = {
Authorization: `Bearer ${API_KEY}`,
};
// 1) Count the newest 20 matches with subject + since filters.
const companyCountsResponse = await request.get(`${API_BASE_URL}/messages/count`, {
params: {
subject: COMPANY_SUBJECT,
since: SINCE_ISO,
},
headers: authHeaders,
});
if (companyCountsResponse.status() !== 200) {
throw new Error(`Company counts request failed: ${companyCountsResponse.status()}`);
}
// 2) Assert expected email and attachment totals.
const companyCountsData = await companyCountsResponse.json();
expect(companyCountsData.emailCount).toBeGreaterThanOrEqual(3);
expect(companyCountsData.attachmentCount).toBeGreaterThanOrEqual(2);
});cURL
# List messages with Authorization header
curl -X GET https://api.devmail.space/api/v1/messages \
-H "Authorization: Bearer mlb_your_api_key_here" \
-H "Content-Type: application/json"
# Incremental polling with stable cursor
curl -X GET "https://api.devmail.space/api/v1/messages?since=2026-03-01T10:00:00.000Z&sinceId=0&limit=20" \
-H "Authorization: Bearer mlb_your_api_key_here" \
-H "Content-Type: application/json"JavaScript (Fetch API)
Fetch the message for a unique run alias. Replace the base recipient with your own generated
address. URLSearchParams preserves the + in aliases.
const headers = { Authorization: 'Bearer mlb_your_api_key_here' };
const baseInbox = '[email protected]';
const [localPart, domain] = baseInbox.split('@');
if (!localPart || !domain) throw new Error('baseInbox must be an email address');
const runStartedAt = new Date().toISOString();
const runAlias = `${localPart}+ci-${crypto.randomUUID()}@${domain}`;
// Configure the application under test to send its email to runAlias.
const query = new URLSearchParams({
to: runAlias,
subject: 'Verify email address',
since: runStartedAt,
limit: '20',
});
const response = await fetch(`https://api.devmail.space/api/v1/messages?${query}`, { headers });
if (!response.ok) throw new Error(`DevMail request failed: ${response.status}`);
const page = await response.json();
if (page.total !== 1) {
throw new Error(`Expected one message for this run alias; found ${page.total}`);
}
console.log(page.messages[0]);Python (Requests)
import requests
# Using Authorization header
headers = {
'Authorization': 'Bearer mlb_your_api_key_here',
'Content-Type': 'application/json'
}
response = requests.get(
'https://api.devmail.space/api/v1/messages',
headers=headers
)
data = response.json()
print(data)Example API Response
A filtered request to /api/v1/messages?subject=Invoice&limit=1 returns 200 OK with this shape. Here, 3 messages match and the first page contains 1:
{
"total": 3,
"count": 1,
"start": 0,
"messages": [
{
"id": 123,
"messageId": "<[email protected]>",
"from": "[email protected]",
"to": "[email protected]",
"subject": "Invoice for February",
"snippet": "Your invoice is ready.",
"hasAttachments": false,
"attachmentCount": 0,
"size": 1024,
"receivedAt": "2026-02-18T10:21:00.000Z"
}
]
}Rate Limiting
Public API requests are rate-limited per API key. Requests without a valid API key fall back to IP-based rate limiting:
- Rate limiter: 10 requests per second
- Primary bucket key: API key from the
Authorizationheader - Fallback bucket key: client IP address
Rate-limit responses come as 429 Too Many Requests.
Usage Policy
These rules apply to all use of DevMail (API and web app):
- No illegal activity: Usage of DevMail for illegal activity is strictly prohibited.
- No reselling: Do not build a paid product that is primarily a wrapper around DevMail.
- No proxy services: Do not mirror or proxy DevMail APIs under a different domain or brand.
See Terms and Conditions for full policy details.
Mailbox Aliases
DevMail supports plus-address aliases for all mailboxes. Use aliases to segment scenarios without creating new inboxes.
- Base mailbox:
[email protected] - Alias mailbox:
[email protected] - Free and Pro plans: alias routes to the same generated mailbox as the base address
- For CI, generate one alias tag per run, for example
test-stable123+ci-<uuid>@mailbox.space, and retrieve it with/messages?to=....
Email and Mailbox Limits
| Plan | Max email | Max single attachment | Max total attachments | Mailbox storage |
|---|---|---|---|---|
| Free | 1 MB | 512 KB | 1 MB | 5 MB |
| Pro | 8 MB | 3 MB | 6 MB | 50 MB |
Error Responses
API returns standard HTTP status codes for request outcomes:
401 Unauthorized
Your API key is invalid, missing, or has been revoked.
{
"error": "Invalid API key"
}Common causes:
- API key is missing from the request
- API key format is incorrect
- API key has been revoked
- API key doesn't exist
429 Too Many Requests
You have exceeded your rate limit.
{
"error": "Rate limit exceeded. Please try again later."
}Response headers:
Retry-After: Number of seconds to wait before retrying
400 Bad Request
The request is malformed or contains invalid parameters.
{
"error": "Invalid request parameters"
}500 Internal Server Error
An unexpected error occurred on the server.
{
"error": "An error occurred. Please try again later."
}Best Practices
- Keep your API keys secure: Never commit API keys to version control or share them publicly.
- Use environment variables: Store API keys in environment variables rather than hardcoding them.
- Rotate keys regularly: Generate new API keys periodically and revoke old ones.
- Use descriptive names: Give your API keys meaningful names to identify their purpose.
- Revoke unused keys: Delete API keys that are no longer in use to minimize security risks.
- Monitor usage: Check the "Last Used" timestamp in your API keys list to identify inactive keys.
- Handle rate limits gracefully: Implement exponential backoff when you receive 429 responses.
Getting Started
- Sign in, then open API Keys settings to generate your first API key
- Give your key a descriptive name (e.g., "Production CI/CD")
- Copy the generated key and store it securely (it will only be shown once)
- Use the key in your API requests using one of the authentication methods above
- Monitor your key usage and revoke keys when no longer needed