Mailhooks LogoMailhooks
Use CasesPricingDocsBlog
Use CasesPricingDocsBlog

Documentation

Getting Started

IntroductionQuick StartHow It Works

Guides

IMAP Client SetupBring Your Own StorageReal-time Notificationsn8n IntegrationDiscord IntegrationNotion IntegrationNotion CalendarCLI for Agents

API Reference

AuthenticationEmailsWebhooksInboxesDomainsReal-time (SSE)API Playground

Product

  • Features
  • Pricing

Legal

  • Terms of Service
  • Privacy Policy

Resources

  • Documentation
  • Blog
  • Discord

Contact

[email protected]

© 2026 Mailhooks. All rights reserved.

Getting Started

IntroductionQuick StartHow It Works

Guides

IMAP Client SetupBring Your Own StorageReal-time Notificationsn8n IntegrationDiscord IntegrationNotion IntegrationNotion CalendarCLI for Agents

API Reference

AuthenticationEmailsWebhooksInboxesDomainsReal-time (SSE)API Playground

Inboxes API

Virtual inboxes are scoped email addresses you can create from code — for E2E tests, CI runs, per-customer capture, or anywhere you need a mailbox that's disposable, long-lived, and programmatically accessible over HTTP, SSE, and IMAP.

Overview

Permanent by default

Omit ttlSeconds to keep an inbox forever; set it for temporary CI inboxes.

Scoped API keys

Mint a key bound to one inbox. It can read that inbox via HTTP / SSE / IMAP but nothing else on the account.

Attach hooks

Link any webhook to any number of inboxes. Each arriving email fires every hook attached to its inbox.

All inbox endpoints accept either a tenant API key (full access) or an inbox-scoped API key (read-only, bound to that inbox). Pass it via x-api-key.

Create Inbox

POST undefined/api/v1/inboxes

Request Body

FieldTypeDescription
prefixstring?Local part of the address. Used as-is when provided (e.g. "support" gives [email protected]). Auto-generated when omitted. A short suffix is appended only if the prefix is already taken.
domainIduuid?Restrict the inbox to one verified domain. Defaults to your tenant's default domain.
environmentIduuid?Environment scope. Defaults to production.
tagsstring[]?Arbitrary tags for filtering in the list endpoint.
ttlSecondsnumber?If set (60–86400), the inbox auto-deletes after this many seconds. Omit for a permanent inbox.

Example

curl -X POST undefined/api/v1/inboxes \
  -H "x-api-key: mh_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "prefix": "ci-run",
    "tags": ["ci", "auth"]
  }'

Response

{
  "id": "5983de4e-af3a-482d-bce0-608b144d47d5",
  "address": "[email protected]",
  "addressPrefix": "ci-run",
  "tags": ["ci", "auth"],
  "expiresAt": null,
  "active": true,
  "messageCount": 0,
  "createdAt": "2026-04-19T18:00:00.000Z",
  "domain": { "id": "...", "domain": "yourdomain.com" },
  "environment": { "id": "...", "name": "Production", "slug": "production" }
}

List Inboxes

GET undefined/api/v1/inboxes

Supports environmentId, domainId, tags, includeExpired, plus page & perPage. When called with an inbox-scoped key, returns only that inbox.

Get / Delete Inbox

GET
undefined/api/v1/inboxes/:inboxId

Returns the inbox metadata and stats.

DELETE
undefined/api/v1/inboxes/:inboxId

Permanently deletes the inbox and all captured messages. Requires a tenant-level key.

Messages

GET undefined/api/v1/inboxes/:inboxId/messages
GET undefined/api/v1/inboxes/:inboxId/messages/:messageId

Each message includes the parsed body and an extracted object with common auth artefacts (magic links, OTPs, verification codes).

{
  "id": "62641ed7-1f7a-4daa-bd86-144da461587f",
  "inboxId": "5983de4e-af3a-482d-bce0-608b144d47d5",
  "from": "[email protected]",
  "to": ["[email protected]"],
  "subject": "Sign in to YourApp",
  "textBody": "Your code is 135791",
  "extracted": {
    "otp": "135791",
    "verificationCode": "135791",
    "links": [],
    "codes": ["135791"]
  },
  "createdAt": "2026-04-19T18:02:21.000Z"
}

Wait for a Message

GET undefined/api/v1/inboxes/:inboxId/messages/wait

Long-poll for a matching message. Great for E2E tests: send a sign-up email, wait for it to arrive, pull the OTP or magic link, continue.

Query Params

ParamTypeDescription
timeoutnumberMax wait in ms (default 30000, max 60000).
subjectstringCase-insensitive substring match on subject.
fromstringCase-insensitive substring match on sender.
afterIduuidOnly return messages received after this message ID.
curl "undefined/api/v1/inboxes/${INBOX_ID}/messages/wait?subject=Sign%20in&timeout=30000" \
  -H "x-api-key: mh_your_api_key"

Inbox-scoped API Keys

Generate a key that can only access one inbox. Handy for shipping a read-only credential to CI, a customer-facing integration, or a per-user test runner — without granting tenant-wide access.

POST undefined/api/v1/inboxes/:inboxId/api-keys
GET undefined/api/v1/inboxes/:inboxId/api-keys
DELETE undefined/api/v1/inboxes/:inboxId/api-keys/:keyId
The key secret is returned exactly once, in the POST response under key. Store it somewhere safe — it cannot be retrieved again.
curl -X POST undefined/api/v1/inboxes/${INBOX_ID}/api-keys \
  -H "x-api-key: mh_tenant_key" \
  -H "Content-Type: application/json" \
  -d '{"name": "CI runner", "scopes": ["api", "imap"]}'

Scope enforcement

When an inbox-scoped key is used:

  • Reads to /inboxes/:id and /inboxes/:id/messages* succeed for the bound inbox only.
  • Any cross-inbox request returns 403.
  • Management routes (create / delete inboxes, manage keys, manage hooks) return 403.
  • SSE streams are filtered to inbox.message events for the bound inbox.
  • IMAP LOGIN accepts the key as the password for the inbox's address; other mailboxes are denied.

Attach Hooks

Hooks are fire-and-forget delivery targets (see the Webhooks API). Attach any hook to any number of inboxes; every arriving email fires every attached hook.

GET undefined/api/v1/inboxes/:inboxId/hooks
POST undefined/api/v1/inboxes/:inboxId/hooks
DELETE undefined/api/v1/inboxes/:inboxId/hooks/:webhookId

POST accepts either { "webhookId": "..." } to link an existing hook, or { "url": "https://..." } to create and link in one step. The same link is idempotent.

curl -X POST undefined/api/v1/inboxes/${INBOX_ID}/hooks \
  -H "x-api-key: mh_tenant_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://my-ci.com/email-events"}'

Realtime (SSE)

Open an SSE stream with an inbox-scoped key and you'll receive only inbox.message events for that inbox (plus heartbeat / connected frames).

curl -N "undefined/api/v1/realtime/events" \
  -H "x-api-key: mh_inbox_scoped_key"

IMAP Access

An inbox-scoped key with the imap scope can be used as the password for an IMAP LOGIN against your IMAP host, with the username set to the inbox's full address. Wildcards and other mailboxes are rejected.

Host:     imap.mailhooks.dev
Port:     993 (TLS)
Username: [email protected]
Password: mh_inbox_scoped_key