The command line
Everything the admin API and the SaaS admin panel can change can also be changed from a shell on the server: tenants, domains, mailboxes, aliases, shared domains, plans, platform settings, DKIM keys, DMARC reports, filters, out-of-office replies, per-user limits, two-step sign-in resets, app passwords and panel administrators.
Running it
On a server installed with make install, use corpsuite as root:
sudo corpsuite help
sudo corpsuite mailbox show amy@example.com
corpsuite runs corpsuited as the service user with the service's settings, so it reaches the same database and key file. The server does not need to be running, and no platform key is needed. Anyone who can run it as root could read the database anyway.
sudo corpsuite logs shows the service log. It takes journalctl options, such as -f to follow or --since today.
In development, or wherever the CORPSUITE_* variables are already set, run corpsuited directly: corpsuited help.
From another machine, through the API
The same commands can run from your own computer, or any other machine, against a server's admin API. Copy the corpsuited binary there and tell it where the API is and where to find the platform API key:
export CORPSUITE_API_URL=https://admin.mailo.city
export CORPSUITE_API_KEY_FILE=~/.config/zappocity/platform-key
corpsuited tenant list
corpsuited user suspend amy@example.com
- The key file holds the platform API key printed by
corpsuited gen-platform-key. Keep it readable only by you (chmod 600).CORPSUITE_API_KEYworks too, and without either the command asks for the key. CORPSUITE_API_URLmust start withhttps://, except for this machine (http://localhost:…), so the key never crosses a network in clear. Redirects are not followed, so the key goes only to the server named.- Every command goes through the API, with the same checks as the panel and API clients. Mailboxes are still named by address; the command finds them through the tenants' account lists.
- Panel administrators (
admin list,admin set,admin disable) are not part of the API, so those commands work only on the server.
How commands look
corpsuite tenant add slug=acme name="Acme Inc" seatLimit:=10 plan=team
corpsuite domain request acme name=acme.com # then publish the TXT record
corpsuite domain verify acme acme.com
corpsuite domain dns acme acme.com format=zone > acme.com.zone
corpsuite mailbox add acme address=amy@acme.com displayName=Amy password=-
corpsuite mailbox limits set amy@acme.com sendPerHour:=50
corpsuite settings set hstsMaxAge:=31536000 hstsIncludeSubdomains:=true
- TENANT is a tenant's slug (such as
acme) or its id. - MAILBOX is the address the mailbox signs in with (
userstill works formailbox). - Fields are written
name=valuefor text andname:=valuefor numbers,true,false,null, lists and objects, as in JSON. name=@filetakes the text of a file, for example a filter script.name=-asks for the value without showing it, which is how to type a password. When the input is a pipe, one line is read from it instead.
Results are printed as JSON, so they can be piped to jq. Errors say what was wrong in the same words as the API, and exit with status 1.
The commands
Run corpsuite help for the full list, or corpsuite help user for one group. In short:
| Group | Commands |
|---|---|
| Tenants | tenant list, add, show, set, suspend, unsuspend, usage, queue, filter, filter set, team, team set, invites, invites add, invites revoke, keys, keys add, keys revoke, approve, reject, spam, spam set, reports, report |
| Domains | domain list, add, pending, request, verify, withdraw, show, set, remove TENANT DOMAIN confirm=DOMAIN [now=true], keep, dns, dkim, dkim add, dkim activate, dkim remove, dmarc, dmarc reports, dmarc report, filter, filter set |
| Mailboxes | mailbox list, add, show, set, suspend, unsuspend, password, limits, limits set, features, features set, signins, unlock, verify, sessions, sessions end, imports, import, import cancel, two-factor reset, app-passwords, app-passwords revoke, invites, invites set, vacation, vacation set, filters, filters add, filters show, filters set, filters remove |
| Aliases | alias list, alias add TENANT ALIAS MAILBOX, alias remove |
| Tenant templates | tenant templates TENANT, tenant template add TENANT name=... (html=... | text=...), tenant template set TENANT ID ..., tenant template delete TENANT ID |
| Rooms and equipment | tenant resources TENANT, tenant resource add TENANT address=... name=..., tenant resource set TENANT ID, tenant resource delete TENANT ID |
| Tenant address books | tenant address-books TENANT, tenant address-book add TENANT name=NAME [memberAccess=read|write], tenant address-book set TENANT BOOK-ID, tenant address-book delete TENANT BOOK-ID [removeContents:=true] |
| Mailing lists | lists TENANT, list add TENANT address=... name=... kind=..., list show LIST, list set LIST, list delete LIST, list members LIST, list member add LIST ADDRESS, list member remove LIST ADDRESS, list held LIST, list held approve LIST ID, list held discard LIST ID (LIST is the list's address; see Mailing lists) |
| Shared domains | shared-domain with the same commands as domain, plus remove |
| Plans | plan list, add, show, set, remove, entitlements |
| BIMI logos | domain bimi TENANT DOMAIN, domain bimi set TENANT DOMAIN svg=@logo.svg [certificate=@vmc.pem], domain bimi check, domain bimi remove, and shared-domain bimi ... the same way |
| Domain blocking | blog posts, blog post, blog post add, blog post set, blog post delete, blog preview, jobs, job run, job set, platform jobs, platform job run, platform job set, dns settings, dns settings set, dns zones, dns zone add, dns zone, dns zone remove, dns record add, dns record set, dns record remove, dns zone transfers set ACCOUNT-ID ZONE-ID addresses:='["192.0.2.53"]', dns zone transfers new-key, dns zone primaries set ACCOUNT-ID ZONE-ID addresses:='["192.0.2.53"]' [key:=...], dns zone refresh, dns zone delegation, access-block, access-block add, access-block remove, access-block test, domain-block, domain-block add kind=mx value=DOMAIN [action=junk], domain-block remove ID; domain-block team TENANT (add, remove); mailbox domain-block MAILBOX (add, remove) |
| Greylisting | greylist, greylist set, ip-allowlist TENANT, ip-allowlist set TENANT items:=[...]; per domain with domain set TENANT DOMAIN greylist:=true |
| Mail log | maillog [q=TEXT] [log=main|reject|panic], maillog team TENANT, maillog settings, maillog settings set days:=N |
| Accounts | accounts [q=TEXT] finds mailboxes of every tenant (the Zappocity panel's Accounts page) |
| Feedback loop | feedback, feedback set, complaints, complaints team TENANT, mailbox suppressions MAILBOX, mailbox suppression remove MAILBOX RECIPIENT, mailbox resume-sending MAILBOX |
| Automatic DNS | domain dns-automation TENANT DOMAIN, set (accessKeyId=... secretAccessKey=-, or provider=dnso for dnso.city), remove, sync; shared-domain dns-automation DOMAIN the same, with useServerCredentials:=true (see Automatic DNS) |
| Domain proof | domain prove TENANT DOMAIN marks a domain proved by hand, so nobody else can claim it |
| Invitations | invitation list, invitation add plan=PLAN [count:=N], invitation revoke CODE |
| Support | tickets, tenant tickets, tenant ticket, tenant ticket add, tenant ticket set, tenant ticket reply, mailbox notes, mailbox note |
| Shared mailboxes | shared-mailbox list TENANT, shared-mailbox add TENANT address=ADDRESS [displayName=NAME]; members with mailbox share ADDRESS TEAMMATE access=read|write|send and mailbox unshare (see Shared mailboxes) |
| Staff | staff, staff add email=EMAIL permissions:=[...] (a Zappocity account in the staff organization), staff set, staff organization, staff organization set orgId=ORG-ID, mailbox impersonate MAILBOX reason=TEXT |
| Legal | mailbox legal-export MAILBOX caseReference=REF > export.zip, legal-exports |
| Billing | billing, billing set, billing ledger, billing ledger add, billing invoices, billing invoices all, billing invoice, billing invoice pay, billing invoice void, billing run, billing coupons, billing coupon give, billing coupon remove |
| Coupons | coupons, coupon create, coupon, coupon set, coupon delete, coupon redemptions |
| Loyalty | loyalty, loyalty set, points, points add |
| Tenant roles | tenant roles, tenant role create, tenant role set, tenant role delete |
| Forums | forums, forum create, forum set, forum delete, forum topic set, forum post set, tenant forums, tenant forum create, tenant forum set, tenant forum delete, tenant wiki, tenant wiki set, tenant sites, tenant sites set (forum, wiki, webmail and tenant manager addresses), tenant sites check |
| Deleting | mailbox delete, mailbox delete now, mailbox restore, tenant deleted-users, zappocity person delete, zappocity person delete now, zappocity person delete all, zappocity person restore |
| Merging duplicates | zappocity org merge ORG-ID from=OTHER-ORG-ID, zappocity person merge PERSON-ID from=OTHER-PERSON-ID, mailbox zappocity unlink MAILBOX (a mailbox away from its Zappocity account) (see Zappocity customers) |
| Profiles and badges | badges, badge create, badge set, badge delete, badge give, badge take, profile, profile set |
| Help desk | support help categories, support help category add, support help category set, support help category delete, support help articles, support help article, support help article add, support help article set, support help article delete, support overview, support queues, support queue add, support queue set, support queue remove (with moveTo=SLUG), support queue address add, support queue address remove, support domain add, support tickets, support ticket, support ticket add, support ticket set, support ticket reply, support ticket contact add, support ticket contact remove, support followup add, support followup done, support followups, support replies, support reply add, support reply set, support reply remove, support ticket preview-reply, support ticket use-reply |
| Single sign-on | sso, sso set, sso connect, zappocity sso clients, zappocity sso client add, zappocity sso client set, zappocity sso client remove, zappocity sso client secret |
| Plans and features (Zappocity) | zappocity features PRODUCT, zappocity plan set PRODUCT PLAN ..., zappocity subscription features SUB, zappocity subscription features set SUB on:=[...] off:=[...] |
| Sharing | mailbox shares, mailbox share MAILBOX TEAMMATE access=read|write|send, mailbox unshare (see Sharing) |
| Calendar subscription links | mailbox calendar-links MAILBOX, mailbox calendar-link revoke MAILBOX LINK (see Calendars) |
| Service mail | notice-mail, notice-mail set, notice-mail test, zappocity mail, zappocity mail set, zappocity mail test, mailbox tokens, mailbox token add, mailbox token revoke |
| Zappocity | zappocity products, product set, price set, people, person, person add, person set, orgs, org, org add, org set, org member, org member remove, subscribe, subscriptions, subscription, subscription set, audit |
| Archive | tenant archive, tenant archive set, tenant archive search, tenant archive message TENANT ID > FILE.eml, tenant archive log, mailbox hold MAILBOX reason=TEXT, mailbox hold lift |
| Abuse | abuse, abuse set, abuse test, mailbox signup-checks, signup-verification, signup-verification set, signup-verification test |
| Security | security, security set (lockouts, history, alerts, how long sessions last: adminIdleMinutes:=240 and the like) |
| MTA-STS | mta-sts, mta-sts set, domain mta-sts check, shared-domain mta-sts check |
| Spam | spam, spam set, spam test, spam reports |
| Outbound IP addresses | ip-pool, ip-pool add, ip-pool set ADDRESS, ip-pool remove ADDRESS, ip-settings, ip-settings set, ip-settings publish, tenant sending-addresses TENANT (see Outbound IP addresses) |
| Storage | storage shows where message files are kept; corpsuited blobs copy-to-s3 (on the server) copies them to the bucket in CORPSUITE_S3_* |
| Settings | settings, settings set (signup switches, invitations, host names, notice address, HSTS, reserved names) |
| Filters | sieve check script=@file checks a script without saving it |
| Administrators | admin list, admin set EMAIL [NAME], admin disable EMAIL |
| Anything else | api METHOD /v1/PATH [fields] calls any route in api/openapi.yaml |
set-admin and disable-admin still work as the old names of admin set and admin disable.
zappocityd
When Zappocity runs apart from the mail server, zappocityd has three commands of its own; the operator commands above still go through corpsuited (or the API), which forwards platform calls to it.
zappocityd # serve the platform
zappocityd migrate # apply the platform's migrations and exit
zappocityd copy-data postgres://.../corpsuite # move the platform's data
copy-data copies the platform's tables from the database it shared with the mail server into its own (CORPSUITE_PLATFORM_DATABASE_URL). Stop the servers first and start this release's corpsuited once, so both sides have the same migrations. The new database must be unused: nobody signed up and nothing set up. Every table is counted and compared row by row before the copy is kept; if anything differs nothing is kept. The source is not changed, so you can go back to it.
Good to know
- On a machine without the server's key file (while setting up), commands work on the database, but nothing can be emailed: a reply to a ticket is saved and the command says the requester was not told. Run it on the server, or through the API from anywhere, to notify them.
- Commands go through the same checks as the API. A plan limit, a taken address or a bad value is refused here too.
- Changes are written to the service log as
admin: command line ...lines, like changes made in the panel. - Domains are proved before they are added:
domain requestreturns a TXT record to publish, anddomain verifyadds the domain once that record is found.domain add TENANT name=DOMAIN skipProof:=trueadds one at once without proof (logged); such a domain can still be claimed by whoever proves it, if nothing uses it. Turn the requirement off withsettings set requireDomainProof:=false(see Tenant administration). mailbox passwordsigns the mailbox out of their other sessions and mail apps, as a password change in webmail does.mailbox app-passwordsshows only names and dates. App passwords cannot be read back, only revoked.- Platform settings that the server caches, such as the webmail host name, take effect within 30 seconds.
- New DKIM keys need the key file the server uses (
CORPSUITE_KEY_FILE). When it is missing,dkim addis refused, and new domains get their first key when the server next starts.