How can we help? ๐Ÿ‘‹

Mailbox API v1alpha1

The Mailbox API lets you see and act on the email activity of your sending.ac mailboxes. You can list the emails your mailboxes send and receive, get notified in real time through webhooks, and send or read mail through the Microsoft Graph endpoints you already know.

This is an early preview (v1alpha1). Endpoints and fields can change. We will tell you before we make breaking changes.

Base URL

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

Authentication

Every request needs your API key as a bearer token:

Authorization: Bearer sk_sandbox_xxxxxxxx

Create a key from your dashboard at api.customers.ac, in the Mailbox API credentials section. You can create sandbox keys (sk_sandbox_...) for testing and production keys (sk_live_...) for live traffic. Both return your real data and use the same base URL. Keep your key secret. If it leaks, revoke it from the dashboard and create a new one. For an interactive reference, open the Mailbox API Reference page in the dashboard.

List email events

GET /events

Returns the individual emails your mailboxes sent and received, newest first, with aggregate counts included in the same response. This is the endpoint to poll when you are not using webhooks.

Time range

from and to accept either an absolute time (RFC 3339 or a Unix timestamp) or a relative expression (now-24h, now-7d, now). The default range is the last 24 hours. The maximum range is 7 days.

?from=now-24h&to=now
?from=2026-07-09T00:00:00Z&to=2026-07-10T00:00:00Z

Filters

Parameter
Values
filter[direction]
inbound or outbound
filter[classification]
one or more of the values below, comma separated
filter[domains]
one or more domain names or domain ids, comma separated

Classifications:

Value
Meaning
warmup
Automated warmup message.
warmup_reply
A reply inside the warmup network.
outreach
A message from your outreach campaigns.
reply
A genuine reply from a recipient.
other_inbound
Other received mail.
soft_bounce
Temporary delivery failure.
hard_bounce
Permanent delivery failure.
autoresponder
Automatic reply, such as out of office.
system
System or postmaster message.
placement_test
Inbox placement test message.

Pagination

Parameter
Meaning
page[size]
Results per page. Default 25, maximum 100.
page[after]
A cursor from a previous response.
order
desc (newest first, default) or asc (oldest first).

Example

curl "https://api.customers.ac/api/mailbox/v1alpha1/events?from=now-24h&filter[direction]=inbound&filter[classification]=reply&filter[domains]=example.com,acme.io&page[size]=25" \
  -H "Authorization: Bearer sk_sandbox_xxxxxxxx"

Response

{
  "data": [
    {
      "id": "evt_01J...",
      "type": "email.received",
      "direction": "inbound",
      "classification": "reply",
      "occurred_at": "2026-07-10T01:22:05Z",
      "domain": { "id": "019d...", "name": "example.com" },
      "mailbox": "alice@example.com",
      "message": {
        "internet_message_id": "<CAB...@mail.gmail.com>",
        "subject": "Re: your note",
        "from": "lead@prospect.com",
        "to": ["alice@example.com"],
        "received_at": "2026-07-10T01:22:03Z",
        "body": { "content_type": "html", "content": "<the full message body>" }
      },
      "cursor": "eyJ0IjoiMjAyNi0..."
    }
  ],
  "summary": {
    "window": { "from": "2026-07-09T01:23:00Z", "to": "2026-07-10T01:23:00Z" },
    "totals": { "inbound": 812, "outbound": 3140 },
    "by_classification": { "warmup": 3020, "reply": 34, "hard_bounce": 12, "outreach": 61 },
    "by_domain": [
      { "id": "019d...", "domain": "example.com", "inbound": 812, "outbound": 3140 }
    ]
  },
  "links": { "self": "https://api.customers.ac/api/mailbox/v1alpha1/events?from=2026-07-09T01:23:00Z&to=2026-07-10T01:23:00Z" },
  "pagination": { "has_more": true, "next_cursor": "eyJ0IjoiMjAyNi0..." }
}

The full message body is included in every event. summary counts the full filtered range, not just the current page.

Poll for new events

To watch for new email, do not re-request a moving time window. Instead, page forward from the last event you saw:

  1. Request with order=asc.
  1. Store the next_cursor from the response.
  1. On the next poll, pass it as page[after]. You receive only events newer than that cursor.
curl "https://api.customers.ac/api/mailbox/v1alpha1/events?order=asc&page[after]=eyJ0IjoiMjAyNi0..." \
  -H "Authorization: Bearer sk_sandbox_xxxxxxxx"

