YOUR NAME. YOUR MAIL.
An inbox on your own domain.
Personal includes one custom domain and 10 aliases for $1/month. Pro includes 3 domains and Team 10, with more addresses per domain. Add more domains for $1 each per 30 days, prepaid. Keep your DNS provider and website.
Start with ownership.
Open Addresses in your inbox and add a domain you control. Copy the ownership TXT record into your DNS provider, then check the records. You can prepare and verify your domain before subscribing; creating addresses requires Personal.
Create your addresses before moving mail.
An alias is another address for an existing inbox. It has no separate login and shares that inbox’s sending allowance. Create aliases up to your plan’s limit per domain (10 on Personal), directed to your own inbox or an enabled mailbox on your domain.
For a separate inbox, create a human mailbox with an initial password or an agent mailbox with an API key. You can create separate mailboxes up to your plan’s limit per domain (10 on Personal). Each has its own account. On Team, mailboxes share the owner’s daily sending allowance, up to 500 each; otherwise each has its own plan and limits: a free human mailbox sends to 3 recipients per day; an agent receives and reads for free and needs prepaid credits to send. Domain ownership does not give you access to another mailbox’s messages.
Save an agent’s API key when it is shown: it cannot be retrieved later. If mailbox creation times out, refresh your mailbox list before trying again.
Add authentication, then switch delivery.
Addresses provides the exact ownership, SPF, DKIM, DMARC and MX records for your domain. Keep one SPF policy and one DMARC policy. If you already send through another provider, review the existing policies before changing them. KeyKeeper’s checks explain any missing record.
Create all the addresses that should receive mail before changing MX. When you are ready, replace your old mail provider’s MX records with the supplied KeyKeeper MX record, then check again. Keep your website’s A, AAAA and CNAME records. Changing MX moves new incoming mail; it does not import messages from your previous provider.
Sending and receiving require all DNS checks to pass and active hosting for additional domains. Signing-key preparation can take about a minute; DNS changes can take longer. Authentication helps recipients verify your identity, but does not guarantee inbox placement.
Send from the right identity.
Your active aliases appear in the composer’s From menu. An agent can set from to its own mailbox or an active alias assigned to it. Aliases use the destination account’s credits and sending limit. Buying credits or adding addresses does not raise those limits.
Several domains. One inbox.
Create aliases from each domain into your primary inbox, then choose the address in the composer’s From selector. Domains included in your plan stay included: one on Personal or primary agent hosting. Additional domains are prepaid at $1 per 30 days, with 1–12 periods per purchase. In Addresses, verify ownership and open Activate hosting. Agents can use kind: domain_addon with domainId and periods through the payment API. Primary hosting must be active when purchasing or creating addresses.
Each additional domain shows its paid-through date. Renewals extend remaining time; there is no automatic renewal. At expiry, creating addresses stops. Existing sending and receiving continue for seven days, then pause until renewal. Stored mail is retained. Check domain.hosting for these states and domain.mailReady for DNS plus hosting readiness. More domains do not increase sending limits.
Pause or move a domain.
You can disable a domain or one of its mailboxes in Addresses. This stops its domain mail routing and sending while keeping stored mail. Removing an alias stops new delivery to that alias. A domain with provisioned mailboxes stays reserved; contact support for a transfer or permanent removal.
Domain registration is separate. Higher-volume sending still requires a volume review.
Custom-domain HTTP API.
Use an existing authorized owner account. A full agent account key can manage that agent’s own domain directly. For an integration, issue a limited key with domains:read for inventory and domains:write for setup changes, including DNS checks. Supply it as Authorization: Bearer KEY. Existing mail-only keys have no domain access. Domain management uses HTTP; there are no domain MCP tools yet.
Human owners can open Addresses → Domain access keys to delegate setup without sharing their password. The equivalent API is POST /api/user/domain-keys with an owner session and {"name":"Domain setup","scopes":["domains:read","domains:write"]}. Include factorCode if 2FA is enabled. Agent owners issue the same scopes through /api/v1/agent/access-keys using their full account key.
A setup key can change routing, add/remove aliases, create mailboxes and disable domain mail. It cannot read existing mail, change account credentials or purchase anything. Newly created agent mailbox credentials are returned to the setup integration. Keys cover domains owned now or added later by the issuing account; they are not restricted to one domain name. Revoke the setup key when finished; separately revoke any child mailbox keys that should no longer be used.
Save the one-time secret securely as KEYKEEPER_DOMAIN_KEY for the examples below. Keys expire in 30 days by default, at most 366 days. Keep secrets out of URLs, logs and shell history. GET the key-management route to inspect metadata and DELETE its /ID route to revoke a key; full account credentials or the owner’s session are required.
Owner session JWTs from /api/auth/login or /api/auth/register and the existing auth_token browser cookie still work for domain management. Login honors totpCode or an unused backupCode. Cookie-authenticated writes require an Origin matching KeyKeeper. Register a new account only when needed and authorized; agents can use /api/v1/agent/register without a password.
OpenAPI: complete request and response schemas →
Agents can pay and activate through the API.
With the owner’s full agent key, call POST /api/v1/agent/payment with {"kind":"domain_hosting","method":"base","periods":1} and a unique Idempotency-Key. Native USDC is accepted on Polygon, Base, Ethereum and Solana. Pay using the invoice’s exact network, token, amount and address, then poll its statusUrl. A paid result activates hosting automatically—no human checkout page. Each $1 period buys 30 prepaid days; renewals extend existing prepaid time. Sending credits are separate. Payment guide and retry rules →
Check entitlement before provisioning.
curl --fail-with-body https://keykeeper.world/api/user/domains \
-H "Authorization: Bearer $KEYKEEPER_DOMAIN_KEY" \
-H 'User-Agent: MyDomainSetup/1.0'The response contains domains, aliases, mailboxes, canCreateAddresses and limits. Up to 20 domains are allowed per owner account. Alias and mailbox limits per domain follow your plan (10 and 10 on Personal). Domains included in your plan are covered by primary hosting; each additional domain costs $1 per 30 days. Adding and checking a domain do not require payment. Creating an alias or mailbox requires an active owner subscription or prepaid domain hosting and ownership verified within the last 24 hours. The domain entitlement is included in Personal at $1/month for human owners. Agent owners can activate it with a card subscription or $1 per prepaid 30 days in native USDC, while retaining pay-as-you-go sending. Prepaid agent sending credits do not activate it.
To prepare checkout, use the owner session or full agent account key with POST /api/billing/checkout, {"kind":"personal"} and a unique Idempotency-Key. This is the optional recurring card path; personal also selects the agent custom-domain subscription. Return the Stripe url to the operator to review and complete; it does not activate until payment is confirmed. Limited domain keys cannot purchase. The subscription adds no agent sending credits or higher daily limit.
canCreateAddresses: true reports the account’s paid entitlement. Domain ownership, enabled state, address availability and limits are checked separately. Each separate mailbox keeps its own plan, credits and sending limits.
Available operations.
All request bodies below are JSON. Placeholder values must be replaced; optional fields may be omitted. Domain and address mutations return HTTP 200, including mailbox creation; human account registration and scoped-key creation return 201.
POST /api/auth/login — Sign in as the domain owner
Use an existing owner account. Returns token, a session JWT. With 2FA, supply totpCode or an unused backupCode; missing proof returns 403 with requires2FA: true. Limits: 30 requests/IP and 15/account per 15 minutes.
No existing session required.
{
"email": "owner@keykeeper.world",
"password": "<saved password>",
"totpCode": "<if 2FA is enabled>"
}POST /api/auth/register — Create an owner account when needed
Creates a human/password account and session token. Only register with operator authorization; reuse an existing suitable account when possible. This does not activate Personal. Five registrations/IP/hour shared with agent registration. After an uncertain response, try signing in; do not blindly register again.
No existing session required.
{
"email": "owner@keykeeper.world",
"name": "Domain owner",
"password": "<12+ characters; at most 72 UTF-8 bytes>"
}GET /api/user/domains — List domains, addresses and entitlement
Requires domains:read when using a limited key. Returns domains, aliases, mailboxes, canCreateAddresses and limits. DNS statuses are stored snapshots; POST verify for current lookup evidence. Up to 120 requests/account/minute.
Owner session, full account key or domains:read key required.
POST /api/user/domains — Add a domain
Up to 20 domains per owner; one included with primary hosting. Free to prepare DNS; no subscription needed here. Returns the exact dnsRecords and domain id. No purchase, nameserver change or automatic DNS writing. After a timeout, list domains before retrying.
Owner session, full account key or domains:write key required.
{
"domain": "example.com"
}POST /api/user/domains/verify — Check domain DNS
Checks all five record types independently, including before subscribing. HTTP 200 means the check ran, not that all records passed. Inspect domain.dnsStatus and domain.dnsDetails. Recursive DNS can return cached records from a previous nameserver. Poll with increasing delays, then stop; no need to recreate the domain.
Owner session, full account key or domains:write key required.
{
"domainId": "<domain UUID>"
}PATCH /api/user/domains — Enable or disable domain mail
Disabling stops domain routing/sending and retains stored mail. Re-enabling still requires current DNS checks and signing readiness.
Owner session, full account key or domains:write key required.
{
"domainId": "<domain UUID>",
"enabled": false
}DELETE /api/user/domains — Remove unused domain setup
Only unprovisioned domain setup can be removed. Provisioned mail returns 409; disable instead and contact support for transfer/removal. Send domainId in the JSON body.
Owner session, full account key or domains:write key required.
{
"domainId": "<domain UUID>"
}POST /api/user/domains/aliases — Create an alias
Requires paid owner access, active additional-domain hosting where applicable, and ownership verified within 24 hours. Up to 10 aliases/domain. Default destination is the owner inbox; an alias has no separate credentials and shares its destination mailbox credits/limits. Prepare before MX cutover. Conflicts return 409.
Owner session, full account key or domains:write key required.
{
"domainId": "<domain UUID>",
"local": "hello",
"destinationUserId": "<optional mailbox userId; defaults to owner>"
}An alias has no separate login or API key. Its destination must be the owner inbox or an enabled mailbox on this same domain. Arbitrary external forwarding is not supported.
DELETE /api/user/domains/aliases — Remove an alias
Send the alias id in the JSON body. Stops new delivery to the alias; does not erase delivered mail.
Owner session, full account key or domains:write key required.
{
"id": "<alias UUID>"
}POST /api/user/domains/mailboxes — Create a separate mailbox
Requires paid owner access, active additional-domain hosting where applicable, and ownership verified within 24 hours; up to 10 mailboxes/domain. Human requires a password; agent returns a one-time apiKey. Separate account, credits and limits; domain ownership does not grant message access. Check inventory after a timeout before retrying. Prepare addresses before MX cutover.
Owner session, full account key or domains:write key required.
{
"domainId": "<domain UUID>",
"local": "hello",
"name": "Application mail",
"accountType": "agent"
}For a human mailbox, set accountType: "human" and include password (at least 12 characters; at most 72 UTF-8 bytes). An agent mailbox needs no password and returns its own API key once. Domain owners do not gain access to another mailbox’s messages.
PATCH /api/user/domains/mailboxes — Enable or disable a mailbox
id is the mailbox userId. Stops or restores domain sending/receiving without deleting login or stored mail. Domain must also remain active.
Owner session, full account key or domains:write key required.
{
"id": "<mailbox userId>",
"enabled": false
}GET /api/user/domain-keys — List owner-issued keys
Owner session only. Lists metadata, never secrets. Agent owners may instead use /api/v1/agent/access-keys with their full account key.
Owner session required.
POST /api/user/domain-keys — Delegate domain setup to an agent
Owner session only. Issues domains:read and/or domains:write; no mail scopes here. Defaults to 30-day expiry, at most 366 days and 20 active keys. Existing 2FA requires factorCode. Secret is returned once. Key management mutations share 30/account/15 minutes.
Owner session required.
DELETE /api/user/domain-keys/{id} — Revoke delegated domain access
Owner session only; existing 2FA requires factorCode. Stops new authorized changes; already committed changes are retained. Does not revoke separate mailbox keys the integration previously provisioned. Repeating revocation is safe.
Owner session required.
POST /api/billing/checkout — Prepare a subscription checkout
Owner session or full agent account key only; limited keys cannot purchase. With operator authorization, request kind personal for the $1/month domain entitlement. Returns a Stripe checkout URL for the operator to complete. Requires Idempotency-Key; retry the same body and key. Agent sending remains pay as you go with separate credits and unchanged daily limits. This route also accepts domain_addon with domainId and periods (1–12) for $1 per 30 days per extra domain, or mail_credits for agent top-ups. Verify ownership and activate primary hosting first. Extra hosting is prepaid, never auto-renewed; new addresses stop at expiry and sending/receiving pause after 7 days. See billing docs.
Owner session or full agent account key required.
Each domain, from setup to a usable mailbox.
- Add the domain once.Save
domain.id. Use the returneddomain.dnsRecords; each entry hascheck,type,name,value,purpose, and MXpriority. Names are fully qualified. - Publish ownership TXT and verify.Use the same domain id in each check. After ownership passes, signing-key preparation runs asynchronously; refresh or verify until the DKIM entry appears. Four initial records are expected while the key is being prepared.
- Create the receiving addresses.Activate the owner’s domain entitlement by card or agent USDC payment if required. Create an alias into an existing inbox, or a separate human/agent mailbox. Save a new agent mailbox’s one-time key securely.
- Publish authentication, then switch MX.Preserve other senders when updating SPF, retain one valid DMARC policy, and use the exact DKIM value. Change MX only after all intended receiving addresses exist. Preserve your website’s A, AAAA and CNAME records.
- Verify all five checks, then test mail.Check DNS with gradually increasing delays, for example 5, 10, 20, 40 and 60 seconds, then stop and return later. Once active, an agent mailbox uses its own key with the mail REST/MCP API. Sending also requires credits and an authorized recipient.
curl --fail-with-body https://keykeeper.world/api/user/domains \
-H "Authorization: Bearer $KEYKEEPER_DOMAIN_KEY" \
-H 'Content-Type: application/json' \
-d '{"domain":"example.com"}'curl --fail-with-body https://keykeeper.world/api/user/domains/verify \
-H "Authorization: Bearer $KEYKEEPER_DOMAIN_KEY" \
-H 'Content-Type: application/json' \
-d '{"domainId":"YOUR_DOMAIN_ID"}'curl --fail-with-body https://keykeeper.world/api/user/domains/mailboxes \
-H "Authorization: Bearer $KEYKEEPER_DOMAIN_KEY" \
-H 'Content-Type: application/json' \
-d '{"domainId":"YOUR_DOMAIN_ID","local":"hello","name":"Application mail","accountType":"agent"}'An alias into a human inbox remains a human inbox; it does not gain an agent API key. For application mail such as magic links, create a separate agent mailbox or assign an alias to an existing agent mailbox on this domain. Use a limited sending key in the application.
Read what the DNS check actually found.
HTTP 200 means the check completed. It does not mean all records passed, an address exists, or a message was delivered. domain.dnsStatus has independent ownership, MX, SPF, DKIM and DMARC booleans; a failed check does not skip the others. domain.verified also requires enabled routing, prepared signing and unexpired DNS verification.
domain.dnsDetails, returned by verify, explains each current lookup with status, message, lookupName, recordType, source, checkedAt and observed. Status is verified, waiting, attention or unavailable. Observed answers are limited to five values of up to 4,096 characters. MX answers include priority and host.
{
"dmarc": {
"status": "verified",
"lookupName": "_dmarc.example.com",
"recordType": "TXT",
"source": "recursive_dns",
"checkedAt": "2026-09-22T16:00:00.000Z",
"observed": [
"v=DMARC1; p=quarantine"
],
"message": "Valid DMARC policy found (p=quarantine). An existing or cached policy can pass before a nameserver change completes."
}
}Checks use recursive DNS and can see cached records from previous nameservers during a migration. For domains using two standard Cloudflare nameservers, unsuccessful checks also consult both authorities. Matching valid answers return source: authoritative_dns with the nameservers consulted. Other resolvers may still cache the previous values until their TTL expires. A single existing valid DMARC policy with p=none, quarantine or reject is accepted; it need not match the suggested value exactly. A green DMARC check means a policy was found, not that an actual message passed DMARC or that Cloudflare activation completed. Missing records do not pass. Multiple SPF or DMARC policies require attention.
Inventory responses contain stored status. During temporary DNS failures, the service may retain earlier valid verification until it expires, for at most 24 hours. Use the verify response’s current dnsDetails to distinguish an unavailable lookup from a newly confirmed record; checkedAt alone is not proof of success.
Retries, limits and recovery.
Inventory allows 120 requests per account per minute. Adding domains, checking DNS, creating addresses and changing mailbox status share a 20-per-minute account bucket. Changing domain status has a separate 20-per-minute bucket; deletion operations share another. Respect 429 responses and stop polling when records pass or the retry window ends.
Creation is not idempotent. After an uncertain domain, alias or mailbox response, inspect inventory before retrying. After uncertain owner registration, try login with the saved credentials. A repeated creation may return 409, and a lost mailbox-creation response cannot reveal its API key through inventory. The domain owner cannot reset that agent key through these endpoints; use support if no copy was saved. Do not create duplicate accounts or publish new DKIM selectors to work around a timeout.
401 means the credential is missing, expired, revoked or invalid. 403 can mean a missing scope, missing 2FA at login, missing paid access, unverified ownership or a reached limit. 404 means the resource is unavailable to this owner. 409 means a conflict or provisioned domain that cannot be removed. Error responses contain error; inspect it before deciding the next step.