Mailboxes
Order Google mailboxes on a domain in one call, and sign in with OAuth or an app password.
ResellerHQ sets up whole Google mailboxes for your customers: the Google account, the domain's mail DNS (MX, SPF, DKIM and DMARC) and two-step verification. You get each mailbox back with its sign-in details once it is ready.
It works in two steps:
- Order mailboxes on a domain, up to ten per domain. Name the domain and the mailboxes in one call. There is nothing to connect first.
- Read each mailbox's sign-in details once it is
active, and connect to it in one of two ways.
A domain bought here needs nothing else. For a domain registered elsewhere, you also set its nameservers at its registrar.
Mailboxes can't be ordered by a sub-reseller yet: those calls answer 422 not_available.
Sandbox
In the sandbox, nothing is created at Google. Domains connect and mailboxes come up within
seconds, and their sign-in details are realistic but don't work anywhere. A domain starting
with taken- behaves like one already used with Google Workspace. On a domain starting with
fails-, every mailbox fails during setup.
Price
curl https://resellerhq.com/api/v1/mailboxes/price \
-H "Authorization: Bearer $RHQ_API_KEY"price is what you pay per mailbox per month, in US cents, and perDomain is how many
mailboxes a domain takes. A domain keeps the price it was connected at, for every mailbox on
it, for as long as you keep it.
To fix what a customer pays per mailbox on one domain, send customerPrice to
PATCH /v1/mailbox-domains/{id}. Otherwise their price list sets it, under the mailbox
product. See Customer pricing.
Order mailboxes
POST /v1/mailboxes takes the domain and the mailboxes you want. Every mailbox is a sale to
the domain's customer: its first month once it is active, then each renewal.
A domain bought here: one call, nothing to do
Send the domain's domainId (or its name) and the mailboxes:
curl -X POST https://resellerhq.com/api/v1/mailboxes \
-H "Authorization: Bearer $RHQ_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "domainId": "dom_01K6...",
"mailboxes": [
{ "username": "jo", "firstName": "Jo", "lastName": "Bloggs" },
{ "username": "sam", "firstName": "Sam", "lastName": "Lee" }
] }'The answer has the mailboxes in data, and the domain they are on in mailboxDomain. We do
the rest:
- Nameservers. We point the domain at its mail nameservers ourselves.
mailboxDomain.nameserversManagedistrueand itsstatusispropagating. It is neverawaiting_nameservers. - Existing records. Its website, verification and other records are copied over, so nothing it serves stops. Its old MX, SPF, DKIM and DMARC records are not copied, because mail sets its own.
- Customer.
customerIddefaults to the customer the domain was bought for. - Slow nameservers. If the domain is slow to move, we ask for fresh nameservers and point
it again ourselves. We do this up to three times before the domain is
failed.
If you pointed the domain's DNS somewhere else yourself (your own nameservers), we can't copy
its records from there, so the order is refused with domain_dns_elsewhere. Send
"moveNameservers": true to move it anyway, then set its records again under
DNS.
A domain registered elsewhere: set its nameservers
A domain you don't hold here is checked with the registrar first. If it is already
registered, whether to you at another registrar or to someone else, send its name and a
customerId:
curl -X POST https://resellerhq.com/api/v1/mailboxes \
-H "Authorization: Bearer $RHQ_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "domain": "northwind-mail.com", "customerId": "cus_01K6...",
"mailboxes": [{ "username": "jo", "firstName": "Jo", "lastName": "Bloggs" }] }'mailboxDomain.status is awaiting_nameservers and nameserversManaged is false. Set
mailboxDomain.nameservers at the domain's registrar. The mailboxes wait as queued, paid
for, and are set up once the domain points there. That usually takes minutes and can take up
to a day. When it does, the domain becomes active and we send mailbox_domain.active.
If it never points there in time, it becomes failed. Call
POST /v1/mailbox-domains/{id}/nameservers/regenerate, set the new nameservers, and we start
checking again.
To get the nameservers before buying anything, connect the domain on its own first with
POST /v1/mailbox-domains (free), then order on it later the same way.
A domain nobody has registered
When the registrar says the domain isn't registered anywhere, send "buy": true to register
it (one year, to the customer's registrant or your account default) before the mailboxes are
ordered:
curl -X POST https://resellerhq.com/api/v1/mailboxes \
-H "Authorization: Bearer $RHQ_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "domain": "northwind-mail.com", "customerId": "cus_01K6...", "buy": true,
"mailboxes": [{ "username": "jo", "firstName": "Jo", "lastName": "Bloggs" }] }'registeredDomainId in the response is the domain just bought. It waits as awaiting_domain
while the registration finishes, its mailboxes paid for, then connects itself
(nameserversManaged: true) exactly like a domain you already held.
Without buy: true, that same request is refused with 422 domain_not_registered: nothing is
connected, held or charged. A premium name is never bought here, with or without buy
(422 premium_domain). Already registered somewhere, by the time buy: true tries it, answers
409 domain_taken. An ending we don't sell can't be asked whether it's registered either, so
it connects as above, and buy: true on it answers 422 ending_not_sold. If the registrar
can't be reached, this answers 502 check_failed and nothing is held; retry. If the domain
was bought but the mailbox order then fails, the error's own message says so, and
registeredDomainId names it in details too: it stays the customer's, nothing is charged
twice.
POST /v1/mailboxes/bulk and the dashboard already worked this way; this section just brings
the single-order route in line with them.
Every order
- Adding more. Send
POST /v1/mailboxesagain with the same domain, orPOST /v1/mailbox-domains/{id}/mailboxes. A domain takes 10 mailboxes in all; more is refused withmailbox_limit. - The first mailbox on a domain administers its Google account. Keep it: OAuth client IDs are granted through it.
- Setup. Each mailbox is
provisioningwhile it is set up, usually within 10 to 20 minutes and sometimes an hour or more.setupStagesays how far it has got. When it is ready it becomesactiveand we sendmailbox.active. - Payment. Your wallet is checked for every mailbox first, so if it is short the order is
refused with
insufficient_fundsand nothing changes. Each mailbox's first month is held when you order, but only taken once that mailbox isactive. A mailbox that fails (mailbox.failed, with afailurereason) costs nothing, and its money goes back to your wallet.
A domain already used with Google Workspace in another account is refused with
google_workspace_unavailable. Remove it from that account first.
Several domains at once
POST /v1/mailbox-domains/check takes up to 50 domain names and says, for each, whether it is
already connected (and its room for more mailboxes), held here, available to buy (with
its price), registered elsewhere, or unusable, with a reason.
POST /v1/mailboxes/bulk then orders on up to 50 domains in one call, for one customerId.
Send "buy": true on a domain the check called available to register it for a year first.
Your wallet is checked for everything up front. After that each domain succeeds or fails on
its own, and results says which. A domain still being registered comes back
awaiting_domain, with its mailboxes paid for and set up once the registration finishes.
DNS once mailboxes are on it
Once mailboxes are on a domain, its DNS is hosted with them. Change records under
/v1/mailbox-domains/{id}/dns/records, not under /v1/domains: those routes answer
dns_managed_by_mailboxes for this domain. Its nameservers follow the same rule from the other
side: PATCH /v1/domains/{id} refuses to move them away from the mail provider's own with 409 nameservers_used_by_mailboxes, naming the domain and its billable mailbox count, unless the
same call also sends acknowledgeMailboxes: true - setting them back to what the mail provider
already uses needs no flag. DNSSEC is refused outright, with 409 dnssec_not_available: there
is nothing to sign while the zone is theirs.
Sign-in details
curl -X POST https://resellerhq.com/api/v1/mailboxes/mbx_01K6.../credentials \
-H "Authorization: Bearer $RHQ_API_KEY"This answers with:
password, the Google account password;appPassword, for IMAP and SMTP;totpSecret, the seed of the account's two-step verification;- the Gmail IMAP and SMTP settings.
Sign-in details are stored encrypted and appear on this route only. They are never in a list,
a webhook or a log. Each time they are revealed, it is recorded in your
activity log. Reading them needs the mailbox.credentials permission, and an
API key needs at least the manage level.
When Google asks for a code at sign-in, POST /v1/mailboxes/{id}/totp gives you the current
one. You can also generate it yourself from totpSecret in any authenticator app.
To change a mailbox's name, username (and so its address), picture or password, use
PATCH /v1/mailboxes/{id}. A new password is 12 to 100 characters. To upload a profile
photo instead of linking one, use POST /v1/mailboxes/{id}/picture.
Two ways to connect
Pick the one that fits your tool. Both work on every mailbox.
1. OAuth with a client ID
This is best for apps that already sign in with Google: CRMs, sequencers, your own app. Grant your app's Google OAuth client on the domain, and its mailboxes can then sign in to it without Google blocking an unknown app.
curl -X POST https://resellerhq.com/api/v1/mailbox-domains/mdm_01K6.../client-ids \
-H "Authorization: Bearer $RHQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "clientId": "102938475610293847561" }'- Trusted app (the default). Each mailbox still signs in through your app's OAuth screen, once, with its password and code.
- Domain-wide delegation (
"delegated": true, withscopes). Your app (a Google service account) can act as any mailbox on the domain, with no sign-in per mailbox. Scopes are full URLs, such ashttps://mail.google.com/.
The request takes a few minutes. We send client_id.completed or client_id.failed, and
GET /v1/mailbox-domains/{id}/client-ids lists every request with its status. The domain's
first (admin) mailbox must be active first.
2. App password over IMAP and SMTP
Use this for any mail client or tool that takes a server, a username and a password. Use the app password, not the account password: Google refuses the account password over IMAP and SMTP.
| Host | Port | Security | |
|---|---|---|---|
| IMAP (receive) | imap.gmail.com | 993 | SSL/TLS |
| SMTP (send) | smtp.gmail.com | 587 | STARTTLS |
The username is the full email address.
DNS
The domain's DNS is hosted with its mailboxes:
GET /v1/mailbox-domains/{id}/dns/recordslists the records.POSTadds records, andPATCHandDELETEon/dns/records/{recordId}change or remove one.
Mail depends on some records: MX, the domain's SPF, its DKIM keys, its DMARC policy and its
nameservers. These are marked protected, and changing them is refused with
record_protected. Set DMARC through its own route instead.
POST /v1/mailbox-domains/{id}/dns/check says whether every mail record is in place.
POST /v1/mailbox-domains/{id}/dns/repair puts back any that are missing or wrong.
Website forwarding and DMARC
PUT /v1/mailbox-domains/{id}/forwardingwith{ "url": "https://northwind.com" }redirects the domain's website.{ "url": null }removes the redirect.PUT /v1/mailbox-domains/{id}/dmarcwith{ "reportingEmail": "...", "policy": "quarantine" }sets the DMARC policy.DELETEremoves it.
Renewal and deleting
Renewal. Every active mailbox on a domain renews together, monthly, on the date the
domain's first mailbox was ordered (periodEndsAt). A mailbox added mid-month is charged a
full first month and then renews with the rest.
Unpaid renewals. If your wallet cannot pay a renewal, we send
mailbox_domain.renewal_failed with deletesAt and try again. If it is still unpaid three
days later, the domain is deleted with all its mailboxes.
Deleting. A single mailbox cannot be deleted. Delete the whole domain:
curl -X DELETE https://resellerhq.com/api/v1/mailbox-domains/mdm_01K6... \
-H "Authorization: Bearer $RHQ_API_KEY"Every mailbox on it is cancelled at Google and stops renewing, and we send
mailbox_domain.deleted. Nothing already paid is refunded. Money held for mailboxes not yet
set up goes back to your wallet. A domain you hold here keeps its nameservers; change them on
the domain if you want its DNS back here.
Webhooks
Every mailbox_domain.*, mailbox.* and client_id.* event is listed in the
webhooks guide's Events table, with an example payload per family and
the order a multi-mailbox order sends them in. mailbox.setup_progress is frequent and purely
informational - see its setupStage values there.
No event ever carries a password or any other sign-in detail.