Built for trusted integrations
The Trainmail API is a server-to-server API. Every key is bound to one primary Trainmail mailbox and approved manually by support. A provisioning key may also access only the mailboxes created by that same key—never arbitrary Trainmail accounts.
Request access
Tell us what you are building, why mailbox access is required, which permissions you need, and the expected request volume. Support reviews every application before issuing a key.
What makes a good application
Use a Trainmail address you control. Describe the concrete workflow, how you will store the secret, and whether the integration reads mail, changes mailbox state, or sends messages.
Authentication
Send the API key in the Authorization header on every protected request. Keys use the tm_live_ prefix and are shown only once when issued.
Authorization: Bearer tm_live_your_key
Content-Type: application/jsonScopes
Support grants only the permissions your integration needs. Requests outside the approved scope return 403 insufficient_scope.
account:readRead mailbox identity and active key scopes.mail:readList folders, search mail, and retrieve messages.mail:writeMark messages, move them, archive, and delete.mail:sendSend email from the bound Trainmail mailbox.mailbox:createCreate a limited number of new Trainmail mailboxes for an approved server-side workflow.Quickstart
Start with the account endpoint to verify authentication and inspect the granted scopes.
curl https://trainmail.online/api/v1/account \ -H "Authorization: Bearer $TRAINMAIL_API_KEY"
Then fetch the newest inbox messages. Lists use cursor pagination and return at most 50 items per request.
const response = await fetch( "https://trainmail.online/api/v1/messages?folder=inbox&limit=20", { headers: { Authorization: `Bearer ${process.env.TRAINMAIL_API_KEY}` } } ); const page = await response.json(); console.log(page.data, page.pagination.next_cursor);
Create mailboxes
Keys approved for provisioning can create one real @trainmail.online mailbox per request. The password is generated by Trainmail and returned only in the first successful response, so store it immediately in a secure server-side secret store. You can attach private integration metadata and optionally copy every incoming message to an owned Trainmail collector mailbox.
Create one mailbox
curl -X POST https://trainmail.online/api/v1/mailboxes \ -H "Authorization: Bearer $TRAINMAIL_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: project-user-001" \ -d '{"username":"project-user-001","metadata":{"external_id":"user-001","batch":"august"},"forward_to":"collector@trainmail.online"}'
Omit username to receive a generated address. Reusing the same Idempotency-Key never creates a second mailbox; a replay returns the address without returning the password again. metadata is a private JSON object up to 4 KB. forward_to must be the key's primary mailbox or another mailbox created by the same key. Forwarding uses copy mode, so the original message remains in the source inbox.
Small Node.js provisioning script
import { randomUUID } from "node:crypto"; const usernames = ["project-one", "project-two"]; for (const username of usernames) { const response = await fetch("https://trainmail.online/api/v1/mailboxes", { method: "POST", headers: { Authorization: `Bearer ${process.env.TRAINMAIL_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": randomUUID() }, body: JSON.stringify({ username }) }); const mailbox = await response.json(); if (!response.ok) throw new Error(JSON.stringify(mailbox)); console.log(mailbox.email, mailbox.temporary_password); }
Manage many mailboxes
A key with mailbox:create can list every mailbox it created. A key with mail:read can then read any of those mailboxes by adding the mailbox query parameter. No mailbox password is sent to the API.
curl "https://trainmail.online/api/v1/mailboxes?limit=100" \ -H "Authorization: Bearer $TRAINMAIL_API_KEY"
curl "https://trainmail.online/api/v1/messages?mailbox=project-user-001%40trainmail.online&folder=inbox&limit=20" \ -H "Authorization: Bearer $TRAINMAIL_API_KEY"
The same selector works for /folders, message details, read state, move, and delete endpoints. You may use X-Trainmail-Mailbox instead of the query parameter. If both are present, they must match.
Update metadata or forwarding
curl -X PATCH "https://trainmail.online/api/v1/mailboxes/project-user-001%40trainmail.online" \ -H "Authorization: Bearer $TRAINMAIL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"metadata":{"external_id":"user-001","status":"active"},"forward_to":"collector@trainmail.online"}'
Send "forward_to": null to disable forwarding. Trainmail rejects self-forwarding, forwarding loops, external destinations, and addresses outside the key's ownership boundary.
Pagination & server-side search
Message lists are ordered newest first. Pass the opaque next_cursor value back as cursor; do not parse or construct cursors yourself.
GET /api/v1/messages?folder=inbox&limit=20&cursor=eyJiZWZvcmVfdWlkIjo...
Add q to search message subject, sender, and content on the mail server rather than downloading the entire mailbox.
GET /api/v1/messages?folder=inbox&q=invoice&limit=20
Rate limits
Every key has an individual per-minute limit. The default is 60 requests per minute. Sending is additionally protected by a daily quota, usually 100 messages per day. Mailbox provisioning has a separate default allowance of 3 per day and 10 total. Support can approve different limits for a justified use case.
X-RateLimit-Limit: 60 X-RateLimit-Remaining: 42 X-RateLimit-Reset: 1785859200
When a limit is exceeded, the API responds with 429 and a Retry-After header. Use exponential backoff with jitter; never retry in a tight loop.
Endpoints
All responses are JSON. Message IDs are mailbox UIDs and must be used with the same folder supplied when reading the message.
GET/healthPublic
Returns API availability, version, and server time. No API key is required.
GET/accountaccount:read
Returns the mailbox identity and scopes attached to the current key.
GET/mailboxesmailbox:create
Lists mailboxes created by this key with their metadata, forwarding state, and cursor pagination.
POST/mailboxesmailbox:create
Creates one Trainmail mailbox. The Idempotency-Key header is required and the generated temporary password is returned once.
{
"object": "mailbox",
"email": "project-user-001@trainmail.online",
"temporary_password": "returned-once",
"limits": { "remaining_today": 2, "remaining_total": 9 }
}GET/mailboxes/{email}mailbox:create
Returns one mailbox created by this key. URL-encode the email address.
PATCH/mailboxes/{email}mailbox:create
Replaces metadata and/or changes internal forwarding. Send forward_to: null to disable forwarding.
GET/foldersmail:read
Lists supported mailbox folders that currently exist.
GET/messagesmail:read
Lists or searches messages without downloading full bodies.
X-Trainmail-Mailbox is also accepted.GET/messages/{id}mail:read
Returns message metadata, text, HTML when available, and attachment metadata. Pass ?folder=inbox when needed.
PATCH/messages/{id}mail:write
Updates message state.
{ "read": true }POST/messages/{id}/movemail:write
Moves a message between supported folders.
{ "from": "inbox", "to": "archive" }DELETE/messages/{id}mail:write
Moves a message to Trash. Calling DELETE for a message already in Trash permanently removes it.
POST/sendmail:send
Sends a message from the mailbox bound to the key. The sender address cannot be overridden.
{
"to": ["recipient@example.com"],
"subject": "Hello from Trainmail",
"text": "A plain-text fallback",
"html": "<p>A refined email.</p>"
}Up to 25 combined To, CC, and BCC recipients are accepted. Attachments are intentionally not available in v1.
Errors
Errors use a stable machine-readable code and include a request ID for support. Internal mailbox and server details are never returned.
{
"error": {
"type": "rate_limit_error",
"code": "rate_limit_exceeded",
"message": "The API key rate limit has been exceeded."
},
"request_id": "req_..."
}| Status | Meaning | Action |
|---|---|---|
400 | Invalid request | Correct parameters, headers, or JSON. |
401 | Invalid or revoked key | Check the Authorization header. |
403 | Insufficient scope or allowance | Request only the permission you need or contact support. |
404 | Resource not found | Verify message ID and folder. |
409 | Conflict | Use another mailbox name or a new idempotency key. |
429 | Rate limit exceeded | Respect Retry-After and back off. |
503 | Mail service unavailable | Retry later with exponential backoff. |
Security model
Keys are generated from cryptographically secure random bytes and stored by Trainmail only as SHA-256 hashes. A full key is delivered once to the approved Trainmail mailbox. Every request is rate-limited and recorded without request bodies or message content.
