Skip to content

Send API

POST https://api.mailway.net/v1/send # production
POST https://api.mailway.dev/v1/send # sandbox — stored, never auto-forwarded
Authorization: Bearer mw_live_… # production keys — accepted only on api.mailway.net
Authorization: Bearer mw_test_… # test keys — drive the sandbox

Keys are minted per-project in the console and shown once — the key is the project selection, so there’s no project field in the request body. Full details (host binding, storage, rotation): Authentication & API keys.

Terminal window
curl https://api.mailway.net/v1/send \
-H "Authorization: Bearer $MAILWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Acme <no-reply@acme.com>",
"to": "user@example.com",
"subject": "Welcome to Acme",
"html": "<p>Hello!</p>",
"text": "Hello!"
}'
{
"id": "m.01k8w9abcdefghjkmnpqrstvw",
"status": "queued",
"scheduled_at": null,
"paused": false,
"project_id": "p.01k8w9…",
"routing": { "provider": null, "strategy": null }
}

id is the message’s permanent identifier — it threads through the console, webhook events, and the public viewer. status is queued, scheduled (when scheduled_at is in the future), or paused.

Field Type Notes
from address Required. "a@b.com", "Name <a@b.com>", or { "email": …, "name": … }.
to, cc, bcc address | address[] At least one recipient across the three, at most 50 combined. Each entry takes any address form.
reply_to address Optional.
subject string Required. ≤ 998 bytes (the RFC 5322 line limit).
html, text string At least one, ≤ 2 MB each. Send both when you can — some inboxes still want the text part.
headers object Custom headers (e.g. List-Unsubscribe). Transport-critical headers and the X-Mailway-* prefix are reserved and rejected.
attachments object[] ≤ 20, each { "filename", "content" (base64), "content_type", "content_id"?, "disposition"? } — 10 MB decoded each, 25 MB combined. content_id + "disposition": "inline" for inline images.
tags string[] ≤ 10 labels of ≤ 64 chars, echoed on every webhook event for this mail.
metadata object ≤ 10 keys — your correlators (order IDs, user IDs), echoed on every webhook event.
category string One per message — the reporting bucket your per-category stats group by (e.g. "Password reset"). ≤ 64 chars, letters/digits/space/_-./:, case-sensitive, echoed in the response. A project holds at most 100 distinct categories: reusing one is always free, a 101st new name is rejected (category:limit_reached). Use category for reporting and tags for correlation — different jobs.
scheduled_at ISO 8601 Defer the send — up to 72 hours ahead.
paused boolean Accept + store the message but hold it until released in the console.
routing.provider string | string[] Pin the send to a provider (or an ordered preference list) attached to the project, e.g. "amazon-ses-api".
routing.strategy string Override the project’s provider-selection strategy for this send: fallback, round-robin, random, or free-first.
provider_options object Provider-specific extras, allowlist-validated — e.g. { "configuration_set": … } (SES), { "message_stream": … } (Postmark). Unknown options are rejected, never silently dropped.

The full cap sheet — including rate limits and plan quotas — is on one page: Limits & quotas.

Pass an Idempotency-Key header (12–40 chars of [A-Za-z0-9_-]) to make retries safe:

Idempotency-Key: order-84412-receipt

A repeated key returns the original message’s 202 instead of sending again. Keys are unique per project and bounded by your retention window, not a 24-hour timer.

POST /v1/send/batch

Send up to 100 messages in one request — the body is an array where each element uses the exact /v1/send schema. Elements are independent sends: one failing validation doesn’t sink the rest, which is why the response is 207 Multi-Statusdata is an array in your input order, each element either the normal send object ({ "id": "m.…", "status": … }) or an error envelope for that message. Check per element; don’t assume all-or-nothing. Idempotency-Key is not honoured on batch, and each accepted element counts against your quotas.

Point the same code at https://api.mailway.dev with a mw_test_… key: messages are validated, stored, and visible in the console with a sandbox badge — but never forwarded automatically. The payload shape is identical, so promoting to production is a hostname + key swap — and if a specific capture turns out to be the real thing, you can release it from the console instead of re-running the send.

Status Meaning
401 Missing or invalid API key, or a mw_live_… key used outside api.mailway.net.
402 Plan quota exhausted — monthly message volume or throughput. The body’s error.code says which.
422 Validation failed — the body lists field-level errors, e.g. routing_provider_not_configured or an invalid Idempotency-Key.
429 Rate limited — back off and retry.

The full code catalog with handling advice is on the Errors page; when a send doesn’t behave the way you expect, troubleshooting maps the symptom to a fix.