Partner integrators · Any agent product

Email Services & API for Private Clients · API 0.7.0

Any agent product

A template for a product that sends with the Mail Service Now API. Order of work: authenticate, send, then read credits. The shared inbox@mailservicenow.com mailbox stays with Mail Service Now. A hosted mailbox on a verified domain opens in the web app.

Docs revision 2026-09-26 · API 0.7.0 · keys shown as msn_<your-key>

Auth

How to send. Mail leaves through mail.mailservicenow.com. You call HTTPS only. Always set from to an address on a verified domain you own, for example support@example-domain.com. Examples use msn_<your-key>. Never commit a live key.
export MSN_API_BASE=https://mailservicenow.com/api
export MSN_AGENT_API_KEY='msn_<your-key>'
CallerHeaderRoutes
Agent or product backend Authorization: Bearer msn_<your-key> or X-Api-Key: msn_<your-key> sendMail, inbox, mcp

Confirm the server before the first send. GET /mcp needs no key and lists send_mail, get_credits, list_inbox, get_message, mark_read. Register an inbound webhook with POST /webhooks (see Developers).

curl -sS "$MSN_API_BASE/mcp"

Send

Plain text. to, subject, and text are required. Always set from to an address on a verified domain you own, for example support@example-domain.com. If it's omitted and the account has exactly one sendable hosted mailbox, that mailbox is used as a fallback; with more than one, omitting from is an error. The shared agent key sends as noreply@mailservicenow.com.

REST

curl -sS -X POST \
  "$MSN_API_BASE/sendMail" \
  -H "Authorization: Bearer $MSN_AGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "recipient@example.com",
    "from": "support@example-domain.com",
    "subject": "Hello from an agent",
    "text": "Sent via MailServiceNow agent API."
  }'
{
  "ok": true,
  "messageId": "<id@mail.mailservicenow.com>",
  "creditsRemaining": 99
}

MCP

curl -sS -X POST "$MSN_API_BASE/mcp" \
  -H "Authorization: Bearer $MSN_AGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "send_mail",
      "arguments": {
        "to": "recipient@example.com",
        "from": "support@example-domain.com",
        "subject": "Hello from an agent",
        "text": "Sent via MailServiceNow MCP."
      }
    }
  }'

REST: require HTTP 200 and ok: true, then store messageId. MCP: require HTTP 200 and result.structuredContent.ok === true. result.isError: true is a failed tool call even when the HTTP status is 200. Auth failures stay HTTP 401.

Credits

  • One agent send costs 1 credit. The default key starts at 100. People signed in to the web app are not charged and do not get creditsRemaining.
  • Read the balance before a batch. get_credits and inbox reads are free.
  • The credit is taken after validation and before SMTP. A 400 or a rate-limit 429 does not spend one. An MTA failure does not refund it.
  • Zero credits: 429 Insufficient credits. Stop and ask Mail Service Now. Do not look for HTTP 402.
  • GET /credits shows remaining credits and the key’s last four characters. It is free. Signed-in Settings shows the same balance.
  • 503 Agent key disabled means that key is frozen. Do not spend a retry budget on it, and do not fail over to another ESP. 503 Sending temporarily disabled pauses every sender.
  • Burst or daily cap: 429 Rate limit exceeded. Limits are 5 per minute and 50 per UTC day for the shared agent bucket agent:default.
curl -sS -X POST "$MSN_API_BASE/mcp" \
  -H "Authorization: Bearer $MSN_AGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": { "name": "get_credits", "arguments": {} }
  }'

Tool result shape: { "ok": true, "remaining": 100 } inside structuredContent.

Optional inbox

Skip this step when the product only sends. The shared inbox@mailservicenow.com mailbox stays with Mail Service Now. It is not a customer mailbox. Do not tell a customer to write there.

  • Always set from to an address on a verified domain you own, for example support@example-domain.com. Mail to noreply@mailservicenow.com is not a customer mailbox.
  • List, then get. List rows have no body. Getting a message marks it read.
  • mailbox, when set, must be an @mailservicenow.com address. This is not a reader for a hosted mailbox on a customer domain. That mailbox opens in the web app.
  • A follow-up you send is a new message. Always set from to an address on a verified domain you own, for example support@example-domain.com. Pass optional inReplyTo and references to keep it on the thread. list_inbox stays a flat list.
curl -sS "$MSN_API_BASE/inbox?limit=25" \
  -H "Authorization: Bearer $MSN_AGENT_API_KEY"
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "list_inbox",
    "arguments": { "limit": 25, "mailbox": "inbox@mailservicenow.com" }
  }
}

Errors

What you seeDo
401 Invalid API key or missing credential Fix the header. Do not retry in a loop.
400 Fix to, subject, or text. Max 5 recipients, max 100KB.
429 Rate limit exceeded Back off. Same string on MCP via structuredContent.status.
429 Insufficient credits Stop the batch.
503 Sending temporarily disabled Stop. Do not fail over to another mail provider.
404 Message not found The inbox id is wrong. Listing again is free.

