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.
- Base URL
https://api.customers.ac/api/mailbox/v1alpha1- Key
sk_live_…Scope mailbox, live only. Sandbox keys are refused.
Before you start
Section titled “Before you start”You need a Mailbox key in the live environment. Create one under Production Credentials, choosing the Mailbox scope.
Base URL and auth
Section titled “Base URL and auth”Every request goes to:
https://api.customers.ac/api/mailbox/v1alpha1The Graph surface lives under /azure/v1.0. Authenticate with your key as a bearer token:
Authorization: Bearer sk_live_xxxxxxxxPoint your client at us
Section titled “Point your client at us”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"));Send an email
Section titled “Send an email”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.
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,});Read mail
Section titled “Read mail”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();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.
Paging
Section titled “Paging”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();What you can call
Section titled “What you can call”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
Section titled “Errors”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. |
Rate limits
Section titled “Rate limits”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.
Not available yet
Section titled “Not available yet”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
messagesfor the mailboxes you care about. Shape preview under Events. - Gmail — reserved under a
/google/prefix, which answers501so you can detect it deliberately. Only Microsoft 365 mailboxes work today.
Getting help
Section titled “Getting help”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.