# DeniMail (denimail.com) > Self-hosted encrypted inbox + two-factor (TOTP) authenticator with a JSON API, hosted at https://denimail.com. > Every address at @denimail.com is a working inbox: mail sent to ANY name@denimail.com is stored and readable, no setup needed. > Typical use by an agent: create an address, give it to a sign-up form, wait for the verification email, read the code, log in with a TOTP code from the vault. ## Authentication - Send the header `X-API-Key: ` (or `?key=`) on every /api/* call. The key is shown in https://denimail.com/settings (login required). - Responses are JSON. Errors are JSON with a `detail` field and a 4xx/5xx status. Times are ISO 8601 UTC. ## Addresses - `GET /api/addresses` → list of addresses with mail counts (created ones and ones that simply received mail). - `POST /api/addresses` body `local=name` (or `address=name@denimail.com`, optional `label`) → {"address": "name@denimail.com"}. Empty `local` = random address. - `POST /api/addresses/bulk` JSON {"count": 25, "prefix": "bot-"} → bot-01@denimail.com … or {"locals": ["a", "b", "c"]} → creates many (max 500). - Rules for names: letters, digits, dots, dashes, plus, underscores, max 64 chars. Creation is optional: any name works the moment mail arrives. - Bulk with your own passwords: `POST /api/addresses/bulk` JSON {"accounts": [{"email": "a@denimail.com", "password": "…"}, …]} (max 500 per call). - Add `"login": true` (or a `password`) to /api/addresses or /api/addresses/bulk to also create a mailbox login: that email + password logs in at https://denimail.com/login and sees only its own inbox (and can use the API with its session). ## Reading mail - `GET /api/messages?to=name@denimail.com&limit=20&since_id=0&q=text` → newest first. Every message is accepted; there is no spam filter. - `GET /api/messages/{id}` → full message: from, to, subject, text, html, attachment_list, plus `code` (verification code found in the mail) and `link` (verification link). - `GET /api/wait?to=name@denimail.com&since_id=0&timeout=60` → long-poll; returns {"found": true, "message": {...}} as soon as a NEW message arrives, or {"found": false} after `timeout` seconds (max 170). - `GET /api/code?to=name@denimail.com&wait=90` → shortcut: waits up to 90 s for a new mail and returns {"found": true, "code": "482913", "link": "https://…", "subject": …}. - `GET /api/messages/{id}/attachments/{attachment_id}` → the file. `DELETE /api/messages/{id}` deletes a message. ## TOTP (two-factor codes) - `GET /api/totp?secret=BASE32SECRET` → {"code": "123456", "remaining": 17, "period": 30}. Also accepts an otpauth:// URI in `secret`. - `GET /api/totp?label=Gmail` or `?vault_id=3` → code for a secret saved in the vault (https://denimail.com/authenticator). - `POST /api/vault` JSON {"label": "Gmail adela", "secret": "BASE32 or otpauth://…", "owner": "adela@denimail.com"} → saves the secret (owner = a mailbox login; default admin) and returns the current code + share link. ## Shareable code links (no login, no API key) - `https://denimail.com/` shows the live code for that 2FA secret in the browser (API2FA-style; the secret is the path, so treat the link as the secret itself). - `https://denimail.com/?json=1` → {"code": "123456", "remaining": 17, "period": 30} for scripts. Optional `digits`, `period`, `algorithm` query params. ## Other - `GET /api/health` (no auth) → service status. `GET /openapi.json` → machine-readable OpenAPI schema. `GET /status` → human status page. - Mail is encrypted at rest and deleted after the retention period set in Settings. No filtering: everything sent to the domain is stored. Inbound only: DeniMail does not send mail, except forwarding copies of configured addresses (e.g. support@denimail.com) to an external inbox. ## Example: sign-up flow 1. `POST /api/addresses` with `local=alice` → alice@denimail.com 2. Use alice@denimail.com in the sign-up form. 3. `GET /api/code?to=alice@denimail.com&wait=120` → read `code` or `link`. 4. If the account has 2FA, save its secret in the vault once, then `GET /api/totp?label=alice` at every login.