Skip to content

Guide

Send and read mail

The Mailbox API lets you send and read mail from the mailboxes you own on sending.ac. It speaks Microsoft Graph, so if you already have a Graph client you keep it — you change the base URL and the credential, and nothing else.

Mailbox APIv1alpha1Unstable
Base URL
https://api.customers.ac/api/mailbox/v1alpha1
Key
sk_live_…Scope mailbox, live only. Sandbox keys are refused.

You need a Mailbox key in the live environment. Create one under Production Credentials, choosing the Mailbox scope.

Every request goes to:

https://api.customers.ac/api/mailbox/v1alpha1

The Graph surface lives under /azure/v1.0. Authenticate with your key as a bearer token:

Authorization: Bearer sk_live_xxxxxxxx

Most Graph SDKs only attach the bearer token to hosts they trust. Pointed at another base URL they send the request unauthenticated, and you get a 401 that looks like a bad key. Tell the SDK to trust our host:

import { Client } from '@microsoft/microsoft-graph-client';
const client = Client.init({
baseUrl: 'https://api.customers.ac/api/mailbox/v1alpha1/azure',
defaultVersion: 'v1.0',
authProvider: (done) => done(null, 'sk_live_xxxxxxxx'),
// Required. Without this the SDK omits your key and every call returns 401.
customHosts: new Set(['api.customers.ac']),
});
var http = new HttpClient {
BaseAddress = new Uri("https://api.customers.ac/api/mailbox/v1alpha1/azure/v1.0")
};
// Allow the credential to be sent to our host, as customHosts does in the JS SDK.
var graph = new GraphServiceClient(http, new StaticTokenProvider("sk_live_xxxxxxxx"));

sendMail takes Graph’s own payload and returns 202 Accepted with an empty body once Microsoft accepts the message. Full request schema in the reference.

Terminal window
curl -X POST \
https://api.customers.ac/api/mailbox/v1alpha1/azure/v1.0/users/alice@acme.com/sendMail \
-H "Authorization: Bearer sk_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"message":{"subject":"Hi","body":{"contentType":"Text","content":"hello"},
"toRecipients":[{"emailAddress":{"address":"lead@example.com"}}]}}'
await client.api('/users/alice@acme.com/sendMail').post({
message: {
subject: 'Hi',
body: { contentType: 'Text', content: 'hello' },
toRecipients: [{ emailAddress: { address: 'lead@example.com' } }],
},
saveToSentItems: true,
});

List messages with the OData parameters you already use — $select, $filter, $top, $orderby — forwarded to Microsoft untouched.

const inbox = await client
.api('/users/alice@acme.com/messages')
.select('id,subject,from,receivedDateTime')
.top(25)
.get();
Terminal window
curl -G \
https://api.customers.ac/api/mailbox/v1alpha1/azure/v1.0/users/alice@acme.com/messages \
-H "Authorization: Bearer sk_live_xxxxxxxx" \
--data-urlencode '$select=id,subject,from,receivedDateTime' \
--data-urlencode '$top=25'

Always send $select. The default projection is large, and message bodies dominate the response. Every supported parameter is listed under List messages.

We rewrite @odata.nextLink to point back at us, so page iterators work unchanged and you never receive a raw graph.microsoft.com link that your key could not authenticate against.

import { PageIterator } from '@microsoft/microsoft-graph-client';
const first = await client.api('/users/alice@acme.com/messages').top(25).get();
await new PageIterator(client, first, (message) => {
console.log(message.subject);
return true;
}).iterate();

The proxy runs a strict allow-list. These four routes are reachable; anything else returns 404 even if it exists in Graph.

Method Path
POST /users/{email}/sendMail
GET /users/{email}/messages
GET /users/{email}/messages/{id}
GET /users/{email}/mailFolders

You can only address mailboxes belonging to your account. A mailbox that is not yours returns 404 rather than 403, so the API never confirms which addresses exist. A supported path with the wrong method returns 405.

Errors on the Graph surface come back in Microsoft’s own format, including errors we raise ourselves, so your existing Graph error handling keeps working.

{
"error": {
"code": "ResourceNotFound",
"message": "No such mailbox.",
"innerError": { "request-id": "", "date": "" }
}
}
Status Meaning
401 Missing or invalid API key, or the key was revoked.
403 Valid key, but not a live Mailbox key — a Provisioning or sandbox key lands here.
404 Unknown mailbox or path, including a mailbox that is not yours.
405 Supported path, unsupported method.
413 Request body above 10 MB.
429 Rate limit exceeded. See Retry-After.
502 We could not reach Microsoft. Ambiguous for sends.
503 Temporarily unavailable. Safe to retry with backoff.

60 requests per minute per API key. Going over returns 429 with Retry-After and X-RateLimit-* headers — wait the number of seconds given and try again. Microsoft’s own throttling is passed through with its headers intact, so your SDK’s built-in backoff keeps working.

Two things are planned and appear in the API reference so you can see them coming, but there is nothing to call today:

  • Events and webhooks — a feed of delivery and engagement activity across your mailboxes, plus push delivery. Until it ships, poll messages for the mailboxes you care about. Shape preview under Events.
  • Gmail — reserved under a /google/ prefix, which answers 501 so you can detect it deliberately. Only Microsoft 365 mailboxes work today.

When something goes wrong, quote the x-correlation-id response header — the same value appears as request-id inside our error bodies. It identifies the exact request, including Microsoft’s own id from x-ms-request-id, which is what we need to escalate on your behalf.