GET /me
Current account
Base URL https://api.tmpmails.net/api/v1. Everything is JSON;
send Accept: application/json and, where a body is required,
Content-Type: application/json.
67 operations across 15 groups, taken straight from the contract.
/meCurrent account
/meUpdate the account
| Field | Type | Rules |
|---|---|---|
locale | string | optional, max 10 |
/me/2faTwo-factor status
/me/2faDisable two-factor
Requires a current code, so a stolen session alone cannot remove the second factor.
| Field | Type | Rules |
|---|---|---|
code | string | required, max 16 |
/me/2fa/setupBegin two-factor enrolment
The secret is held server-side for a short window and is not active until `confirm` succeeds, so an abandoned enrolment cannot lock the account out.
/me/2fa/confirmActivate two-factor
| Field | Type | Rules |
|---|---|---|
code | string | required, max 16 |
/auth/password/forgotAsk for a password reset link
Always answers 204, whether or not the address has an account. Answering differently would turn this into a way to find out who has one.
| Field | Type | Rules |
|---|---|---|
email | string | required, max 255 |
/auth/password/resetSet a new password with a reset token
The token is single use and expires after 30 minutes. Two-factor enrolment is untouched: a reset recovers the password, not the second factor.
| Field | Type | Rules |
|---|---|---|
token | string | required, min 32, max 128 |
password | string | required, min 8, max 200 |
/auth/tokenSign in
With 2FA enabled the first call answers `401 auth.totp_required`; repeat it with `code`. Invalid credentials and a wrong code are the same failure to the caller by design.
| Field | Type | Rules |
|---|---|---|
email | string | required, max 255 |
password | string | required, max 200 |
code | string | optional, max 16 |
/plansPlan and add-on catalog (prices in points)
/payment-providersPayment gateway registry (public)
Mirrors `settings['billing.providers']` so the frontend renders a switched-off gateway as a disabled button instead of hiding it or letting it be clicked until the next deploy.
/me/subscriptionCurrent subscription and effective entitlements
/me/subscriptionSubscribe or switch plan (debits points immediately)
Debit and activation happen in one transaction. Switching plans cancels the previous subscription and starts a fresh period; no proration.
| Field | Type | Rules |
|---|---|---|
planCode | string | required |
interval | string | required, one of monthly, yearly |
/me/subscriptionTurn off auto-renew (the paid period is never cut short)
/me/addonsBuy an add-on (debits points immediately, keeps the current plan)
An add-on is bought on top of the running plan, never instead of it. Buying the same add-on again stacks a second subscription and raises the entitlement again.
| Field | Type | Rules |
|---|---|---|
code | string | required |
interval | string | required, one of monthly, yearly |
/me/walletWallet balance
/me/wallet/transactionsPoints ledger (paginated)
| Parameter | In | Type |
|---|---|---|
| ||
|
/me/wallet/topupsList the caller's top-up orders
A pending order carries the only copy of its bank memo. Without this list a customer who closed the tab has no way back to the account number they still have to pay.
| Parameter | In | Type |
|---|---|---|
limit | query | integer |
/me/wallet/topupsCreate a top-up order
`sepay` is active (VND bank transfer). `bnb` is `coming_soon` and returns 400 payment.provider_unavailable. The wallet is never credited from the bank webhook directly — reconciliation matches memo + amount.
| Field | Type | Rules |
|---|---|---|
provider | string | required, one of sepay, bnb |
amount | integer | required, ≥ 1 |
currency | string | required, one of VND |
/me/wallet/topups/{orderId}Poll top-up order status
| Parameter | In | Type |
|---|---|---|
orderId | path | string |
/content/noticesNotices to show over the product
Banners and dialogs published by staff, already filtered by schedule. The HTML is sanitised on write against a fixed allowlist, so it is safe to render as-is.
/content/pagesPublished pages
Titles and keys only — enough for a footer, without shipping every page body.
/content/footerStaff-written site footer for the requested language
`null` when nobody has published a footer, which is not an error — the client then renders its built-in footer. Falls back to the default locale when the requested one has no translation yet.
/content/pages/{key}One published page
| Parameter | In | Type |
|---|---|---|
key | path | string |
/me/domainsList private domains
/me/domainsAdd a private domain
| Field | Type | Rules |
|---|---|---|
domain | string | required, max 253 |
/me/domains/{id}Remove a private domain
/me/domains/{id}/catch-allTurn catch-all on or off for an own domain
With catch-all on, mail to any local part of the domain is accepted and a mailbox is created on first delivery. Requires the `domains.catchAll` entitlement and a verified domain.
| Field | Type | Rules |
|---|---|---|
enabled | boolean | required |
/me/domains/{id}/verifyRe-check the DNS records
Both the TXT proof and the MX record must resolve. A domain that verified once is re-checked periodically, so removing the MX later suspends it again.
/me/filtersList custom extraction filters
/me/filtersCreate a filter (entitlement `filters.max`)
| Field | Type | Rules |
|---|---|---|
name | string | required, max 100 |
scopeType | string | optional, one of all, domain |
domainId | string,null | optional |
fromPattern | string,null | optional |
subjectPattern | string,null | optional |
extractRegex | string | required, max 512 |
priority | integer | optional, ≥ 1, ≤ 9999 |
status | string | optional, one of active, disabled |
/me/filters/testDry-run a filter against a sample without saving
| Field | Type | Rules |
|---|---|---|
filter | extractRegex, fromPattern, subjectPattern | required |
sample | from, subject, text | required |
/me/filters/{id}Update a filter
| Field | Type | Rules |
|---|---|---|
name | string | optional, max 100 |
extractRegex | string | optional, max 512 |
fromPattern | string,null | optional |
subjectPattern | string,null | optional |
priority | integer | optional |
status | string | optional, one of active, disabled |
/me/filters/{id}Delete a filter
/integrations/alertsAlertmanager webhook — delivers operational alerts to the configured chat
Not part of the product surface. Delivery lives here rather than in Alertmanager because Alertmanager's Telegram client logs the URL it failed to call, and that URL contains the bot token. Authenticated by a shared secret, not a user session: the caller is a container. Always answers 200 once authorised — a non-2xx only makes Alertmanager retry a batch that will not format any better the second time.
| Field | Type | Rules |
|---|---|---|
status | string | optional, one of firing, resolved |
commonLabels | alertname, severity | optional |
alerts | annotations[] | optional |
/integrations/sepay/webhookSePay bank-transaction callback (append-only, idempotent)
Guarded by `Authorization: Apikey <shared-secret>` plus an ingress IP allowlist. The handler only appends a `sepay_transactions` row; crediting happens in the reconciliation job.
/me/api-keysList developer keys
/me/api-keysIssue a developer key
`key` is returned exactly once — only its hash is stored, so a database leak cannot be replayed. The tier follows the account plan and cannot be chosen.
| Field | Type | Rules |
|---|---|---|
name | string | required, max 100 |
scopes | string[] | required |
allowedCidrs | string[] | optional |
expiresAt | string | optional |
/me/api-keys/{prefix}Revoke a developer key
/keys/mailboxesCreate a mailbox with a developer key
Requires scope `mailboxes:write`. The mailbox belongs to the key's owner.
| Field | Type | Rules |
|---|---|---|
domain | string | optional, max 255 |
localPart | string | optional, max 64 |
ttl | integer | optional, ≥ 60 |
/keys/mailboxes/{id}/messagesList a mailbox's messages with a developer key
Requires scope `messages:read`. A mailbox the key does not own answers 404 rather than 403, so the endpoint cannot be used to discover which mailboxes exist.
| Parameter | In | Type |
|---|---|---|
| ||
|
/keys/mailboxes/{id}/otp/latestLatest OTP for a mailbox with a developer key
Requires scope `otp:read`.
/domainsActive public domains
/me/mailboxesMailboxes this account owns
Newest first, each with a bearer token. This is the only way to find a catch-all mailbox: it was opened by an incoming message, so the owner has never seen the address.
/mailboxesCreate a mailbox
| Parameter | In | Type |
|---|---|---|
|
| Field | Type | Rules |
|---|---|---|
domain | string | optional |
localPart | string | optional, min 1, max 64, pattern ^[a-z0-9][a-z0-9._-]{0,63}$ |
ttl | integer | optional, ≥ 60 |
/mailboxes/meMailbox info + TTL
/mailboxes/meDestroy the mailbox now
/mailboxes/me/extendExtend mailbox TTL (re-issues the mailbox token)
| Field | Type | Rules |
|---|---|---|
ttl | integer | optional, ≥ 60 |
/mailboxes/me/messagesList messages (cursor paginated, optional long-poll)
| Parameter | In | Type |
|---|---|---|
| ||
| ||
|
/messages/{id}Full message (sanitized HTML)
/messages/{id}Delete a message
/messages/{id}/rawDownload the original message (.eml)
The bytes as delivered, before parsing — full `Received:` chain, `Authentication-Results`, the untouched MIME tree and the original HTML before sanitizing. Opens in any mail client. The original is the largest per-message item in redis-mail, which runs `noeviction`, so it is kept only briefly: **15 minutes** for free mailboxes and **1 hour** for paid ones. After that this answers `410 message.raw_expired` — gone, not missing. `Message.rawExpiresAt` says when, so a client can drop the button rather than offer a download that fails.
/messages/{id}/attachments/{attId}Download an attachment
Always served as `application/octet-stream` with `Content-Disposition: attachment` and `nosniff`, whatever the sender declared: the file arrived from an unauthenticated stranger, so the browser must never be allowed to render it. Attachments above the inline size limit are not retained and answer 404.
| Parameter | In | Type |
|---|---|---|
| ||
attId | path | string |
/mailboxes/me/otp/latestLatest extracted OTP
| Parameter | In | Type |
|---|---|---|
|
/sessionMailboxes opened in this session
Newest first. Expired mailboxes are omitted rather than listed as dead addresses. Each mailbox carries a freshly minted bearer token capped at its own remaining TTL — the guest key is the only credential the visitor kept, so without tokens the list would name addresses it cannot read.
/sessionOpen a guest session
Redis-only, sliding 30 minutes, no account. Lets an anonymous visitor keep several mailboxes across reloads. It carries no plan and grants no extra quota — mailbox creation stays clamped per IP, so minting a new session buys nothing.
/sessionEnd the guest session
Drops the key and the session's mailbox index. The mailboxes themselves keep their own TTL — this is signing out, not deleting mail the visitor may still hold a token for.
/siteTitle, description and indexing rules for the hostname being read
Resolved from the request's Host header, not a parameter: this is per-site configuration, and letting a caller name a different site would hand out the wrong canonical URL.
/site/robots.txtrobots.txt for the hostname being read
/site/sitemap.xmlsitemap.xml for the hostname being read
/healthzLiveness probe
/readyzReadiness probe (dependencies checked)
/me/webhooksList webhook endpoints
/me/webhooksRegister an endpoint (entitlement `webhooks.max`)
The URL must be https on port 443/8443 and resolve to a public address. The response is the only time the signing secret is returned.
| Field | Type | Rules |
|---|---|---|
url | string | required, max 2048 |
events | string[] | required |
domainId | string,null | optional |
/me/webhooks/{id}Update an endpoint (resuming clears the failure history)
| Field | Type | Rules |
|---|---|---|
url | string | optional |
events | string[] | optional |
status | string | optional, one of active, paused |
/me/webhooks/{id}Delete an endpoint
/me/webhooks/{id}/deliveriesRecent delivery attempts (ring buffer, last 100)
| Parameter | In | Type |
|---|---|---|
id | path | string |
|