DevMail API Documentation

Browse the public API, then use Swagger UI for complete schemas and interactive requests.

Open Swagger UI

Public API Endpoints

All public endpoints require an API key and are available under /api/v1.

MethodPathPurpose
GET/api/v1/infoRead API version, inbox address, and mailbox usage.
GET/api/v1/messagesList and search messages with filters and pagination.
GET/api/v1/messages/countCount matching messages and their attachments.
GET/api/v1/message/:idRead a message body and attachment metadata.
GET/api/v1/message/:id/headersRead message headers only.
GET/api/v1/message/:id/attachment/:partIdDownload an attachment.
DELETE/api/v1/message/:idDelete one message.

Message Filters

Use these query parameters with GET /api/v1/messages. Combined filters must all match.

ParameterUseBehavior
fromSenderCase-insensitive literal partial match.
toRecipient or plus-address aliasCase-insensitive literal partial match; encode + with URLSearchParams.
subjectSubjectCase-insensitive literal partial match.
sinceStart of date rangeInclusive ISO 8601 timestamp; required with sinceId.
beforeEnd of date rangeInclusive ISO 8601 timestamp.
hasAttachmentsUse has=attachment.
limitPage size1–20; defaults to 20.
startPage offsetDefaults to 0; must remain 0 with sinceId.
sinceIdPolling cursorRequires 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_here

Using 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.
  • total counts all matching messages before pagination. count counts 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 Authorization header
  • Fallback bucket key: client IP address
Rate Limit Exceeded

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

PlanMax emailMax single attachmentMax total attachmentsMailbox storage
Free1 MB512 KB1 MB5 MB
Pro8 MB3 MB6 MB50 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

  1. Sign in, then open API Keys settings to generate your first API key
  2. Give your key a descriptive name (e.g., "Production CI/CD")
  3. Copy the generated key and store it securely (it will only be shown once)
  4. Use the key in your API requests using one of the authentication methods above
  5. Monitor your key usage and revoke keys when no longer needed

Testing use only. Do not send sensitive or production data.