Mailexam REST API¶
The Mailexam REST API lets you manage projects, inboxes, and messages programmatically — ideal for automated tests, QA, and CI/CD. A typical flow: your app sends mail via SMTP, and a test uses the API to confirm delivery and assert on subject, body, or attachments.
The full OpenAPI spec is available in Swagger UI.
Base URL¶
| Parameter | Value |
|---|---|
| Base URL | https://mailexam.io/api/v1 |
| Format | JSON |
| Methods | GET, POST, PUT, DELETE |
Regional domains
| Region | API |
|---|---|
| Russia | mailexam.ru |
| China | mailexam.cn |
| International | mailexam.io |
Paths and request format are the same; only the host differs.
Authentication¶
Every request requires an API token from the Mailexam dashboard.
Pass the token as the query parameter token:
Secrets in CI/CD
Do not commit the token to your repository. Store it as MAILEXAM_API_TOKEN in GitLab CI, GitHub Actions, or your pipeline secrets.
| Status | Meaning |
|---|---|
200 / 201 |
Success |
403 |
Insufficient permissions or invalid token |
404 |
Object not found for the given UUID |
Typical CI/CD flow¶
- Send mail from your app via SMTP (see integration examples).
- Wait for delivery — usually 1–5 seconds.
- Fetch projects → inboxes → messages through the API.
- Find the message by subject or recipient and assert on content.
sequenceDiagram
participant App as Application
participant SMTP as Mailexam SMTP
participant API as REST API
participant Test as Automated test
App->>SMTP: sendMail()
Note over SMTP: Message in sandbox
Test->>API: GET /project
Test->>API: GET /inbox?project=...
Test->>API: GET /email?inbox=...
Test->>Test: assert subject, body
Endpoint reference¶
Projects (/project)¶
| Method | Path | Description |
|---|---|---|
GET |
/project |
List projects |
POST |
/project/store |
Create project |
GET |
/project/{uuid} |
Project details |
PUT |
/project/{uuid}/update |
Update project |
DELETE |
/project/{uuid}/delete |
Delete project |
Request body for create/update:
Sample response (ProjectResponse):
{
"uuid": "536a47df-5aad-44d0-8163-a39bb55abe0b",
"name": "My project",
"description": null,
"user": {
"uuid": "984f1f70-21e1-4244-bbb7-734ac151dcdc",
"name": "Mailexam Support",
"email": "support@mailexam.io"
}
}
Inboxes (/inbox)¶
| Method | Path | Description |
|---|---|---|
GET |
/inbox?project={uuid} |
List inboxes in a project |
POST |
/inbox/store |
Create inbox |
GET |
/inbox/{uuid} |
Inbox details |
PUT |
/inbox/{uuid}/default |
Set as default inbox |
PUT |
/inbox/{uuid}/update |
Update inbox |
DELETE |
/inbox/{uuid}/delete |
Delete inbox |
Create request body (InboxRequest):
{
"project_uuid": "536a47df-5aad-44d0-8163-a39bb55abe0b",
"name": "All messages",
"has_autorelay": false,
"relay_host": null,
"relay_port": null,
"relay_pass": null
}
Messages (/email)¶
| Method | Path | Description |
|---|---|---|
GET |
/email?inbox={uuid} |
List messages in an inbox |
GET |
/email/{uuid} |
Full message (body, headers) |
GET |
/email/{uuid}/attachment?cid={id} |
Attachment |
PUT |
/email/{uuid}/unread |
Mark unread/read |
PUT |
/email/{uuid}/like |
Like toggle |
PUT |
/email/{uuid}/dislike |
Dislike toggle |
PUT |
/email/{uuid}/relay |
Relay to SMTP server |
DELETE |
/email/{uuid}/delete |
Delete message |
Short message format (EmailShort):
{
"uuid": "e2f9a506-d766-4935-be11-c413384de020",
"from": "\"Mailexam\" <support@mailexam.io>",
"to": "Mailexam Support <support@mailexam.io>",
"cc": null,
"subject": "Mailexam connection parameters",
"human_size": "113 Bytes",
"date": "2025-03-01T08:58:49.000000Z",
"attachments": true,
"unread": false,
"like": true,
"dislike": false
}
Full format (EmailDetailed) also includes body (html, text, raw, attachments), headers, and relay.
Example: verify a message with curl¶
Environment variables:
Example: Python automated test¶
After SMTP send, find a message by subject and assert on body text.
import os
import time
import urllib.parse
import urllib.request
import json
API_BASE = os.environ.get("MAILEXAM_API_BASE", "https://mailexam.io/api/v1")
TOKEN = os.environ["MAILEXAM_API_TOKEN"]
PROJECT_UUID = os.environ["MAILEXAM_PROJECT_UUID"]
EXPECTED_SUBJECT = "CI check"
def api_get(path, params=None):
params = dict(params or {})
params["token"] = TOKEN
url = f"{API_BASE}{path}?{urllib.parse.urlencode(params)}"
with urllib.request.urlopen(url) as resp:
return json.loads(resp.read().decode())
def wait_for_email(subject, timeout=30, interval=2):
inboxes = api_get("/inbox", {"project": PROJECT_UUID})
inbox_uuid = inboxes[0]["uuid"]
deadline = time.time() + timeout
while time.time() < deadline:
emails = api_get("/email", {"inbox": inbox_uuid})
for item in emails:
if item["subject"] == subject:
return api_get(f"/email/{item['uuid']}")
time.sleep(interval)
raise TimeoutError(f"Message with subject «{subject}» not found in {timeout}s")
if __name__ == "__main__":
# ... send mail via your app's SMTP here ...
time.sleep(3)
email = wait_for_email(EXPECTED_SUBJECT)
body_text = (email.get("body") or {}).get("text") or ""
assert "Hello" in body_text, "Body mismatch"
print("OK")
Uses Python 3 stdlib only. Use requests or httpx if already in your project.
Example: Node.js automated test¶
const TOKEN = process.env.MAILEXAM_API_TOKEN;
const API_BASE = process.env.MAILEXAM_API_BASE || 'https://mailexam.io/api/v1';
const PROJECT_UUID = process.env.MAILEXAM_PROJECT_UUID;
const EXPECTED_SUBJECT = 'CI check';
async function apiGet(path, params = {}) {
const query = new URLSearchParams({ ...params, token: TOKEN });
const res = await fetch(`${API_BASE}${path}?${query}`);
if (!res.ok) throw new Error(`${res.status} ${path}`);
return res.json();
}
async function waitForEmail(subject, timeoutMs = 30000, intervalMs = 2000) {
const inboxes = await apiGet('/inbox', { project: PROJECT_UUID });
const inboxUuid = inboxes[0].uuid;
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const emails = await apiGet('/email', { inbox: inboxUuid });
const match = emails.find((item) => item.subject === subject);
if (match) return apiGet(`/email/${match.uuid}`);
await new Promise((r) => setTimeout(r, intervalMs));
}
throw new Error(`Message «${subject}» not found in ${timeoutMs / 1000}s`);
}
(async () => {
// ... SMTP send ...
await new Promise((r) => setTimeout(r, 3000));
const email = await waitForEmail(EXPECTED_SUBJECT);
const text = email.body?.text || '';
if (!text.includes('Hello')) throw new Error('Body mismatch');
console.log('OK');
})().catch((err) => {
console.error(err);
process.exit(1);
});
Requires Node.js 18+ (built-in fetch).
GitLab CI¶
integration_test:
stage: test
variables:
MAILEXAM_API_BASE: "https://mailexam.io/api/v1"
MAILEXAM_PROJECT_UUID: "536a47df-5aad-44d0-8163-a39bb55abe0b"
script:
- python send_and_verify.py
# MAILEXAM_API_TOKEN, MAILEXAM_LOGIN, MAILEXAM_PASSWORD — in CI/CD Variables
SMTP credentials (MAILEXAM_LOGIN, MAILEXAM_PASSWORD) and the API token are different values: the former for sending, the latter for reading via REST.
Mailtrap API comparison¶
When migrating from Mailtrap, replace Sandbox API calls:
| Task | Mailtrap | Mailexam |
|---|---|---|
| List messages | Sandbox API | GET /email?inbox={uuid} |
| Single message | Sandbox API | GET /email/{uuid} |
| Attachment | Sandbox API | GET /email/{uuid}/attachment?cid={id} |
| Manage sandbox | Sandboxes API | GET /project, /inbox |
Troubleshooting¶
403 Forbidden
- Ensure the token is complete and passed as
?token=.... - The token belongs to your account — verify the project is under the same user.
Empty message list
- The message may have landed in a different project or inbox — match the SMTP login to the correct project.
- Increase the pause after SMTP send (2–5 s) or use polling with a timeout.
Message visible in the dashboard but not in tests
- Filter by exact subject or check the latest messages by
datein the API response. - For attachments, fetch
EmailDetailedfirst, then download bycid.