Developer Documentation

Inbox API

Programmatic inbox operations for automation pipelines.

Authentication

  • Method: Bearer API key (Authorization: Bearer <api_key>) or X-Api-Key.
  • Permissions:
    • inbound for POST /api/v1/mail/inbound
    • read for email retrieval endpoints
    • write for read/delete/update actions
    • webhook for webhook registration endpoints
  • inbound for POST /api/v1/mail/inbound
  • read for email retrieval endpoints
  • write for read/delete/update actions
  • webhook for webhook registration endpoints

Common errors:

Error Response

{ "status": "error", "reason": "API key is required." }

Error Response

{ "status": "error", "reason": "API key does not have webhook permission." }

Mailbox Bulk Task Behavior

For POST /api/v1/create/mailbox/bulk and the follow-up GET /api/v1/get/mailbox/bulk task polling:

  • Existing mailbox addresses are skipped automatically and do not fail the task.
  • Task completion still returns status=completed when every requested address already exists.
  • In that all-existing case, total_email is 0 and results is empty.

Inbound Ingestion

POST /api/v1/mail/inbound

Receives inbound email payload from upstream mail provider.

Required request fields:

  • message_id string (unique per mailbox)
  • from_email string email
  • to_email string email
  • subject string
  • received_at ISO timestamp

Optional:

  • from_name, cc[], bcc[], body_text, body_html, headers, in_reply_to, references[]
  • attachments[] (filename, mime_type, size, and either content base64 or content_url)

Request Example

curl -X POST "$APP_URL/api/v1/mail/inbound" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message_id": "msg-12345",
    "from_email": "sender@example.com",
    "to_email": "support@mails.now",
    "subject": "Need help",
    "body_text": "Hello team",
    "received_at": "2026-04-27T12:10:00Z"
  }'

Success Response

{
  "status": "accepted",
  "message_id": "msg-12345"
}

Error examples:

  • 401 invalid/missing API key
  • 403 missing inbound permission
  • 422 unknown mailbox or validation error

Mailcow IMAP bridge rollout

IMAP polling ingests mail in-process in the application that runs the poller (mailbridge:poll-inbound): messages are normalized and dispatched without an HTTP loopback or bridge-specific API keys.

  1. Set MAILBRIDGE_POLL_ENABLED=true when you want Laravel’s scheduler to run mailbridge:poll-inbound every 30 minutes (see routes/console.php).
  2. Set MAILBRIDGE_QUEUE=inbound and run a queue worker that listens to the inbound queue (recommended: a dedicated Supervisor program separate from default / mailcow workers).
  3. Only mailboxes with Active status and Ready provisioning (provisioning_status = active) that have IMAP credentials or a stored password are scheduled for polling.
  4. Run php artisan mailbridge:poll-inbound manually and inspect bridge logs/dead-letter rows until behavior is stable.
  5. Roll out mailbox-by-mailbox (e.g. --mailbox=ID) before relying on scheduler-driven polling for all boxes.
  6. Users can still trigger an immediate poll from the Inbox Refresh button; those jobs are queued on inbound for all pollable active mailboxes in their tenant.

Tenant integrations may still POST to /api/v1/mail/inbound with a normal workspace API key and inbound permission—that path is unrelated to IMAP polling.

Dead-letter replay:

Example

php artisan mailbridge:replay-deadletters --id=123
php artisan mailbridge:replay-deadletters --mailbox=10
php artisan mailbridge:replay-deadletters --all

Email Retrieval API

GET /api/v1/emails

Query params:

  • mailbox_id
  • unread=true|false
  • since=ISO_TIMESTAMP
  • before=ISO_TIMESTAMP
  • q=keyword
  • cursor pagination via cursor

Example:

Request Example

curl -H "Authorization: Bearer $API_KEY" \
  "$APP_URL/api/v1/emails?mailbox_id=10&unread=true&q=invoice"

Response:

Success Response

{
  "data": [
    {
      "id": 101,
      "mailbox_id": 10,
      "thread_id": 44,
      "message_id": "msg-123",
      "from_email": "billing@example.com",
      "to_email": "ops@mails.now",
      "subject": "Invoice",
      "body_text": "Please review",
      "body_html": "<p>Please review</p>",
      "is_read": false,
      "is_deleted": false,
      "is_spam": false,
      "spam_score": 0,
      "received_at": "2026-04-27T10:00:00Z",
      "updated_at": "2026-04-27T10:00:01Z",
      "attachments": []
    }
  ],
  "meta": {
    "next_cursor": null,
    "prev_cursor": null
  }
}

GET /api/v1/emails/{id}

Returns one email scoped to tenant.

GET /api/v1/emails/sync?since=ISO_TIMESTAMP

Returns only records where updated_marker_at >= since for incremental polling clients.

POST /api/v1/emails/{id}/read

Marks email read and emits email.read webhook event.

Request Example

curl -X POST -H "Authorization: Bearer $API_KEY" \
  "$APP_URL/api/v1/emails/101/read"

DELETE /api/v1/emails/{id}

Soft-deletes email and emits email.deleted.

GET /api/v1/emails/{id}/attachments/{attachment_id}

Returns a short-lived signed URL.

Response:

Success Response

{
  "data": {
    "url": "https://app.test/api/v1/downloads/attachments/9?expires=...",
    "expires_at": "2026-04-27T12:15:00Z"
  }
}

Webhook Management API

POST /api/v1/webhooks

Registers tenant webhook endpoint.

Payload:

  • url (required URL)
  • events[] (email.received, email.read, email.deleted)
  • is_active optional bool

Example:

Request Example

curl -X POST "$APP_URL/api/v1/webhooks" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://client.example/webhooks/email","events":["email.received","email.deleted"]}'

GET /api/v1/webhooks

Lists current tenant webhook endpoints.

PATCH /api/v1/webhooks/{id}

Updates url/events/is_active.

DELETE /api/v1/webhooks/{id}

Deletes webhook.

Outbound Webhook Delivery Format

Every webhook request body:

Success Response

{
  "event": "email.received",
  "timestamp": 1777290021,
  "data": {
    "email_id": 101,
    "mailbox_id": 10
  }
}

Headers:

  • X-Webhook-Signature: HMAC_SHA256(timestamp + "." + raw_json_payload, webhook_secret)
  • X-Webhook-Timestamp: unix timestamp

Delivery is queued and retried with exponential backoff.