Poll about once per minute. This path is deterministic and cache friendly, so it stays fast and does not count against you as heavily.

Webhooks

Register a URL and we call it when a matching event happens on your domains. This is the preferred way to react to received email.

Register

curl -X POST https://api.customers.ac/api/mailbox/v1alpha1/webhooks \
  -H "Authorization: Bearer sk_sandbox_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/hooks/sending",
    "domains": ["example.com", "acme.io"],
    "events": ["email.received", "email.sent"],
    "secret": "whsec_your_shared_secret"
  }'
Field
Meaning
url
Where we send the POST.
domains
One or more domain names or ids to watch.
events
email.received, email.sent, or both.
secret
Used to sign each delivery. If you omit it, we generate one and return it once.

Manage subscriptions with GET /webhooks, GET /webhooks/{id}, and DELETE /webhooks/{id}.

Delivery payload

We POST a body that wraps the same event object the events endpoint returns:

{
  "id": "whd_01J...",
  "type": "email.received",
  "created_at": "2026-07-10T01:22:06Z",
  "data": { "id": "evt_01J...", "type": "email.received", "direction": "inbound", "...": "same shape as an /events item" }
}

Because the data object matches an /events item, one handler can process both webhook deliveries and polled events.

Verify the signature

Every delivery includes an X-Sending-Signature header:

X-Sending-Signature: sha256=<hex hmac of the raw request body, keyed with your secret>

Compute the HMAC SHA-256 of the raw body with your secret and compare it in constant time. Reject the request if it does not match.

import crypto from "node:crypto";

function verify(rawBody, header, secret) {
  const expected = "sha256=" +
    crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}

Send and read mail

The Mailbox API exposes the Microsoft Graph endpoints you need to send and read mail on your mailboxes. Point your existing Microsoft Graph client at our base URL and use your sending.ac key as a static token. Your code stays the same.

Base URL: https://api.customers.ac/api/mailbox/v1alpha1/azure/v1.0

Auth: Authorization: Bearer sk_sandbox_xxxxxxxx

You change two things: the base URL, and the credential (your sending.ac key instead of a Microsoft token). Paths, query parameters, and request and response bodies are native Microsoft Graph.

Supported endpoints:

Method
Path
POST
/users/{email}/sendMail
GET
/users/{email}/messages (supports $select, $filter, $top)
GET
/users/{email}/messages/{id}
GET
/users/{email}/mailFolders

You can only address mailboxes that belong to your account. Other Graph paths return 404.

curl

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

Microsoft Graph JavaScript SDK

import { Client } from "@microsoft/microsoft-graph-client";

const client = Client.init({
  baseURL: "https://api.customers.ac/api/mailbox/v1alpha1/azure/v1.0",
  authProvider: (done) => done(null, "sk_sandbox_xxxxxxxx"),
});

await client.api("/users/alice@acme.com/sendMail").post({ message: { /* ... */ } });

const inbox = await client.api("/users/alice@acme.com/messages")
  .select("subject,from,receivedDateTime").top(25).get();

Microsoft Graph .NET SDK

var http = new HttpClient {
    BaseAddress = new Uri("https://api.customers.ac/api/mailbox/v1alpha1/azure/v1.0")
};
var graph = new GraphServiceClient(http, new StaticTokenProvider("sk_sandbox_xxxxxxxx"));

await graph.Users["alice@acme.com"].SendMail.PostAsync(new() { Message = msg });

Gmail support is planned under a separate /google/ prefix and is not available yet.

Rate limits and caching

The events endpoint allows 10 requests per minute per key. If you exceed it you get 429 with a Retry-After header. Wait for the number of seconds in that header and try again.

Responses can be reused for up to a minute. Each response carries an ETag and a Cache-Control header. Send the ETag back on your next request as If-None-Match. If nothing changed, you get 304 Not Modified with no body, which is faster and cheaper for both sides. For live updates, prefer the cursor polling described above over re-querying a moving window.

Errors

Errors use a consistent JSON shape:

{ "error": { "code": "rate.quota_exceeded", "message": "Too many requests. Retry after the number of seconds in Retry-After." } }
Status
Meaning
401
Missing or invalid API key.
403
The key is valid but not allowed to access this resource.
404
Unknown mailbox, path, or resource.
422
A parameter failed validation. The response lists the fields.
429
Rate limit exceeded. See Retry-After.

Support

If something is not working or you need access you do not have, contact your Deliverability Advisor.

Did this answer your question?
๐Ÿ˜ž
๐Ÿ˜
๐Ÿคฉ