Set up and operate a self-hosted mail server.
The Mail API drives Synq'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 on a hosted cloud instance: the module is
dynamically imported only on self-hosted instances (every Synq install). On a cloud instance these paths
return 404.
Every route is server-scoped
Most paths carry a :serverId (or take one in the body / ?serverId=). Synq 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.
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 "$SYNQ_URL/api/mail/setup" \
-H "Authorization: Bearer $SYNQ_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.
The terminal verify_reachability step reads the target's TCP listener table and then connects from the API
host to the public-DNS address on 25, 465, 587, and 993. Listener failures and off-box failures remain distinct
in the result. A measured failure blocks completion and points at both the host firewall and the provider-level
firewall/security group/security list/NSG; an unavailable probe is returned as a non-blocking warning rather
than being invented as an outage.
GET /api/mail/health/:serverId reuses one public-port sweep for 60 seconds. Pass
?refreshReachability=1 after changing firewall rules to force an immediate recheck; concurrent requests are
still coalesced into one bounded four-port probe.
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 Synq lost its record of a mail server (a rebuilt install, a wiped database), 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 "$SYNQ_URL/api/mail/scan" \
-H "Authorization: Bearer $SYNQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{"serverId":"srv_123"}'
# 2. Re-adopt it (idempotent)
curl -X POST "$SYNQ_URL/api/mail/adopt" \
-H "Authorization: Bearer $SYNQ_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 "$SYNQ_URL/api/mail/admin/srv_123/domains" \
-H "Authorization: Bearer $SYNQ_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 "$SYNQ_URL/api/mail/admin/srv_123/mailboxes" \
-H "Authorization: Bearer $SYNQ_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
Installs the webmail app from the app catalog, pointed at this mail server's IMAP/SMTP, then queues a
deployment and starts it. Returns { deploymentId, projectId } so the dashboard can redirect to
/build/[deploymentId]. Re-deploying an existing webmail reuses its project (and its stored sessions).
The IMAP/SMTP backend is derived from the mail server — mail.<domain> on 993/465 — so there is nothing
to configure here. To point a webmail at a mailbox host you run elsewhere, install the webmail app from
/apps (or use POST /api/mail/webmail/deploy-external) instead.
POST /api/mail/webmail/deploy-projectProp
Type
target is one of:
{ "kind": "self", "serverId": "srv_123" }— self-hosted on one of your Synq servers.{ "kind": "cloud" }: managed by a hosted cloud (switched off in Synq, so not offered).
curl -X POST "$SYNQ_URL/api/mail/webmail/deploy-project" \
-H "Authorization: Bearer $SYNQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mailServerId":"srv_123","hostname":"mail.example.com","target":{"kind":"self","serverId":"srv_123"}}'Replacing a pre-catalog webmail
Synq 0.6.1 and earlier shipped webmail as a prebuilt bundle rather than an image. Those installs can't be
redeployed — they have to be replaced by the catalog app. GET /api/mail/status reports one as
webmail.legacy: true, and a deploy over it is refused with 409 and "code": "LEGACY_WEBMAIL" until you
retry the same request with replaceLegacy: true.
The replace tears the old project down — container, routes, host state directory, project row — and then installs the webmail app at the same hostname. Mailboxes, mail data and every message live on the mail server and are untouched; the old webmail's sign-in sessions and per-user settings are not, so everyone signs in again. If the old install can't be removed, nothing is installed and the existing webmail keeps running.
Errors
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.
409 — LEGACY_WEBMAIL
The webmail linked to this mail server predates the catalog app, so it can only be replaced. Retry the same
deploy-project call with replaceLegacy: true once you've confirmed the operator accepts losing the old
webmail's sessions and per-user settings.
502 — Scan / adopt failed
Synq couldn't reach the server over SSH, or the probe threw. Check the server is online and its SSH credentials are valid, then retry.
Operations
| Operation | REST API |
|---|---|
| List mail aliases on a server. | GET /api/mail/admin/:serverId/aliasesmail_server:list · Self-hosted |
| Create a mail alias. | POST /api/mail/admin/:serverId/aliasesmail_server:write · Self-hosted |
| Remove a mail alias. | DELETE /api/mail/admin/:serverId/aliases/:idmail_server:admin · Self-hosted |
| Update a mail alias. | PATCH /api/mail/admin/:serverId/aliases/:idmail_server:write · Self-hosted |
The mail server's backup policy (or null). | GET /api/mail/admin/:serverId/backup-policymail_server:read · Self-hosted |
| Create or update the backup policy. | POST /api/mail/admin/:serverId/backup-policymail_server:admin · Self-hosted |
| Recent backup runs for the mail server. | GET /api/mail/admin/:serverId/backup-runsmail_server:read · Self-hosted |
Run an action (start/stop/restart/…) on one component. | POST /api/mail/admin/:serverId/components/:key/:actionmail_server:admin · Self-hosted |
| Tail a component's logs. | GET /api/mail/admin/:serverId/components/:key/logsmail_server:read · Self-hosted |
| Restart every mail component. | POST /api/mail/admin/:serverId/components/restart-allmail_server:admin · Self-hosted |
| Live DNS health scan (MX/SPF/DKIM/DMARC/PTR). | GET /api/mail/admin/:serverId/dns-scanmail_server:read · Self-hosted |
| List mail domains on the server. | GET /api/mail/admin/:serverId/domainsmail_server:list · Self-hosted |
| Add a mail domain. | POST /api/mail/admin/:serverId/domainsmail_server:write · Self-hosted |
| List additional domains whose DNS is still pending. | GET /api/mail/admin/:serverId/domains-dns/pendingmail_server:read · Self-hosted |
Delete a domain (add ?cascade=true to remove its mailboxes too). | DELETE /api/mail/admin/:serverId/domains/:domainmail_server:admin · Self-hosted |
| Get one domain's config. | GET /api/mail/admin/:serverId/domains/:domainmail_server:read · Self-hosted |
| Update a domain (quotas, limits, active flag). | PATCH /api/mail/admin/:serverId/domains/:domainmail_server:write · Self-hosted |
| Count mailboxes/aliases that depend on a domain. | GET /api/mail/admin/:serverId/domains/:domain/dependentsmail_server:read · Self-hosted |
| DNS records + publish state for an additional domain. | GET /api/mail/admin/:serverId/domains/:domain/dnsmail_server:read · Self-hosted |
| Confirm a domain's DNS records are published. | POST /api/mail/admin/:serverId/domains/:domain/dns/acknowledgemail_server:write · Self-hosted |
| Apply the mail domain's DNS plan through a configured provider. | POST /api/mail/admin/:serverId/domains/:domain/dns/applymail_server:write · Self-hosted |
| Preview the mail domain's DNS records. | GET /api/mail/admin/:serverId/domains/:domain/dns/planmail_server:read · Self-hosted |
| List inbound mail rules. | GET /api/mail/admin/:serverId/inbound-rulesmail_server:read · Self-hosted |
| Create an inbound mail rule. | POST /api/mail/admin/:serverId/inbound-rulesmail_server:write · Self-hosted |
| Remove an inbound mail rule. | DELETE /api/mail/admin/:serverId/inbound-rules/:ruleIdmail_server:write · Self-hosted |
| Update an inbound mail rule. | PATCH /api/mail/admin/:serverId/inbound-rules/:ruleIdmail_server:write · Self-hosted |
| Evaluate a message against inbound mail rules. | POST /api/mail/admin/:serverId/inbound-rules/testmail_server:write · Self-hosted |
| List mailboxes in a domain. | GET /api/mail/admin/:serverId/mailboxesmail_server:list · Self-hosted |
| Create a mailbox. | POST /api/mail/admin/:serverId/mailboxesmail_server:write · Self-hosted |
Delete a mailbox (add ?hard=true to purge its maildir; default is a soft delete). | DELETE /api/mail/admin/:serverId/mailboxes/:emailmail_server:admin · Self-hosted |
| Get one mailbox. | GET /api/mail/admin/:serverId/mailboxes/:emailmail_server:read · Self-hosted |
| Update a mailbox (name, password, quota, active). | PATCH /api/mail/admin/:serverId/mailboxes/:emailmail_server:write · Self-hosted |
| Rotate the platform mailbox credential. | POST /api/mail/admin/:serverId/platform-mailbox/rotatemail_server:admin · Self-hosted |
| Remove outbound relay configuration. | DELETE /api/mail/admin/:serverId/relaymail_server:admin · Self-hosted |
| Read outbound relay configuration. | GET /api/mail/admin/:serverId/relaymail_server:read · Self-hosted |
| Configure an outbound relay. | POST /api/mail/admin/:serverId/relaymail_server:admin · Self-hosted |
| Aggregate counts (domains, mailboxes, storage). | GET /api/mail/admin/:serverId/statsmail_server:read · Self-hosted |
| Send a test email from the server. | POST /api/mail/admin/:serverId/test-emailmail_server:write · Self-hosted |
| Re-adopt a scanned server whose orchestrator state was lost. | POST /api/mail/adoptmail_server:write · Self-hosted |
| Rotate the postmaster account password. | POST /api/mail/credentials/postmastermail_server:write · Self-hosted |
| Mail daemons, outbound delivery, and cached public-port reachability (TCP 25/465/587/993). | GET /api/mail/health/:serverIdmail_server:read · Self-hosted |
| Probe a server for an existing iRedMail install + its on-server state (read-only). | POST /api/mail/scanmail_server:write · Self-hosted |
| List every server Synq has provisioned (or is provisioning) mail on. | GET /api/mail/serversmail_server:list · Self-hosted |
| Stop managing a server (drops the DB row only; leaves the stack + state file intact). | DELETE /api/mail/servers/:serverIdmail_server:admin · Self-hosted |
| Start or resume the install wizard — returns an SSE stream. | POST /api/mail/setupmail_server:write · Self-hosted |
| Cancel the running setup. | POST /api/mail/setup/cancelmail_server:write · Self-hosted |
| Mark DNS (DKIM) records as configured, releasing the DNS gate. | POST /api/mail/setup/dns-ackmail_server:write · Self-hosted |
| Mark reverse-DNS (PTR) as configured, releasing the PTR gate. | POST /api/mail/setup/ptr-ackmail_server:write · Self-hosted |
| Wipe the on-server state file (does not touch iRedMail). | POST /api/mail/setup/resetmail_server:admin · Self-hosted |
| Current setup progress for a server (rendered from its on-server state file). | GET /api/mail/statusmail_server:read · Self-hosted |
| List all setup steps and the total count. | GET /api/mail/stepsmail_server:read · Self-hosted |
| Deploy webmail for an external mail server. | POST /api/mail/webmail/deploy-externalmail_server:write · Self-hosted |
| Create a webmail project + deployment and start the build. | POST /api/mail/webmail/deploy-projectmail_server:write · Self-hosted |
| List deploy-target options (self-hosted servers / cloud). | GET /api/mail/webmail/targetsmail_server:read · Self-hosted |