Build around your inbox.
Read-only access to an authorized mailbox. Structured responses, explicit limits, and examples you can copy into your scripts.
1. Quickstart
Base URL: https://mailread.tech/api/v1. Use your website password to get a token. Never send your email-provider/IMAP password to this API. Replace placeholders with credentials issued by your administrator.
curl --fail-with-body -X POST 'https://mailread.tech/api/v1/token' \
-H 'Content-Type: application/json' \
-d '{"email":"you@example.com","password":"YOUR_WEBSITE_PASSWORD"}'Copy access_token from the response. Supply it in an Authorization header:
curl --fail-with-body 'https://mailread.tech/api/v1/messages?limit=5' \ -H 'Authorization: Bearer YOUR_MAILBOX_TOKEN'
The browser inbox uses separate session endpoints. Scripts should use the documented /api/v1 routes below. There is no API endpoint for bypassing mailbox passwords or for anonymous mailbox discovery.
2. Authentication
- Tokens are read-only and scoped to one mailbox.
- Tokens expire after 3,600 seconds. Obtain a new token when expired.
- Changing or resetting the website password revokes previously issued tokens.
- Send
Authorization: Bearer TOKENon read endpoints. Bulk reads take one token per mailbox in the JSON body. - Never put credentials in query strings. This API does not enable cross-origin browser access (CORS).
- POST bodies must use
Content-Type: application/json. Versioned API requests do not require cookies or CSRF tokens.
3. Limits and polling
| Limit | Current behavior |
|---|---|
| Read requests | 120 per token per minute |
| IP requests | 300 credits per minute; a bulk batch costs 10 |
| Token sign-ins | 10 per identifier/IP in 15 minutes; shared IP sign-in cap of 60 |
| Bulk read | Up to 10 tokens per batch |
| Message list | Latest 30 search candidates, exact-recipient filtered; INBOX only |
| Request body | 128 KiB maximum |
| Upstream cache | List: 5 seconds; opened message: 2 minutes |
Poll every 10 seconds or slower, use a timeout of at least 60 seconds, and back off on 429/502. Read Retry-After on 429. Authenticated single-mailbox responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix time). The VPS proxy may apply additional limits. App quotas are process-local.
since_uid is IMAP-only and exclusive. Gmail API clients should deduplicate (uid, epoch) pairs while polling; passing since_uid in Gmail mode returns 400. Store each UID alongside its epoch and reset your cursor when the epoch changes. There is no historical pagination; poll often enough that your expected traffic fits the 30-candidate window.
Endpoint reference
/api/v1/tokenWebsite passwordGet a mailbox token
Exchange an email address (or mailbox_id from its reading URL) and website password for a read-only Bearer token. The token expires after one hour. Password changes invalidate existing tokens.
| Parameter | Type | Usage |
|---|---|---|
email | string | Required unless mailbox_id is supplied. |
mailbox_id | UUID string | Alternative to email; do not send both. |
password | string | Required. The Mailread website password, never the IMAP password. |
{
"access_token": "YOUR_MAILBOX_TOKEN",
"expires_in": 3600,
"mailbox": {
"email": "you@example.com",
"id": "11111111-1111-4111-8111-111111111111"
},
"scope": "mail:read",
"token_type": "Bearer"
}No cookies or CSRF token are needed for /api/v1. POST requests require application/json. Never put passwords or tokens in a URL.
/api/v1/mailboxBearer tokenIdentify the authorized mailbox
Returns only the mailbox authorized by the token. There is no endpoint that lists other users’ mailboxes.
{
"data": {
"email": "you@example.com",
"id": "11111111-1111-4111-8111-111111111111"
}
}/api/v1/messagesBearer tokenList recent mail
Newest-first summaries from the latest 30 provider search candidates, after exact-recipient filtering. This is a recent-mail window, not a complete mailbox export.
| Parameter | Type | Usage |
|---|---|---|
limit | integer 1–30 | Optional; default 30. |
search | string ≤200 | Optional; matches sender, subject, snippet or extracted code. |
sender | string ≤200 | Optional; case-insensitive sender substring. |
subject | string ≤200 | Optional; case-insensitive subject substring. |
since_uid | numeric string | IMAP-only; larger UIDs. Gmail clients should deduplicate uid/epoch pairs. |
{
"data": [
{
"address": "hello@example.com",
"code": "123456",
"date": "2026-10-23T10:00:00+00:00",
"epoch": "100",
"from": "Example \u003chello@example.com\u003e",
"sender": "Example",
"snippet": "Your verification code is 123456.",
"subject": "Your verification code",
"uid": "42"
}
],
"meta": {
"cached": false,
"fetched_at": "2026-10-23T10:00:00+00:00",
"has_more": false,
"returned": 1,
"window": 30
}
}Filters apply within the 30-message window. has_more means upstream search returned over 30 candidates; it does not expose a pagination cursor. Message data is cached in RAM for up to five seconds.
/api/v1/messages/{uid}Bearer tokenRead a message
Returns normalized text, safe structured blocks, visible HTTP(S) links and an image-blocking count. The UID and epoch must come from this mailbox’s message list.
| Parameter | Type | Usage |
|---|---|---|
uid | opaque path string | Required opaque message UID from the list; Gmail IDs have a g_ prefix. |
epoch | numeric query string | Required epoch from the list; IMAP uses UIDVALIDITY, Gmail uses 1. |
{
"data": {
"address": "hello@example.com",
"blocks": [
{
"runs": [
{
"text": "Your verification code is 123456."
}
],
"type": "paragraph"
}
],
"code": "123456",
"date": "2026-10-23T10:00:00+00:00",
"epoch": "100",
"from": "Example \u003chello@example.com\u003e",
"images_blocked": 1,
"links": [],
"sender": "Example",
"snippet": "Your verification code is 123456.",
"subject": "Your verification code",
"text": "Your verification code is 123456.",
"truncated": false,
"uid": "42"
}
}Images, scripts, attachments and original sender HTML are never returned for rendering. IMAP reads the first 1 MiB of MIME. Gmail transfers at most 4 MiB of JSON and decodes up to 1 MiB of text, falling back to a snippet when oversized. Previews are limited to 200,000 characters/2,000 blocks; truncated=true indicates a bounded preview. Opened content is cached in RAM for up to two minutes.
/api/v1/latestBearer tokenGet the newest matching message
Returns the newest summary, or {"data":null} with HTTP 200 when nothing matches.
| Parameter | Type | Usage |
|---|---|---|
sender / subject / search | string ≤200 | Optional filters, as in the message list. |
since_uid | numeric string | IMAP-only exclusive minimum UID. Gmail clients should deduplicate uid/epoch pairs. |
{
"data": {
"address": "hello@example.com",
"code": "123456",
"date": "2026-10-23T10:00:00+00:00",
"epoch": "100",
"from": "Example \u003chello@example.com\u003e",
"sender": "Example",
"snippet": "Your verification code is 123456.",
"subject": "Your verification code",
"uid": "42"
}
}/api/v1/otpBearer tokenExtract a recent email code
Finds the newest detected numeric verification code among the recent summaries, fetching up to five message bodies when snippets do not contain a code. Detection is heuristic, not proof of authenticity.
| Parameter | Type | Usage |
|---|---|---|
format | json or text | Optional; default json. |
sender / subject / search | string ≤200 | Optional filters. |
since_uid | numeric string | IMAP-only; useful while waiting for new mail. Gmail clients deduplicate uid/epoch pairs. |
{
"data": {
"code": "123456",
"date": "2026-10-23T10:00:00+00:00",
"epoch": "100",
"uid": "42"
}
}JSON returns data:null with HTTP 200 when no code is detected. format=text returns just the code (200), or an empty response (204). This reads codes from mail; it does not generate TOTP codes from a 2FA secret.
/api/v1/linksBearer tokenRead links from the latest email
Reads the newest matching message body and returns visible, deduplicated HTTP(S) links without visiting them.
| Parameter | Type | Usage |
|---|---|---|
sender / subject / search | string ≤200 | Optional filters. |
since_uid | numeric string | IMAP-only exclusive minimum UID. Gmail clients should deduplicate uid/epoch pairs. |
{
"data": {
"links": [
{
"label": "Continue verification",
"url": "https://example.com/verify?token=demo"
}
],
"uid": "42"
}
}No matching mail returns {"data":{"uid":null,"links":[]}} with HTTP 200.
/api/v1/bulk/messagesOne token per mailbox in JSONRead multiple authorized mailboxes
Read up to ten mailboxes in one request. Every mailbox must have its own valid token; a token cannot authorize another mailbox. Results preserve input order, with per-item errors.
| Parameter | Type | Usage |
|---|---|---|
tokens | array of 1–10 strings | Required mailbox Bearer tokens, without the Bearer prefix. |
{
"results": [
{
"data": [
{
"address": "hello@example.com",
"code": "123456",
"date": "2026-10-23T10:00:00+00:00",
"epoch": "100",
"from": "Example \u003chello@example.com\u003e",
"sender": "Example",
"snippet": "Your verification code is 123456.",
"subject": "Your verification code",
"uid": "42"
}
],
"index": 0,
"mailbox": {
"email": "you@example.com",
"id": "11111111-1111-4111-8111-111111111111"
},
"meta": {
"cached": false,
"fetched_at": "2026-10-23T10:00:00+00:00"
}
},
{
"error": {
"code": "invalid_token",
"message": "A valid mailbox Bearer token is required."
},
"index": 1
}
]
}HTTP 200 can contain successful and failed items. Each token consumes one read from its quota. A batch consumes ten IP request credits. All tokens and credentials stay out of URLs.
/api/v1/healthPublicCheck application health
Checks the web process, not upstream mail-provider connectivity. It does not reveal configuration or mailbox counts.
{
"api_version": "1",
"status": "ok"
}Errors and empty results
Application errors use the JSON envelope below. A reverse proxy may return a non-JSON error; check the HTTP status before parsing the body.
{
"error": {
"code": "invalid_token",
"message": "A valid mailbox Bearer token is required."
}
}| Status | Meaning | Action |
|---|---|---|
| 400 | Invalid JSON, parameters or missing epoch | Fix the request. |
| 401 | Wrong credentials, invalid/expired/revoked token | Reauthenticate with the current website password. |
| 404 | Message unavailable or does not belong to this mailbox | Refresh the list; do not reuse another mailbox’s UID. |
| 413 | Body exceeds 128 KiB | Reduce batch/input size. |
| 415 | POST is not application/json | Set the JSON content type. |
| 429 | Application or proxy rate limit reached | Honor Retry-After; add backoff/jitter. |
| 502 | Mail provider unavailable | Retry later; check server configuration if persistent. |
| 500 | Unexpected application error | Retry with backoff; contact the server owner. |
Empty lists return {"data":[]} with metadata. Latest/OTP JSON returns {"data":null} when empty. OTP text returns an empty 204. Bulk reads return 200 with an error on each failed item; always inspect every result.
Data model and privacy
uidandepochare strings. Treat them as opaque identifiers. IMAP uses numeric UIDs and UIDVALIDITY; Gmail uses a g_ prefix and epoch 1.codeis a detected 4–8 digit string or null. It is heuristic and can be wrong.- Dates normally use ISO 8601; malformed upstream dates may remain in their original string form.
textis readable text including link destinations.blockscontains safe paragraph/heading/list/pre runs with optional href/bold/italic fields.linkscontains visible HTTP(S) destinations. Links are extracted, not visited.images_blockedcounts removed HTML image elements. No sender images/scripts or original HTML are rendered.- The VPS stores mailbox addresses and password hashes. Message previews are temporarily cached in RAM. Backups contain private access configuration and should be protected.
- The formatter and TOTP generator are browser-only utilities. There is intentionally no server endpoint that accepts a TOTP secret.
- All configured domains share the same installation and connected master account (IMAP or Gmail HTTPS API). API tokens work across those hostnames; browser cookies are separate per hostname.
Machine-readable schemas for requests, summaries, message bodies and errors are included in openapi.json.