Your inboxes, programmable.
Create an API key in your console. Authenticate every request with a Bearer token.
Quick start
curl https://api.inbox.cheap/v1/mailboxes \ -H "Authorization: Bearer YOUR_API_KEY"
| Method | Endpoint | Action |
|---|---|---|
| GET / POST | /v1/mailboxes | List or create addresses |
| DELETE | /v1/mailboxes/{id} | Permanently retire an address |
| GET | /v1/mailboxes/{id}/messages | List received messages |
| GET | /v1/messages/{id}/raw | Download original email |
| GET | /v1/domains | List available domains |
Create an inbox
{"localPart":"project-tests","domain":"159.hk"}Prefixes require 6–63 characters on Free, 5–63 on Plus, and 3–63 on Pro and Enterprise. Destroyed addresses cannot be reused. Free and Plus use mailx.im and 159.hk; Pro and Enterprise also unlock inbox.cheap.
Batch mailbox creation
Create 1–100 mailboxes per request, within your active plan limit. The operation is atomic: invalid prefixes, reserved names, reused addresses, or insufficient capacity reject the entire batch.
POST https://api.inbox.cheap/v1/mailboxes/batch
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{"mode":"custom","localParts":["project-one","project-two"],"domain":"159.hk","includeShareLink":true}Generate random inboxes
{"mode":"random","count":5,"domain":"159.hk","includeShareLink":true}Returns HTTP 201 with data and count. Each mailbox includes shareUrl only when includeShareLink is true (default false). The same option works with single creation: POST /v1/mailboxes.
Free: 6–63 characters; Plus: 5–63; Pro and Enterprise: 3–63. Use lowercase letters, digits, dots, underscores, or hyphens; the first character must be a letter or digit. Random prefixes are 12 characters. Pro and Enterprise can use @inbox.cheap. Shared links expose the latest 10 messages to anyone holding the link.
Bulk sharing & CSV export
Use your Bearer API key. Both endpoints require POST and operate only on your own inboxes. Existing share links are preserved.
Generate shared links
POST https://api.inbox.cheap/v1/mailboxes/share-links/batch\nContent-Type: application/json\n\n{"mailboxIds":["MAILBOX_ID_1","MAILBOX_ID_2"]}Specify up to 100 IDs, or send {} for all active inboxes. The response contains data and count. Explicitly selected retired inboxes return 410. Foreign inboxes return 404.
Export all inboxes
curl -X POST https://api.inbox.cheap/v1/mailboxes/export.csv \\n -H "Authorization: Bearer YOUR_API_KEY" \\n -o inboxes.csv
CSV includes address, status, message count, creation time (UTC), and read-only URL. Missing links are generated for active inboxes; retired inboxes have blank URLs. Keep exported links private.
Read-only shared inboxes
Each active inbox has a persistent private link on inbox.cheap. Anyone with that link can read its latest 10 messages without signing in. Retiring an inbox disables shared access.
| Method | Endpoint | Action |
|---|---|---|
| GET | /v1/mailboxes/{id}/share-link | Get or initialize the persistent URL |
| POST | /v1/mailboxes/{id}/share-link | Reset the URL with {"rotate":true} |
{"mailboxId":"…","url":"https://inbox.cheap/s/…","readOnly":true,"messageLimit":10}Requires the owning account’s Bearer API key. Resetting a link immediately revokes the old URL. Treat the link as a password.
Reserved mailbox prefixes
These exact prefixes are reserved on every receiving domain to avoid impersonation and confusion. Matching is case-insensitive. They cannot be created through the website or API.
abuse, account, accounts, admin, administration, administrator, admins, api, auth, authentication, billing, bounce, bounces, careers, compliance, contact, customer-service, customer-support, customercare, customerservice, daemon, devops, dmarc, dns, do-not-reply, donotreply, email, feedback, finance, fraud, ftp, hello, help, helpdesk, hostmaster, hr, inbox.cheap, inboxcheap, info, invoice, invoices, it, jobs, legal, mail, mailer, mailer-daemon, marketing, moderator, moderators, news, newsletter, no-reply, noc, noreply, notification, notifications, notify, office, official, operations, ops, owner, payment, payments, postmaster, press, privacy, recovery, refund, refunds, register, registration, root, sales, security, service, services, smtp, ssl, staff, status, support, sysadmin, system, team, tech, technical, trust, unsubscribe, verification, verify, webmaster, welcome, www
Account, plan & quota
GET https://api.inbox.cheap/v1/account returns the authenticated account’s opaque public ID, email, current plan, expiration, limits, usage, and receiving domains. /v1/me and /v1/usage are aliases returning the same response. All require your Bearer API key and return private, non-cacheable data.
curl https://api.inbox.cheap/v1/account \ -H "Authorization: Bearer YOUR_API_KEY"
{
"id": "acct_<32 random hexadecimal characters>",
"email": "[email protected]",
"plan": "enterprise_100k",
"planName": "Enterprise 100K",
"planSource": "manual",
"expiresAt": "2099-12-31T00:00:00Z",
"expiryType": "manual_grant",
"quota": {
"activeMailboxes": {
"limit": 100000, "used": 2, "remaining": 99998, "overLimit": false
}
},
"usage": {"totalMailboxes": 6, "retiredMailboxes": 4},
"features": {
"minimumPrefixLength": 3,
"maximumPrefixLength": 63,
"maximumBatchSize": 100,
"receivingDomains": ["mailx.im", "159.hk", "inbox.cheap"]
},
"asOf": "2026-11-01T12:00:00Z"
}This is an illustrative response; use your actual returned values. expiresAt is null when no expiry is set. expiryType is manual_grant for manual plans or billing_period_end for Paddle subscriptions; a billing period end can renew. Free has no expiry. Retired mailboxes do not consume the active limit. If a downgrade leaves usage above the limit, remaining is zero and overLimit is true. Quotas are a snapshot, not a reservation; concurrent creation is checked atomically.
Paginated mailbox listing
GET /v1/mailboxes?limit=100&status=active. The default and maximum page size is 100. Use 1–100 for limit. Omit status for all your mailboxes or choose active or destroyed.
{"data": [], "pagination": {"limit": 100, "hasMore": false, "nextCursor": null}}When hasMore is true, pass nextCursor as the next request’s cursor, keeping the other filters unchanged. Results are ordered by opaque mailbox ID descending. Account ownership is enforced on every page. Cursors are pagination positions, not access credentials. Responses no longer expose numeric user_id; use the stable public ID from /v1/account when you need an account identifier. Mailbox IDs and API keys are unchanged.
For larger accounts, update clients that previously assumed one response contained every mailbox to follow pagination. Set connection and request timeouts. Retry read-only requests with bounded exponential backoff for transient transport errors or 5xx responses; do not blindly retry creation requests after a timeout because the original operation may have succeeded.
Automation & scoped API keys
Download and run the email-flow demo. Python 3.10+, no extra packages. It creates an inbox, waits for a uniquely matched email, asserts its verification code, and retires the address. Sending uses your own mail client or configured SMTP provider.
Each API key can have specific permissions: account:read, mailboxes:read, mailboxes:write, mailboxes:delete, messages:read, codes:read, shares:write, exports:read, webhooks:read, webhooks:write. Missing permissions return HTTP 403. Permissions apply to all of the account’s owned mailboxes; per-mailbox key restrictions are not implemented. Legacy keys retain full access until edited or revoked.
Creating inboxes with includeShareLink: true also requires shares:write. CSV export creates missing shared links, so it requires both exports:read and shares:write. A GET to the sharing-link endpoint may initialize the link, so it also requires shares:write.
Match the current test’s message
GET /v1/mailboxes/{mailboxId}/messages?since=2026-11-01T12%3A00%3A00Z&subject=unique-test-name&limit=100Requires messages:read. since must be a UTC ISO timestamp ending in Z. Optional sender and subject filters are substring matches. Results contain up to 100 recent matching messages. Use a new inbox or unique subject for each parallel test; don’t depend on the web inbox’s read status.
Extract verification-code candidates
GET /v1/messages/{messageId}/codes
Authorization: Bearer YOUR_SCOPED_KEYRequires codes:read. Does not require permission to download the full email. Supports messages up to 10 MB; scans decoded subject and text for numeric 4–8 digit candidates near verification labels. It does not execute email content, follow links, or use an external AI service.
{"messageId":"…","candidates":[{"value":"735144","source":"subject","context":"735144 is your login code"}],"ambiguous":false,"notice":"Candidates are heuristic matches. Check the email before using a code."}There may be zero or several candidates. Values remain strings to preserve leading zeroes. A candidate can be incorrect; validate expected sender, subject, time, and context. Alphanumeric codes and image-only codes are not currently extracted.
Receiving Webhooks
Manage endpoints in the console or use these authenticated API endpoints. Webhooks are available on all plans, up to five active endpoints per account.
| Method | Endpoint | Permission |
|---|---|---|
| GET | /v1/webhooks | webhooks:read |
| POST | /v1/webhooks | webhooks:write |
| GET | /v1/webhooks/{id}/deliveries | webhooks:read |
| POST | /v1/webhooks/{id}/disable | webhooks:write |
| POST | /v1/webhooks/{id}/retry | webhooks:write |
POST /v1/webhooks
{"url":"https://your-app.example/webhooks/inboxcheap"}Creation returns an ID and a signingSecret, shown once. Only HTTPS on port 443 with public IPv4 DNS is supported. Private networks, IP-literal URLs and redirects are rejected. DNS is revalidated and the chosen public address is pinned for each delivery. Editing a URL means creating a new endpoint and disabling the old one.
Event format
{"id":"evt_…","type":"message.received","createdAt":"2026-11-01T12:00:00Z","data":{"messageId":"…","mailboxId":"…","address":"[email protected]","sender":"Example <[email protected]>","subject":"Your verification code","receivedAt":"2026-11-01T12:00:00Z"}}The event includes metadata only. Retrieve message content or code candidates with a separately scoped API key. New messages and their delivery events are saved in the same database transaction. Endpoint registration does not replay historical messages.
Verify the signature
InboxCheap-Event-Id: evt_… InboxCheap-Timestamp: unix_timestamp InboxCheap-Signature: v1=hex_hmac_sha256
Calculate HMAC-SHA256 over the exact bytes of timestamp + "." + raw_request_body, using the full displayed whsec_… secret. Require a timestamp within five minutes and a constant-time signature comparison before parsing or processing the event. See the downloadable Python receiver for a working example with persistent deduplication.
Delivery behavior
Asynchronous delivery, normally picked up by a worker within a minute. Only HTTP 2xx acknowledges an event; redirects are not followed. Up to eight attempts, with backoff of 1 minute, 5 minutes, 15 minutes, 1 hour, 3 hours, 6 hours, and 12 hours between attempts. Network errors and non-2xx responses are retried. Processing is at least once: deduplicate by event ID; ordering is not guaranteed. Save work durably before acknowledging.
The console shows the latest ten events per endpoint; the API returns the latest fifty. Completed, canceled, and failed event records are retained for up to 30 days. A manual retry of a failed delivery sends the same event ID and restarts the attempt budget: {"deliveryId":"…"}. Disabling an endpoint cancels queued deliveries; a request already in flight may finish. Never put API keys in endpoint query strings.
Chat notifications and webhooks
Use Notifications to receive new-email alerts in Telegram, WeCom, Feishu / Lark, DingTalk, WhatsApp Business, or your own HTTPS endpoint. Platform setup instructions are shown when you choose a channel. Up to five active destinations per account. Destinations receive alerts for all active inboxes in that account.
WeCom, Feishu and DingTalk use a group bot URL. Feishu and DingTalk also support bot signing secrets. Telegram requires a bot token and Chat ID; start your bot or grant it permission to post in the destination. WhatsApp requires your own Meta Business Cloud API credentials and an approved template with exactly one positional body text parameter; ordinary wa.me links do not work. WhatsApp provider fees may apply and recipients must agree to receive notifications.
Alerts include address, sender, subject, and a sign-in-required inbox link. They exclude the message body and attachments. Subjects may contain codes or other private information. Platform credentials are encrypted and never returned by listing endpoints. Disable a destination and replace it to change credentials.
API configuration
All endpoints below require webhooks:write to modify or test, and webhooks:read to list endpoints or deliveries. Existing custom-webhook requests remain supported.
POST /v1/webhooks
{"channel":"wecom","label":"Team alerts","url":"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_BOT_KEY"}
POST /v1/webhooks
{"channel":"telegram","label":"My alerts","botToken":"YOUR_BOT_TOKEN","chatId":"YOUR_CHAT_ID"}
channel is custom (default), telegram, wecom, feishu, dingtalk, or whatsapp. Feishu/DingTalk accept url and optional signingSecret. WhatsApp accepts accessToken, phoneNumberId, recipient (international digits without +), templateName, language, and your supported Graph apiVersion such as v23.0; check Meta for version support.
GET /v1/webhooks
POST /v1/webhooks/{id}/test
GET /v1/webhooks/{id}/deliveries
POST /v1/webhooks/{id}/disable
POST /v1/webhooks/{id}/retry
{"deliveryId":"FAILED_DELIVERY_ID"}
A test queues a synthetic notification.test event without sharing a real message. One test per destination per minute. A custom endpoint receives the same signed JSON envelope as regular events but must also accept this test type. Disabled destinations cannot be tested. Platform creation returns no signing secret; custom webhooks return signingSecret once. Platform listings mask credentials.
Delivery is asynchronous, normally picked up within a minute, with up to eight attempts. A platform can reject a request even with HTTP 200; these are marked provider_rejected. HTTP success means accepted by the provider, not read by the recipient. Repeated delivery is possible. Custom receivers should deduplicate by event ID. No redirects or private networks are allowed.
Forward personal email into the API
Use Personal email in the console to verify an external mailbox, create a random dedicated receiving address, and follow your provider’s forwarding guide. Gmail, QQ Mail, Outlook, and other providers can be used where their account settings and policies permit forwarding. No source-mailbox password, IMAP credentials, or extra Google OAuth scopes are requested.
A connection uses one active inbox slot. Mailbox listings include source_email and source_verified_at for connected inboxes. address remains the dedicated forwarding destination; source_email identifies the verified external address. This metadata does not authenticate the origin of every received message. Only forwarded copies are available, without historical import, sent-mail synchronization, or two-way read/delete state.
Create a dedicated read-only AI key from the connection page. It is restricted to one mailbox even if you later select broader permissions. It supports listing that inbox and reading its messages, original MIME and code candidates. It cannot read account information, create/retire inboxes, access other inboxes, share/export mail, or manage webhooks.
GET /v1/mailboxes
GET /v1/mailboxes/{connectedMailboxId}/messages
GET /v1/messages/{messageId}
GET /v1/messages/{messageId}/raw
GET /v1/messages/{messageId}/codesExisting account-wide keys retain their configured access. Existing notifications apply to the forwarded inbox. New message webhook metadata also includes sourceEmail and displayAddress, while address remains the forwarding destination. Chat notifications use the personal address as their display label.
Private forwarding inboxes do not create public share links. CSV export leaves their sharing-link column empty. Disconnecting permanently retires the address and revokes dedicated keys; remove the forwarding rule in your original provider too. Stored messages remain until separately deleted; a request already in flight may finish.