API documentation
Everything you need to integrate: access tokens, sending mail, sandbox testing, contacts and groups. Base URL https://api.mailops.internal/v1
1.Access & authentication
All API calls authenticate with a per-app token in the Authorization header. Create tokens on the API Tokens page — each token is scoped (send, contacts, groups, read) and rate-limited per app.
# every request carries the app token curl https://api.mailops.internal/v1/me \ -H "Authorization: Bearer mkt_live_9f2c…" # → 200 OK { "app": "web-storefront", "scopes": ["send", "contacts"], "rate_limit": "600/min" }
Tokens are shown once at creation. Keep them server-side; never embed them in client code or emails.
2.Send email
Send a single message or a small batch. The relay picks a vendor from your routing rules and returns a message id you can track in the message explorer.
curl -X POST https://api.mailops.internal/v1/send \ -H "Authorization: Bearer mkt_live_9f2c…" \ -H "Content-Type: application/json" \ -d '{ "from": "Acme <hello@acme.com>", "to": ["jane@example.com"], "subject": "Your order is on the way", "html": "<p>Hi Jane…</p>", "tags": ["order-confirmation"] }' # → 202 Accepted { "id": "msg_9f2c1e", "status": "queued", "vendor": "aws-ses" }
Idempotency: pass a unique Idempotency-Key header to safely retry without double-sending.
3.Sandbox sends (test mode)
Send against the sandbox to exercise the full pipeline — validation, routing, webhook events — without delivering anything. Sandbox messages are flagged in the explorer and never touch a real recipient or your reputation.
curl -X POST https://api.mailops.internal/v1/send \ -H "Authorization: Bearer mkt_live_9f2c…" \ -H "X-MailOps-Sandbox: true" \ -H "Content-Type: application/json" \ -d '{ "from": "Acme <hello@acme.com>", "to": ["bounce@sandbox.mailops.dev"], "subject": "Sandbox test", "html": "<p>Nothing is really sent.</p>" }' # → 202 Accepted · simulated bounce webhook follows in ~2s { "id": "msg_sb_44d1", "status": "sandboxed" }
Sandbox sends still emit delivered / bounced webhook events against your test endpoints, so you can develop handlers end-to-end. Use to: "bounce@sandbox.mailops.dev" to simulate a hard bounce.
4.Contacts — add, update, delete
Contacts power campaigns and newsletters. Each contact carries an email, optional name/attributes, and is automatically checked against the global suppression list.
curl -X POST https://api.mailops.internal/v1/contacts \ -H "Authorization: Bearer mkt_live_9f2c…" -H "Content-Type: application/json" \ -d '{ "email": "jane@example.com", "name": "Jane Doe", "attributes": { "plan": "api", "country": "DE" } }' # → 201 Created { "id": "ct_8a41", "email": "jane@example.com", "suppressed": false }
Add a contact. Duplicate emails are rejected with 409 conflict — use PATCH to update instead.
curl -X PATCH https://api.mailops.internal/v1/contacts/ct_8a41 \ -H "Authorization: Bearer mkt_live_9f2c…" -H "Content-Type: application/json" \ -d '{ "attributes": { "plan": "web" } }' # → 200 OK { "id": "ct_8a41", "attributes": { "plan": "web", "country": "DE" } }
Update name or attributes. Attributes merge — send only what changes. Set "suppressed": true to honor an opt-out immediately.
curl -X DELETE "https://api.mailops.internal/v1/contacts/ct_8a41?forget=true" \ -H "Authorization: Bearer mkt_live_9f2c…" # → 204 No Content · address suppressed, history erased
Delete a contact. Deletion also adds the address to the suppression list so no campaign can mail it again — pass ?forget=true for a GDPR erasure (removes events and engagement history too).
5.Groups — organize your audience
Groups map to customer segments (API customers, Web customers, VIP…). Campaigns and newsletters target groups; a contact can belong to several.
curl -X POST https://api.mailops.internal/v1/groups \ -H "Authorization: Bearer mkt_live_9f2c…" -H "Content-Type: application/json" \ -d '{ "name": "API customers", "key": "api-customers" }' # → 201 Created { "id": "grp_api", "name": "API customers", "members": 0 }
Create a group, then add contacts to it.
curl -X POST https://api.mailops.internal/v1/groups/grp_api/members \ -H "Authorization: Bearer mkt_live_9f2c…" -H "Content-Type: application/json" \ -d '{ "contact_ids": ["ct_8a41", "ct_2f90"] }' # → 200 OK { "added": 2, "members": 1240 }
Add contacts to a group (bulk-friendly — up to 500 ids per call). Already-member ids are ignored.
curl -X DELETE https://api.mailops.internal/v1/groups/grp_api/members/ct_8a41 \ -H "Authorization: Bearer mkt_live_9f2c…" # → 204 No Content
Remove a contact from a group. The contact itself is kept; pass a campaign guard by combining with suppression if they opted out entirely.
6.Data export (API only)
The dashboard has no export or download controls — pulling data out of MailOps happens exclusively through the API, with a token carrying the read scope. Every export call is recorded in the audit log with the actor and filters used.
# datasets: messages · events · contacts · suppressions · analytics · audit · invoices curl "https://api.mailops.internal/v1/export/messages?from=2026-08-01&to=2026-08-31&format=csv" \ -H "Authorization: Bearer mkt_live_9f2c…" # → 200 OK · streamed CSV or JSON, one row per record
Large exports stream and may be split across pages — follow the Link: …rel="next" header. Audit and invoice exports are admin-scoped and include a X-Export-Id you can reference in compliance evidence.
✳.Error format
Errors are JSON with a machine-readable code. Rate limits return 429 with a Retry-After header.
{ "error": {
"code": "recipient_suppressed",
"message": "jane@example.com is on the global suppression list",
"doc": "https://mailops.internal/docs#contacts"
} }