# ApiMail — Developer Guide ApiMail is an API-first email platform. Send and receive email programmatically via a REST API — no IMAP, no POP3, no mailbox UI. Create an inbox in one API call on the shared domain, or bring your own domain for full control. ## Base URL ``` https://api.apimail.cc/v1 ``` - Interactive API docs (Swagger UI): https://api.apimail.cc/docs - OpenAPI spec (machine-readable): https://api.apimail.cc/openapi.json The `/docs` page has full request/response schemas, field descriptions, and a "Try it out" feature for every endpoint. Use `/openapi.json` for programmatic consumption. All API endpoints are under the `/v1` prefix. System endpoints (`/health`, `/skill.md`, `/docs`) are at the root. ## Authentication All requests require an `X-API-Key` header: ``` X-API-Key: om_abc123... ``` API keys are returned **once** on creation and **cannot be retrieved again**. Keys have scopes: - `read` — read-only access to messages, threads, inboxes, domains - `send` — send and reply to email - `admin` — full access including creating/deleting resources, managing API keys (implies `send`) Accounts are provisioned by the platform admin — there is no self-service signup. Once you have an API key, you manage your own domains, inboxes, and additional API keys. ## Quick Start ### 1. Set your API key You'll receive an admin API key from the platform admin. Set it as a variable — you'll use it in every request: ```bash export API_KEY="om_your_key_here" ``` ### 2. Create an inbox **Fastest way — shared domain (no DNS setup needed):** ```bash curl -s -X POST https://api.apimail.cc/v1/inboxes/ \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` Response: ```json { "id": "inbox-uuid", "email": "swiftfox482@agentcourier.cc", "local_part": "swiftfox482", "status": "active", "created_at": "2026-03-05T..." } ``` That's it — you have a working inbox. Any email sent to this address is ingested and available via the API. You can also send and reply from shared domain inboxes (up to 25 sends/day, 5 inboxes/account). For higher limits, add a custom domain. **Custom domain — full control:** To use your own domain (e.g. `hello@yourdomain.com`), add and verify it first: ```bash # Add domain curl -s -X POST https://api.apimail.cc/v1/domains/ \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{"domain": "example.com"}' # Add the DNS records from the response, then verify curl -s -X POST https://api.apimail.cc/v1/domains/$DOMAIN_ID/verify \ -H "X-API-Key: $API_KEY" # Create inbox on your domain curl -s -X POST https://api.apimail.cc/v1/inboxes/ \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{"domain": "example.com", "local_part": "hello"}' ``` ### 3. Read your mail Inbox endpoints accept either a **UUID** or an **email address** — no need to remember IDs: ```bash # List messages (using email address or inbox UUID) curl -s https://api.apimail.cc/v1/inboxes/swiftfox482@agentcourier.cc/messages \ -H "X-API-Key: $API_KEY" # Get a single message with attachments curl -s https://api.apimail.cc/v1/messages/$MESSAGE_ID \ -H "X-API-Key: $API_KEY" # Fetch and mark as read in one call curl -s "https://api.apimail.cc/v1/messages/$MESSAGE_ID?mark_read=true" \ -H "X-API-Key: $API_KEY" # List only unread messages curl -s "https://api.apimail.cc/v1/inboxes/$INBOX_ID/messages?unread=true" \ -H "X-API-Key: $API_KEY" ``` ### Managing API keys To create additional keys (e.g. a read-only key for a dashboard), first get your account ID: ```bash curl -s https://api.apimail.cc/v1/accounts/me -H "X-API-Key: $API_KEY" ``` Then create a key: ```bash curl -s -X POST https://api.apimail.cc/v1/accounts/$ACCOUNT_ID/api-keys \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{"label": "read-only-dashboard", "scopes": ["read"]}' ``` ## Complete API Reference ### Account & API Keys | Method | Path | Scope | Description | |--------|------|-------|-------------| | GET | `/v1/accounts/me` | read | Get your account info (ID, email, plan). | | GET | `/v1/accounts/{id}/api-keys` | admin | List API keys (shows prefix, label, scopes — never the raw key). | | POST | `/v1/accounts/{id}/api-keys` | admin | Create a new API key. Raw key returned only in this response. | | DELETE | `/v1/accounts/{id}/api-keys/{key_id}` | admin | Revoke an API key (irreversible). | ### Domains | Method | Path | Scope | Description | |--------|------|-------|-------------| | POST | `/v1/domains/` | admin | Add a domain. Returns DNS records to configure. | | GET | `/v1/domains/` | read | List all domains. | | GET | `/v1/domains/{id}` | read | Get a single domain with verification status. | | POST | `/v1/domains/{id}/verify` | admin | Check all DNS records (MX, ownership, SPF, DKIM, DMARC) and activate domain. Sets `send_enabled` when all 5 pass. | | POST | `/v1/domains/{id}/setup-dkim` | admin | Generate DKIM keypair. Returns the DNS TXT record to add. | | DELETE | `/v1/domains/{id}` | admin | Delete a domain and all its inboxes. | ### Inboxes Inbox endpoints accept either a **UUID** or an **email address** as the identifier. For example, `/v1/inboxes/hello@example.com` and `/v1/inboxes/550e8400-...` both work. | Method | Path | Scope | Description | |--------|------|-------|-------------| | POST | `/v1/inboxes/` | admin | Create an inbox. Omit domain for shared domain (auto-generated address), or provide `domain`/`domain_id` for custom domain. Optional `display_name` sets the From header name. | | GET | `/v1/inboxes/` | read | List all inboxes. | | GET | `/v1/inboxes/{email_or_id}` | read | Get a single inbox by email address or UUID. | | PATCH | `/v1/inboxes/{email_or_id}` | admin | Update inbox properties (e.g. `status`, `display_name`). | | DELETE | `/v1/inboxes/{email_or_id}` | admin | Deactivate an inbox (sets status to inactive). | | GET | `/v1/inboxes/{email_or_id}/messages` | read | List messages in an inbox (paginated, searchable). | **Creating an inbox:** ```bash # Shared domain — no setup, auto-generated address (25 sends/day, limit 5 inboxes/account) curl -s -X POST https://api.apimail.cc/v1/inboxes/ \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{}' # Custom domain — using domain name curl -s -X POST https://api.apimail.cc/v1/inboxes/ \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{"domain": "example.com", "local_part": "hello"}' # Custom domain — using domain UUID + display name curl -s -X POST https://api.apimail.cc/v1/inboxes/ \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{"domain_id": "'$DOMAIN_ID'", "local_part": "hello", "display_name": "Hello Support"}' ``` **Message listing query params:** | Param | Type | Default | Description | |-------|------|---------|-------------| | `cursor` | string | — | Pagination cursor from previous response's `next_cursor` | | `limit` | int | 20 | Results per page (1-100) | | `q` | string | — | Full-text search across subject, sender, and body | | `unread` | bool | — | `true` for unread only, `false` for read only, omit for all | | `label` | string | — | Filter by label(s). Repeat for multiple: `?label=billing&label=urgent`. Messages must have ALL specified labels. | ### Messages | Method | Path | Scope | Description | |--------|------|-------|-------------| | GET | `/v1/messages/{id}` | read | Get a message with attachments. Add `?mark_read=true` to mark read. | | PATCH | `/v1/messages/{id}` | read | Update message properties (e.g. `{"labels": ["processed", "billing"]}`). | | GET | `/v1/messages/{id}/raw` | read | Download the original .eml file (RFC 2822 format). | | GET | `/v1/messages/{id}/attachments/{att_id}` | read | Download an attachment. | | POST | `/v1/messages/{id}/read` | read | Mark a message as read. | | POST | `/v1/messages/{id}/unread` | read | Mark a message as unread. | | DELETE | `/v1/messages/{id}` | admin | Soft-delete a message (hidden, not permanently removed). | ### Threads Threads group related messages based on email In-Reply-To and References headers. | Method | Path | Scope | Description | |--------|------|-------|-------------| | GET | `/v1/threads` | read | List threads (paginated, newest activity first). | | GET | `/v1/threads/{id}/messages` | read | Get all messages in a thread (oldest first). | | DELETE | `/v1/threads/{id}` | admin | Soft-delete all messages in a thread. | **Thread listing query params:** | Param | Type | Default | Description | |-------|------|---------|-------------| | `cursor` | string | — | Pagination cursor | | `limit` | int | 20 | Results per page (1-100) | | `inbox_id` | UUID | — | Filter threads by inbox UUID | | `inbox` | string | — | Filter threads by inbox email address (e.g. `hello@example.com`). Alternative to `inbox_id`. | ### Sending Email Send outbound email from any inbox whose domain is `send_enabled`. Requires `send` or `admin` scope. | Method | Path | Scope | Description | |--------|------|-------|-------------| | POST | `/v1/messages/send` | send | Send a new email. | | POST | `/v1/messages/reply` | send | Reply to an existing message. Auto-sets In-Reply-To, References, and Re: subject. | | POST | `/v1/messages/reply-all` | send | Reply to all recipients. Auto-derives To (original sender) and CC (all other recipients minus your address). | | POST | `/v1/messages/forward` | send | Forward a message to new recipients. Includes original body as quoted content, sets Fwd: subject. | | GET | `/v1/messages/send-jobs` | send | List send jobs (filterable by `?status=sent/failed/queued`). | | GET | `/v1/messages/send-jobs/{id}` | send | Get a single send job's status. | #### Sending a new email You can specify the sending inbox by email address (`"from"`) or UUID (`"from_inbox_id"`): ```bash # Using email address (recommended) curl -s -X POST https://api.apimail.cc/v1/messages/send \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "hello@example.com", "to": ["recipient@example.com"], "subject": "Hello from ApiMail", "body_text": "Plain text body", "body_html": "
HTML body
" }' # Using inbox UUID (also works) curl -s -X POST https://api.apimail.cc/v1/messages/send \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from_inbox_id": "'$INBOX_ID'", "to": ["recipient@example.com"], "subject": "Hello from ApiMail", "body_text": "Plain text body" }' # With attachments (base64-encoded, 10MB total limit) curl -s -X POST https://api.apimail.cc/v1/messages/send \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "hello@example.com", "to": ["recipient@example.com"], "subject": "Invoice attached", "body_text": "Please find the invoice attached.", "attachments": [ { "filename": "invoice.pdf", "content_type": "application/pdf", "content_base64": "'$(base64 -i invoice.pdf)'" } ] }' ``` Attachments are supported on `send`, `reply`, `reply-all`, and `forward` endpoints. Dangerous file types (`.exe`, `.bat`, `.js`, etc.) are blocked. Response: ```json { "id": "send-job-uuid", "status": "sent", "message_id_header": "HTML body content
", "status": "active", "received_at": "2025-01-15T10:30:00Z", "read_at": null, "size_bytes": 4521, "attachments": [ { "id": "uuid", "filename": "report.pdf", "content_type": "application/pdf", "size_bytes": 102400, "is_inline": false } ] } ``` ### Thread object ```json { "thread_id": "uuid", "inbox_id": "uuid", "subject": "Hello from ApiMail", "message_count": 3, "last_message_at": "2025-01-15T12:00:00Z", "from_addr": "sender@example.com" } ``` ### Paginated response All list endpoints return: ```json { "items": [...], "next_cursor": "base64-encoded-cursor-or-null", "has_more": true } ``` To paginate: pass the `next_cursor` value as `?cursor=` on the next request. When `has_more` is `false`, you've reached the end. ### Error response ```json { "detail": "Human-readable error message" } ``` HTTP status codes: `400` (bad request), `401` (invalid API key), `403` (insufficient scope), `404` (not found), `409` (conflict/duplicate), `422` (validation error, e.g. suppressed recipient), `429` (rate limit exceeded). ## Current Limitations - **No SDK** — use raw HTTP calls (curl, httpx, requests, fetch, etc.) ## Tips for AI Agents - **Get started instantly** — `POST /v1/inboxes/` with an empty body creates a receive-ready inbox in one call, no domain setup needed - Create a **dedicated inbox per agent or workflow** — use shared domain for quick experiments, custom domain for production - **Use email addresses instead of UUIDs** — all inbox endpoints accept `hello@yourdomain.com` as the identifier - **Use webhooks** to get notified instantly when email arrives — register a `message.received` webhook instead of polling - If you can't receive webhooks, **poll for new mail** with `GET /v1/inboxes/{email_or_id}/messages?unread=true` - Use **`?mark_read=true`** on `GET /v1/messages/{id}` to fetch and mark in one call — prevents reprocessing - **Thread IDs** group related messages — use `GET /v1/threads/{id}/messages` to get full conversation context - **`body_text`** is usually best for LLM consumption; `body_html` has richer formatting but needs parsing - **Reply to emails** with `POST /v1/messages/reply` — it auto-sets threading headers so the reply appears in the same conversation - **Use idempotency keys** on sends to safely retry without duplicate emails - Use **`GET https://api.apimail.cc/openapi.json`** to programmatically discover all endpoints and their schemas