Mail API
Install and adopt an iRedMail stack over SSH, run the mail admin panel (domains, mailboxes, stats, backups, DNS, components), and deploy webmail.
The Mail API drives Openship's self-hosted email feature: it installs an iRedMail stack on one of your servers over SSH, tracks the wizard's progress, then exposes an admin panel for the running server — domains, mailboxes, stats, backups, DNS health, and per-component control — plus a one-click webmail deploy. In the dashboard this is the Emails page.
Self-hosted only
Every route here is gated by localOnly and is never mounted in Openship Cloud — the module is
dynamically imported only on self-hosted instances. On a cloud instance these paths return 404.
Base path & auth
All paths are relative to your instance, under /api/mail — e.g. https://your-host/api/mail/status.
Send a personal access token as a bearer header (Authorization: Bearer <token>), created with
openship token create. The dashboard uses your session cookie instead. See the
API overview and auth model for details.
Every route is server-scoped
Most paths carry a :serverId (or take one in the body / ?serverId=). Openship checks that the server
belongs to your organization on every call — a mail_server grants SSH-level reach into the box, so a
serverId outside your org returns 404, indistinguishable from one that doesn't exist.
Endpoints
Setup wizard
| Method & path | Permission | What it does |
|---|---|---|
GET /api/mail/steps | mail_server:read | List all setup steps and the total count. |
GET /api/mail/status?serverId= | mail_server:read | Current setup progress for a server (rendered from its on-server state file). |
GET /api/mail/servers | mail_server:list | List every server Openship has provisioned (or is provisioning) mail on. |
DELETE /api/mail/servers/:serverId | mail_server:admin | Stop managing a server (drops the DB row only; leaves the stack + state file intact). |
POST /api/mail/scan | mail_server:write | Probe a server for an existing iRedMail install + its on-server state (read-only). |
POST /api/mail/adopt | mail_server:write | Re-adopt a scanned server whose orchestrator state was lost. |
POST /api/mail/setup | mail_server:write | Start or resume the install wizard — returns an SSE stream. |
POST /api/mail/setup/cancel | mail_server:write | Cancel the running setup. |
POST /api/mail/setup/dns-ack | mail_server:write | Mark DNS (DKIM) records as configured, releasing the DNS gate. |
POST /api/mail/setup/ptr-ack | mail_server:write | Mark reverse-DNS (PTR) as configured, releasing the PTR gate. |
POST /api/mail/setup/reset | mail_server:admin | Wipe the on-server state file (does not touch iRedMail). |
Post-install
| Method & path | Permission | What it does |
|---|---|---|
GET /api/mail/health/:serverId | mail_server:read | Live systemd status of every mail-core daemon. |
POST /api/mail/credentials/postmaster | mail_server:write | Rotate the postmaster account password. |
Admin — domains
| Method & path | Permission | What it does |
|---|---|---|
GET /api/mail/admin/:serverId/domains | mail_server:list | List mail domains on the server. |
POST /api/mail/admin/:serverId/domains | mail_server:write | Add a mail domain. |
GET /api/mail/admin/:serverId/domains/:domain | mail_server:read | Get one domain's config. |
PATCH /api/mail/admin/:serverId/domains/:domain | mail_server:write | Update a domain (quotas, limits, active flag). |
DELETE /api/mail/admin/:serverId/domains/:domain | mail_server:admin | Delete a domain (add ?cascade=true to remove its mailboxes too). |
GET /api/mail/admin/:serverId/domains/:domain/dependents | mail_server:read | Count mailboxes/aliases that depend on a domain. |
GET /api/mail/admin/:serverId/domains/:domain/dns | mail_server:read | DNS records + publish state for an additional domain. |
POST /api/mail/admin/:serverId/domains/:domain/dns/acknowledge | mail_server:write | Confirm a domain's DNS records are published. |
GET /api/mail/admin/:serverId/domains-dns/pending | mail_server:read | List additional domains whose DNS is still pending. |
Admin — mailboxes
| Method & path | Permission | What it does |
|---|---|---|
GET /api/mail/admin/:serverId/mailboxes?domain= | mail_server:list | List mailboxes in a domain. |
POST /api/mail/admin/:serverId/mailboxes | mail_server:write | Create a mailbox. |
GET /api/mail/admin/:serverId/mailboxes/:email | mail_server:read | Get one mailbox. |
PATCH /api/mail/admin/:serverId/mailboxes/:email | mail_server:write | Update a mailbox (name, password, quota, active). |
DELETE /api/mail/admin/:serverId/mailboxes/:email | mail_server:admin | Delete a mailbox (add ?hard=true to purge its maildir; default is a soft delete). |
Admin — stats, backups, DNS, components
| Method & path | Permission | What it does |
|---|---|---|
GET /api/mail/admin/:serverId/stats | mail_server:read | Aggregate counts (domains, mailboxes, storage). |
GET /api/mail/admin/:serverId/backup-policy | mail_server:read | The mail server's backup policy (or null). |
POST /api/mail/admin/:serverId/backup-policy | mail_server:admin | Create or update the backup policy. |
GET /api/mail/admin/:serverId/backup-runs | mail_server:read | Recent backup runs for the mail server. |
GET /api/mail/admin/:serverId/dns-scan?domain= | mail_server:read | Live DNS health scan (MX/SPF/DKIM/DMARC/PTR). |
POST /api/mail/admin/:serverId/test-email | mail_server:write | Send a test email from the server. |
POST /api/mail/admin/:serverId/components/restart-all | mail_server:admin | Restart every mail component. |
POST /api/mail/admin/:serverId/components/:key/:action | mail_server:admin | Run an action (start/stop/restart/…) on one component. |
GET /api/mail/admin/:serverId/components/:key/logs?lines= | mail_server:read | Tail a component's logs. |
Webmail
| Method & path | Permission | What it does |
|---|---|---|
GET /api/mail/webmail/targets?serverId= | mail_server:read | List deploy-target options (self-hosted servers / cloud). |
POST /api/mail/webmail/deploy-project | mail_server:write | Create a webmail project + deployment and start the build. |
Start (or resume) setup
Kicks off the iRedMail install wizard against a server and streams progress as
Server-Sent Events. The wizard is
idempotent: re-POST with startStep to resume from where a previous run stopped (or was gated). Only one
install may run per instance at a time.
POST /api/mail/setupProp
Type
config is an optional IRedMailConfig:
Prop
Type
curl -N -X POST https://your-host/api/mail/setup \
-H "Authorization: Bearer $OPENSHIP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"serverId":"srv_123","domain":"example.com"}'The stream emits step_start, log, and step_done events per step, plus these control events:
dns_records/dns_pending— the DKIM step publishes the records you must add, then holds. Add the records at your DNS provider and callPOST /api/mail/setup/dns-ack, then re-POST/setupwith theresumeStepfrom the event.ptr_pending— after DNS is acknowledged, the wizard holds again for reverse-DNS (PTR). Set the PTR record at your VPS provider, callPOST /api/mail/setup/ptr-ack, and resume.complete— carriesdomain,webmailUrl, andadminUrl.error— carriesmessageand (when resumable)resumeStep.
Acknowledge DNS / PTR
Both flip a single flag in the on-server state file so the wizard can move past its gate on the next /setup
POST. Both take the same body.
POST /api/mail/setup/dns-ack
POST /api/mail/setup/ptr-ackProp
Type
The same { serverId } body applies to POST /api/mail/scan, POST /api/mail/adopt,
POST /api/mail/setup/cancel, and POST /api/mail/setup/reset.
Adopt an existing install
If Openship lost its record of a mail server (rebuilt desktop, wiped DB), scan the server, then adopt it — no reinstall, nothing changes on the box.
POST /api/mail/scan
POST /api/mail/adopt# 1. Probe for iRedMail + its on-server state
curl -X POST https://your-host/api/mail/scan \
-H "Authorization: Bearer $OPENSHIP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"serverId":"srv_123"}'
# 2. Re-adopt it (idempotent)
curl -X POST https://your-host/api/mail/adopt \
-H "Authorization: Bearer $OPENSHIP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"serverId":"srv_123"}'Rotate the postmaster password
POST /api/mail/credentials/postmasterProp
Type
Add a mail domain
POST /api/mail/admin/:serverId/domainsProp
Type
PATCH /api/mail/admin/:serverId/domains/:domain accepts the same fields (all optional) plus
active (boolean).
curl -X POST https://your-host/api/mail/admin/srv_123/domains \
-H "Authorization: Bearer $OPENSHIP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"domain":"example.com","defaultQuotaMB":2048}'Create a mailbox
POST /api/mail/admin/:serverId/mailboxesProp
Type
PATCH /api/mail/admin/:serverId/mailboxes/:email accepts name, password, quotaMB, and active
(all optional). DELETE soft-deletes by default; add ?hard=true to purge the maildir.
curl -X POST https://your-host/api/mail/admin/srv_123/mailboxes \
-H "Authorization: Bearer $OPENSHIP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"localPart":"alice","domain":"example.com","password":"a-strong-password"}'Configure backups
Plugs the mail server into the general backup system — pick a destination and what to capture. Requires an existing backup destination in your organization.
POST /api/mail/admin/:serverId/backup-policyProp
Type
Send a test email
POST /api/mail/admin/:serverId/test-emailProp
Type
Deploy webmail
Creates (or reuses) a webmail-<serverId> project, queues a deployment, and starts the build. Returns
{ deploymentId, projectId } so the dashboard can redirect to /build/[deploymentId].
POST /api/mail/webmail/deploy-projectProp
Type
target is one of:
{ "kind": "self", "serverId": "srv_123" }— self-hosted on one of your Openship servers.{ "kind": "cloud" }— managed by Openship Cloud.
curl -X POST https://your-host/api/mail/webmail/deploy-project \
-H "Authorization: Bearer $OPENSHIP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mailServerId":"srv_123","hostname":"mail.example.com","target":{"kind":"self","serverId":"srv_123"}}'Errors you might see
404 — Not available
You're calling a cloud instance. The Mail API is self-hosted only; run it on an instance where CLOUD_MODE
is off.
404 — Server not found
The :serverId doesn't belong to your organization (or doesn't exist). List your mail servers with
GET /api/mail/servers to get valid IDs.
409 — Setup already running
Only one install runs per instance at a time, and per-server writes (reset, postmaster rotation) are refused
while its setup is active. Wait for it to finish, or cancel it with POST /api/mail/setup/cancel.
502 — Scan / adopt failed
Openship couldn't reach the server over SSH, or the probe threw. Check the server is online and its SSH credentials are valid, then retry.