Contract: Developers, OpenAPI, Download .md.

How an agent requests access

No API key is required. The code is emailed and is not in the response. The first response does not say whether an account is already on file.

  1. Call request_access or POST /requestAccess with contactEmail, domain, agentName, and intendedUse.
  2. Wait for the email at contactEmail. The code is 8 digits and expires in 24 hours.
  3. Call verify_access_request or POST /verifyAccessRequest with requestId and code.
  4. status: "ready" includes access: "standard" and apiKey once. That key can call send, the domain and mailbox routes, webhooks, and the customer MCP tools. It does not add the domain or create mailboxes. Continue with Domain and mailboxes. Default quotas are 100 credits, 5 sends per minute, and 50 per UTC day. status: "setting_up" has no key. The message is "We're setting up your account. We'll email you when it's ready." An account already on file is left unchanged. If it can already send, the response is a standard key once. If setup is still in progress, the response is setting_up. Otherwise status: "unavailable" and the message is "This request could not be completed." There is no key.
  5. Store the key in a secret store. It is shown once. Send it as Authorization: Bearer msn_… or X-Api-Key: msn_…, the same way as any other msn_… key. If X-Api-Key is set, it is the only credential checked. Do not commit the key.

A standard key gets 403 on admin routes and on the admin MCP tools. request_access allows 5 requests per hour per IP and 3 per hour per email. 429 includes retryAfter seconds. Wrong code is 401. Five failures lock the code (401). An expired code is 410. An unknown requestId is 404. Completing the same request again is 409. Invalid input is 400.

curl

curl -sS -X POST "$MSN_API_BASE/requestAccess" \
  -H "Content-Type: application/json" \
  -d '{
    "contactEmail": "ops@example-domain.com",
    "domain": "example-domain.com",
    "agentName": "ExampleAgent",
    "intendedUse": "Send a notification from an agent"
  }'

curl -sS -X POST "$MSN_API_BASE/verifyAccessRequest" \
  -H "Content-Type: application/json" \
  -d '{"requestId":"ar_0123456789abcdef01234567","code":"12345678"}'

MCP JSON-RPC

curl -sS -X POST "$MSN_API_BASE/mcp" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "request_access",
      "arguments": {
        "contactEmail": "ops@example-domain.com",
        "domain": "example-domain.com",
        "agentName": "ExampleAgent",
        "intendedUse": "Send a notification from an agent"
      }
    }
  }'

Then call verify_access_request with requestId and code. Read structuredContent. status is ready or setting_up. Full contract: Markdown guide (How an agent requests access), llms.txt, OpenAPI.

After you have a standard key: set up your domain and mailboxes

DNS for each record, including DMARC, is in Domain setup / deliverability.

Use your hostname in place of example.com. A ready key does not add it. intendedUse does not create mailboxes. Field notes: Markdown guide.

  1. ready with msn_… means you can send and call domain routes. It does not add the domain or create mailboxes. setting_up has no key. Domain calls then return 403 Account setup is not complete yet.
  2. POST /addDomain with {"domain":"example.com"}, or Add domain in the web app. A first response of created: true and status: "failed" means DNS is not in place yet. Read dnsInstructions.records.
  3. Publish MX mail.mailservicenow.com (priority 10), one SPF TXT copied from the dashboard (SPF), and DKIM at mail._domainkey from the response. Moving MX off Cloudflare Email Routing, or any previous host, stops mail that host delivers today. Cut over when you mean to.
  4. POST /verifyDomain with {"domainId":"example.com"}, or Check DNS in the app. verified: true sets status to verified.
  5. POST /addMailbox for each localPart. The standard key works on REST and on MCP add_mailbox. A Firebase ID token also works. The shared agent key is refused with Firebase sign-in required. MCP add_domain adds the one domain.
  6. Wait about 60 seconds (mtaApplied). IMAP, SMTP, and webmail need a verified domain and mode hosted or both.
  7. Optional: webhooks, a proof send, credits. Send before that mailbox exists returns 403 Add a fully hosted mailbox on a verified domain before sending.

An admin pending row with storedStatus: "code_sent" (including an expired code) has decidable: false. It is not a mailbox hold. setting_up means the account is still being finished. After ready, do this list.

curl -sS -X POST "$MSN_API_BASE/addDomain" \
  -H "Authorization: Bearer msn_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{"domain":"example.com"}'

curl -sS -X POST "$MSN_API_BASE/verifyDomain" \
  -H "Authorization: Bearer msn_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{"domainId":"example.com"}'

curl -sS -X POST "$MSN_API_BASE/addMailbox" \
  -H "Authorization: Bearer msn_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{"domainId":"example.com","localPart":"orders","mode":"hosted"}'