API · v1

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.

AUTHAuthorization: Bearer mkt_live_…
# 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.

POST/v1/send
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.

POST/v1/send · X-MailOps-Sandbox: true
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.

POST/v1/contacts
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.

PATCH/v1/contacts/{id}
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.

DELETE/v1/contacts/{id}
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.

POST/v1/groups
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.

POST/v1/groups/{id}/members
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.

DELETE/v1/groups/{id}/members/{contact_id}
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.

GET/v1/export/{dataset}
# 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"
} }