Mailbox API for mailcow — installation and API guide

Installation

  1. The module ships in mailcow Toolkit 0.5.0 or newer (one package, one license per product). On the mailcow host, as root, run the Toolkit installer as usual: sudo ./install.sh --mailcow-dir /opt/mailcow-dockerized --name <instance>. It copies data/web/mlw-mailbox-api/, adds the menu item, the host agent job and the once-a-minute timer, and reloads php-fpm.
  2. Open System → Mailbox API in the mailcow admin menu. The page shows a license banner and, below it, the settings.

License

Open System → Updates (Toolkit) and find the Mailbox API for mailcow card: paste the key and activate it, or start the 30-day trial. On the shell the same is possible with php /web/mlw-toolkit/cli.php activate mailcow-mailbox-api <KEY> inside php-fpm-mailcow. The license is checked daily; without internet the module works for 7 days after the last good check.

Without a valid license the API answers 503 not_licensed, the timer sends nothing, and the page says the license is missing. Nothing is deleted.

Settings

In System → Mailbox API:

  • API on: turns the API on or off (the license is checked anyway).
  • Public API address: the address used in signed attachment links, for example https://mail.example.com/mlw-mailbox-api/. Leave it empty to derive it from the page.
  • Messages per page (1–200) and largest attachment (1–100 MB).
  • Telegram bot token: from @BotFather; stored encrypted (only the last four characters are shown). One bot serves all mailboxes.
  • Dovecot master user (optional): only if you set your own in mailcow.conf; by default the module reads the master credentials mailcow creates at start.

API keys

In the API keys card: give the key a name, tick the rights — read, send, organize — choose the mailboxes it may use, and press Create key. The key (mbx_…) is shown once; store it. The list shows the prefix, rights, mailboxes and last use; Revoke disables it immediately. Only the SHA-256 is kept in the database.

Send it on every call:

Authorization: Bearer mbx_…

API reference

All routes are called on https://<mail host>/mlw-mailbox-api/?r=<route> (mailcow's nginx has no PATH_INFO, so the route is in the query string). Answers are JSON. Every route except the signed attachment link needs the key, and the key must allow the mailbox and the right.

Method and route Right What it does
GET /v1/me any Key name, rights, mailboxes, limits
GET /v1/mailboxes any The allowed mailboxes, their folder count and INBOX UIDNEXT
GET /v1/folders&mailbox=… read Folder names
GET /v1/messages&mailbox=…&folder=INBOX&limit=20&offset=0&q=…&unseen=1 read Message list (newest first): uid, subject, from, to, date, size, seen
GET /v1/messages/{uid}&mailbox=…&folder=… read Full message: text, HTML, headers, attachments (name, type, size, signed url)
POST /v1/messages/{uid}/read body {"read":true} organize Mark read or unread
POST /v1/messages/{uid}/move body {"folder":"Archive"} organize Move to another folder
DELETE /v1/messages/{uid}&mailbox=…&folder=… organize Delete (expunges)
POST /v1/send&mailbox=… body {"to":"a@b.c","subject":"…","text":"…"} send Send as the mailbox (cc, bcc, html, reply_to supported); a copy goes to Sent
GET /v1/attachments&…&t=… signed link Download an attachment

Examples:

curl -H "Authorization: Bearer $KEY" \
  "https://mail.example.com/mlw-mailbox-api/?r=/v1/messages&mailbox=office@example.com&limit=5&unseen=1"

curl -X POST -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{"to":"customer@example.org","subject":"Your invoice","text":"Attached."}' \
  "https://mail.example.com/mlw-mailbox-api/?r=/v1/send&mailbox=office@example.com"

Errors are JSON with an HTTP status: 401 missing or wrong key, 403 the key has no right on that mailbox, 404 unknown route or message, 422 invalid input, 429/502 limits or a server error.

Webhooks

In the Monitored mailboxes card add a mailbox, the folder to watch (default INBOX), an https webhook address and, optionally, a Telegram chat id. The module checks every minute for new messages (UIDNEXT per folder; the first run only sets the starting point, no old mail is replayed).

Every new message produces one JSON POST:

{
  "event": "message.received",
  "mailbox": "office@example.com",
  "folder": "INBOX",
  "uid": 1234,
  "received_at": "2026-10-18T06:12:00+00:00",
  "message": {
    "message_id": "…", "date": "…", "from": "Billing <billing@example.com>", "to": "…", "cc": "",
    "subject": "Invoice 42", "size": 12345, "text": "…", "html": "…", "headers": {"…": "…"},
    "attachments": [{"name": "invoice.pdf", "type": "application/pdf", "size": 23112,
      "sha256": "…", "url": "https://mail.example.com/mlw-mailbox-api/?r=/v1/attachments&…&t=…"}]
  }
}

Verify the signature header before using the body:

X-MLW-Signature: t=1792300000,v1=<hex HMAC-SHA256(secret, "t.body")>
X-MLW-Event: message.received

Compare with your secret (each monitor has its own, generated on first use: the page shows it when the monitor is created and with the Secret button) and reject timestamps older than five minutes. Failed deliveries are retried after 30 s, 1 min, 2 min … up to five attempts; the Recent deliveries card shows status and answer. Deliveries are queued in the database and sent by the once-a-minute timer.

Telegram

Paste the bot token under Settings, then put the chat id (a user, a group or a channel; the bot must be a member and, for channels, an administrator) on each monitored mailbox. Telegram next to a mailbox sends a test message. The notification shows the sender, the subject and the first 400 characters of the text.

Troubleshooting

  • 503 not_licensed: activate the license (System → Updates (Toolkit)); without internet the module works for 7 days after the last check.
  • 502 server_error with "no dovecot master credentials": mailcow did not create /etc/sogo/sieve.creds; check that dovecot-mailcow and sogo-mailcow run, or set the master user and password by hand.
  • 403 not_allowed: the key does not cover that mailbox or lacks the right; edit the key (create a new one) in the admin page.
  • No webhook arrives: check the monitor is active, the address is https and answers 2xx, and look at Recent deliveries; the timer runs every minute (systemctl status mlw-mailcow-mailapi-<name>.timer).
  • No old mail after adding a monitor: by design, the first run only records the current position; new mail from then on is delivered.

Download requires a license or the free 30-day trial.