Go from zero to a fully provisioned domain and mailboxes.This guide walks through the complete flow (i.e, creating a user, checking availability, placing an order, and handling the webhook).
Before You Start#
You'll also need a publicly reachable webhook URL to receive the order confirmation. If you're testing locally, use a tool like ngrok to expose a local port — or use the Sandbox to test the full flow without a live endpoint.
How the Order Flow Works#
Create user → Check availability → Place order
↓
{ actionId: "ACT_..." }
↓
10–90 seconds
↓
domain.order.success ✓
↓
mailbox.order.success ✓
↓
mailbox.created (one per mailbox)
The HTTP 200 on the order request confirms it was accepted — not completed. Wait for mailbox.created events before treating the order as done.
Step 1 — Create a User#
Every domain and mailbox in CMR belongs to a user. Create one first and store the returned id — you'll pass it as userId on every subsequent request for this customer.{
"status": 200,
"message": "User created",
"data": { "id": "usr_abc123" }
}
Step 2 — Check Domain Availability#
Always check availability before placing an order. Attempting to register an unavailable domain results in a failed order.{
"status": 200,
"data": {
"domain": "alicesender.com",
"available": true,
"price": 12.99
}
}
Step 3 — Place the Order#
{
"status": 200,
"message": "Order placed",
"data": { "actionId": "ACT_7a078272-0fa7-4db4-a85b-c78697dacea1" },
"actionId": "ACT_7a078272-0fa7-4db4-a85b-c78697dacea1"
}
Store the actionId — use it to match incoming webhooks to this order.Order Fields#
| Field | Required | Description |
|---|
domains | Yes | Array of domain names to register |
mailboxesPerDomain | Yes | Number of mailboxes to create on each domain |
billingCycle | Yes | MONTHLY or YEARLY |
firstName | Yes | Sender display name applied to every mailbox |
lastName | Yes | Sender display name applied to every mailbox |
Never resubmit an order while waiting for webhooks. Duplicates create duplicate domain registrations and charges with no automatic way to reverse them.
Step 4 — Handle the Webhooks#
A successful order produces a sequence of events — not a single webhook. Listen for all of them.domain.order.success#
Domain is registered. Mailbox provisioning begins immediately after.{
"event": "domain.order.success",
"eventId": "6SSHBXWR3N4JEKGRQSG3KW3D76N6",
"data": {
"userId": "usr_abc123",
"orderId": "NQ682GAVFTMYC2H6C87PPKPG5WXV",
"domain": "alicesender.com"
}
}
mailbox.created#
Fires once per mailbox when it's fully provisioned and ready. If you ordered 3 mailboxes, you receive 3 separate mailbox.created events.{
"event": "mailbox.created",
"eventId": "7TTIBYXS4O5KFLHSRTG4LX4E87O7",
"data": {
"userId": "usr_abc123",
"orderId": "NQ682GAVFTMYC2H6C87PPKPG5WXV",
"mailboxId": "mbx_001",
"email": "alice@alicesender.com"
}
}
domain.order.failed#
{
"event": "domain.order.failed",
"eventId": "8UUJCZYT5P6LGMITSTG5MY5F98P8",
"data": {
"userId": "usr_abc123",
"orderId": "NQ682GAVFTMYC2H6C87PPKPG5WXV",
"reason": "Workspace Validation"
}
}
Respond with HTTP 200 immediately when you receive any webhook — before processing. If your handler throws before responding, CMR will retry the delivery.
Adding Mailboxes to an Existing Domain#
Once you have an active domain, add more mailboxes without a new domain order:Same pattern — mailbox.created fires per mailbox when each is ready.
Sandbox Testing#
Test the full flow without real charges:Control the outcome via the cmr-x-sandbox-scenario header:| Header value | Behavior |
|---|
simulate:order-success | domain.order.success → mailbox.order.success → mailbox.created events fire |
simulate:order-domain-failed | domain.order.failed fires |
simulate:order-partial-failure | Some domains succeed, some fail |
See the full Sandbox API reference for every sandboxed endpoint and scenario key.
Next Steps#
Webhooks Overview
Signature verification, deduplication, retry handling, and the full event reference.
Authentication
Partner-level vs user-level auth in more detail.
Domains
Go deeper on domain search, forwarding, and status handling.