Mailboxes
Real mailboxes on your own domain: read them in Thunderbird, Outlook, Apple Mail or any IMAP/POP3 client, or in the browser at mail.poststack.dev, and send from them over SMTP.
Before you start#
A mailbox can be created only when all of these are true:
- Your team is on a paid plan. On the free plan
POST /mailboxesanswers403. - The domain is verified and has inbound email enabled (see Inbound email).
- The domain’s MX record points at
inbound.poststack.dev, so mail for the address actually reaches us.
Each mailbox costs €1 a month and includes 1 GB of storage. Storage above that is €1 per GB per month, counted per mailbox and rounded up to the next whole GB. Both are charged as a prorated payment before the mailbox (or the larger quota) is written, so a declined card creates nothing and you get a 402 instead. Deleting a mailbox or lowering a quota banks the difference as a credit.
Connect a mail client#
Enter these settings by hand — it is the path that works in every client:
| Protocol | Server | Port | Security |
|---|---|---|---|
| IMAP (incoming) | poststack.dev | 993 | SSL/TLS |
| POP3 (incoming) | poststack.dev | 995 | SSL/TLS |
| SMTP (outgoing) | poststack.dev | 465 | SSL/TLS |
| SMTP (alternative) | poststack.dev | 587 | STARTTLS (implicit TLS also accepted) |
Username- The full email address, e.g.
support@yourdomain.com— not just the part before the@. Password- The mailbox password (not your PostStack login). Authentication method: normal password (
PLAINorLOGIN), the same for incoming and outgoing.
Only encrypted ports are reachable from the internet: plain IMAP on 143 and plain POP3 on 110 are not offered, and there is no STARTTLS upgrade on 993 or 995 — the connection is TLS from the first byte. TLS 1.2 is the minimum.
The mail server allows 20 simultaneous IMAP connections and 10 POP3 connections per mailbox from one IP address. A household or office behind one router shares that allowance, so a client that opens a connection per folder can hit it.
Automatic setup#
Clients that discover settings for themselves find them in three places. None of them needs a record you add by hand beyond the MX.
- Thunderbird (and clients using its format) look up autoconfig on the address’s domain, find nothing there, then follow the MX: because it points at
inbound.poststack.dev, they fetchhttps://autoconfig.poststack.dev/mail/config-v1.1.xml, which describes the settings above with IMAP 993, POP3 995 and SMTP 465. The same document is athttps://poststack.dev/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress=you@yourdomain.comfor provisioning tools. - SRV records (RFC 6186). While a domain has at least one active mailbox, its DNS records list
_imaps._tcp→0 1 993 poststack.devand_submissions._tcp→0 1 465 poststack.dev. They are removed again when the last active mailbox is deleted. Publish them if your DNS is not managed by PostStack. - Outlook-style autodiscover XML is served at
https://poststack.dev/autodiscover/autodiscover.xml.
Apple Mail reads none of these; enter the settings manually.
Sending from a mail client#
Outgoing mail goes through the same pipeline as the SMTP relay and the API, so it is DKIM-signed for your domain, checked against suppressions and shows up in the dashboard’s email log. A mailbox login differs from an API-key login in four ways:
- You can only send as yourself. The
Fromaddress must be exactly the mailbox address (case-insensitive). Anything else is refused with550 From address must be support@yourdomain.com. To send as another address, create a mailbox for it. - 60 messages per hour per mailbox. Past that, the relay answers with a temporary
450 Rate limit exceeded. Try again later.and your client retries later. The window is a fixed hour starting at the first message. - A copy lands in Sent. The server files what you sent into the mailbox’s
Sentfolder, so turn off “save a copy of sent messages” in your client if you see duplicates. - No signature is added. Your mail client already appends its own, so the mailbox signature is only used by the API, webmail and agent replies.
Attachments follow the API’s limits: at most 10 files, 10 MB per file and 25 MB in total. After 10 failed logins in 15 minutes from one IP address or for one username, further attempts are refused for the rest of that window.
Webmail#
Every mailbox can be read in the browser at mail.poststack.dev, which redirects to poststack.dev/mail. The person signs in with the full address and the mailbox password and sees only that mailbox; they need no PostStack account, and the sign-in cannot reach API keys, billing, domains or any other mailbox.
Changing the mailbox password — from the dashboard, the API or webmail itself — signs out every browser that was using the old one. Each webmail session is bound to the password it was opened with. Team members with access to the mailbox can also open the same webmail from the dashboard.
Connected accounts
From the account menu in webmail, a mailbox user can connect up to five other email accounts and read and send from them in the same place. There are presets for t-online.de, GMX, WEB.DE, Yahoo, iCloud, mailbox.org, Posteo, IONOS and STRATO, and any other provider that offers IMAP and SMTP can be entered by hand. Mail is read from the provider while it is being viewed; sending uses the provider’s own SMTP server. The account password is stored encrypted and never returned, and only the mailbox that connected an account can open it.
Gmail and Outlook.com / Hotmail accounts cannot be connected: Google requires its own sign-in, which PostStack does not support, and Microsoft no longer accepts passwords from other mail services.
Connected accounts are managed only from webmail and the dashboard. An API key or OAuth token asking for them gets a 404, because they belong to one person rather than to the team.
Storage#
quotaBytes sets the limit, in bytes, from 1 GB (1073741824, the default) to 50 GB (53687091200). A value outside that range is a 400. The mail server enforces it: once a mailbox is full, new mail for it is refused. usedBytes on the mailbox shows how much is in use, as last measured by a periodic sweep.
Raising the quota charges for the extra GB before the change is saved; lowering it takes effect immediately and credits the difference.
Forwarding#
Set forwardTo to 1–10 comma-separated addresses (1024 characters at most) and every message the mailbox receives is also sent there. Send null or "" to turn it off — there is no separate on/off flag.
forwardMode"relay" | "wrapped"defaultrelayrelaysends the message on unchanged — same subject, bodies and attachments, with open and click tracking forced off so links are not rewritten.wrappedsends it as a forward, with aFwd:subject and the original headers quoted in the body.forwardKeepCopybooleandefaulttrue- When
false, a message that was forwarded successfully is not also delivered to this mailbox. If the forward fails, the message is still delivered.
Either way the forwarded copy is sent from the mailbox address (with the original sender’s display name), because only your domain can pass SPF and DKIM at the destination;Reply-To is set to the original Reply-To, or the original sender when there is none, so replies reach them. Forwarding skips auto-replies (Auto-Submitted), bulk and list mail (Precedence: bulk | junk | list), a destination equal to the mailbox itself, and anything that has already been forwarded three times — so two mailboxes pointed at each other cannot loop. At most 10 attachments are carried.
Signatures#
A mailbox can own its sign-off. Set signatureHtml and signatureText and every message sent from that address through the API, webmail or an agent reply carries them. The lookup also matches plus-addresses: mail from support+invoice-42@yourdomain.com uses the support@ signature unless a mailbox exists at the tagged address itself.
- Placement. It is appended to the end of the body — on a reply, below the quoted original, so the recipient’s client cuts it off with the quote instead of sending it back. Put
{{signature}}in a body or template to place it yourself; then nothing is appended. - Opting out. Send with
"signature": "none"to leave it off one message. - Never signed: broadcasts (they carry their own footer), SMTP submissions (the mail client signs), and bounces or other mail PostStack generates itself.
signatureHtmlstring | null- At most 8 KB. Sanitised when you save it, not on each send: scripts, event handlers, form controls,
<style>blocks, remote stylesheets and unsafe URL schemes are removed, so reading it back can return less than you sent — and what is stored is exactly what goes out. Use inline CSS. signatureTextstring | null- At most 2 KB. Leave out the
--delimiter line; it is added on send. When absent, a text rendering of the HTML is used, which loses URLs hidden behind link text.
Send null (or "") to clear either one; omitting the field leaves it alone. Don’t build a signature out of only a logo — most clients block remote images until the reader allows them — and avoid hard-coded text colours, which can disappear in dark mode.
curl -X PATCH https://api.poststack.dev/mailboxes/mb_V1StGXR8Z5jdHi6BmyT \
-H "Authorization: Bearer $POSTSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"signatureHtml": "<p style=\"margin:0\"><strong>Anna Berg</strong><br />Support · Acme ApS</p>",
"signatureText": "Anna Berg\nSupport · Acme ApS"
}'await client.mailboxes.update('mb_V1StGXR8Z5jdHi6BmyT', {
signatureHtml: '<p style="margin:0"><strong>Anna Berg</strong><br />Support · Acme ApS</p>',
signatureText: 'Anna Berg\nSupport · Acme ApS',
});Mailbox endpoints#
Base URL https://api.poststack.dev. Every endpoint needs a full_access API key (or an OAuth token with mailboxes:manage) in Authorization: Bearer …. Mailboxes are addressed by their mb_… publicId; the numeric id is not accepted in URLs. Errors come back as { "error": "…" }.
Create a mailbox#
Creates the mailbox and charges for it first. It can sign in as soon as this returns.
https://api.poststack.dev/mailboxesimport { PostStack } from '@poststack.dev/sdk';
const client = new PostStack(process.env.POSTSTACK_API_KEY!);
// Returns the mailbox itself (the SDK unwraps { mailbox }).
const mailbox = await client.mailboxes.create({
domainId: 12,
localPart: 'support',
password: 'correct-horse-battery',
displayName: 'Support Team',
});curl -X POST https://api.poststack.dev/mailboxes \
-H "Authorization: Bearer $POSTSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"domainId":"dom_4f8a2c9e1b","localPart":"support","password":"correct-horse-battery"}'{
"domainId": "dom_4f8a2c9e1b",
"localPart": "support",
"password": "correct-horse-battery",
"displayName": "Support Team"
}{
"mailbox": {
"id": 42,
"publicId": "mb_V1StGXR8Z5jdHi6BmyT",
"teamId": 7,
"domainId": 12,
"emailAddress": "support@yourdomain.com",
"assignedUserId": null,
"displayName": "Support Team",
"quotaBytes": 1073741824,
"status": "active",
"webhookEnabled": true,
"forwardTo": null,
"forwardMode": "relay",
"forwardKeepCopy": true,
"signatureHtml": null,
"signatureText": null,
"sentCount": 0,
"lastLoginAt": null,
"inboxUnread": null,
"inboxUnreadFetchedAt": null,
"usedBytes": null,
"createdAt": "2026-09-29T09:12:44.118Z",
"updatedAt": "2026-09-29T09:12:44.118Z"
}
}Body parameters
domainIdstring | integerrequired- The domain, as its
dom_…publicId or numeric id. Must be verified with inbound enabled. localPartstringrequired- The part before the
@. 1–64 characters ofa–z A–Z 0–9 . _ % + -. passwordstringrequired- 8 characters to 72 bytes (UTF-8).
displayNamestring- Up to 255 characters.
quotaBytesintegerdefault1073741824- 1 GB to 50 GB, in bytes. Storage above 1 GB is billed per GB.
webhookEnabledbooleandefaulttrue- Whether mail to this mailbox fires the email.inbound webhook. false still stores and delivers the mail.
Response fields
mailbox.idinteger- Numeric id. Not accepted in URLs — use publicId.
mailbox.publicIdstringmb_…id. Every/mailboxes/:idroute takes this.mailbox.teamIdinteger- The team that owns the mailbox.
mailbox.domainIdinteger- Numeric id of the domain the address is on.
mailbox.emailAddressstring- The full address, which is also the login username.
mailbox.assignedUserIdinteger | null- The team member the mailbox belongs to, or null for a shared team mailbox. Set from the dashboard.
mailbox.displayNamestring | null- Friendly name.
mailbox.quotaBytesinteger- Storage limit in bytes.
mailbox.status"active" | "suspended"- Only an active mailbox can sign in.
mailbox.webhookEnabledboolean- Whether mail to this mailbox fires the email.inbound webhook. When false the mail is still stored and delivered; only the webhook is skipped.
mailbox.forwardTostring | null- Comma-separated auto-forward destinations, or null when forwarding is off.
mailbox.forwardMode"relay" | "wrapped"- How forwarded mail is built. See Forwarding.
mailbox.forwardKeepCopyboolean- Whether a forwarded message is also delivered to this mailbox.
mailbox.signatureHtmlstring | null- Sanitised HTML signature.
mailbox.signatureTextstring | null- Plain-text signature.
mailbox.sentCountinteger- Messages this mailbox has sent over SMTP or from webmail.
mailbox.lastLoginAtstring (ISO 8601) | null- Last webmail sign-in. IMAP, POP3 and SMTP logins do not update it.
mailbox.inboxUnreadinteger | null- Unread messages in INBOX, cached by a periodic sweep. null until the first sweep.
mailbox.inboxUnreadFetchedAtstring (ISO 8601) | null- When inboxUnread was last refreshed.
mailbox.usedBytesinteger | null- Storage in use, cached by the same sweep from the mail server’s quota. null until measured.
mailbox.createdAtstring (ISO 8601)- Creation time.
mailbox.updatedAtstring (ISO 8601)- Last change.
Status codes
| Status | Meaning |
|---|---|
| 201 | Created. The body is the new mailbox. |
| 400 | Validation failed, or the domain is not verified / has inbound disabled. |
| 401 | Missing or invalid API key. |
| 402 | No paid subscription with a payment method, the subscription is not active, or the card was declined. |
| 403 | insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended. |
| 403 | Free plan (mailboxes need a paid plan). |
| 404 | Domain not found on this team. |
| 409 | A mailbox or alias with this address already exists. |
| 429 | More than 10 requests a minute to this endpoint from the same key. Retry-After says how many seconds to wait. |
| 503 | Billing could not be reached. Nothing was created; retry. |
List mailboxes#
Your team's mailboxes, newest first. Deleted mailboxes are not included.
https://api.poststack.dev/mailboxesconst { data, meta } = await client.mailboxes.list({ page: 1, per_page: 50 });curl "https://api.poststack.dev/mailboxes?per_page=50" \
-H "Authorization: Bearer $POSTSTACK_API_KEY"{
"data": [
{
"id": 42,
"publicId": "mb_V1StGXR8Z5jdHi6BmyT",
"emailAddress": "support@yourdomain.com",
"displayName": "Support Team",
"quotaBytes": 1073741824,
"usedBytes": 18350080,
"status": "active",
"inboxUnread": 3,
"createdAt": "2026-09-29T09:12:44.118Z",
"domain": { "id": 12, "name": "yourdomain.com" }
}
],
"meta": { "page": 1, "perPage": 20, "total": 1, "totalPages": 1 }
}Query parameters
pageintegerdefault1- Page number, from 1.
per_pageintegerdefault20- 1–100.
domain_idstring- Only mailboxes on this domain (
dom_…or numeric id). A domain that is not yours returns an empty list, not an error.
Response fields
data[]object[]- Mailboxes, with the same fields as
mailboxabove, plusdomain: { id, name }. meta.pageinteger- Current page.
meta.perPageinteger- Page size used.
meta.totalinteger- Total mailboxes matching.
meta.totalPagesinteger- Number of pages.
Status codes
| Status | Meaning |
|---|---|
| 200 | The page of mailboxes. |
| 400 | Invalid page or per_page. |
| 401 | Missing or invalid API key. |
| 403 | insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended. |
Get a mailbox#
One mailbox by its publicId.
https://api.poststack.dev/mailboxes/:idconst mailbox = await client.mailboxes.get('mb_V1StGXR8Z5jdHi6BmyT');curl https://api.poststack.dev/mailboxes/mb_V1StGXR8Z5jdHi6BmyT \
-H "Authorization: Bearer $POSTSTACK_API_KEY"{
"mailbox": {
"id": 42,
"publicId": "mb_V1StGXR8Z5jdHi6BmyT",
"teamId": 7,
"domainId": 12,
"emailAddress": "support@yourdomain.com",
"assignedUserId": null,
"displayName": "Support Team",
"quotaBytes": 1073741824,
"status": "active",
"webhookEnabled": true,
"forwardTo": null,
"forwardMode": "relay",
"forwardKeepCopy": true,
"signatureHtml": null,
"signatureText": null,
"sentCount": 0,
"lastLoginAt": null,
"inboxUnread": null,
"inboxUnreadFetchedAt": null,
"usedBytes": null,
"createdAt": "2026-09-29T09:12:44.118Z",
"updatedAt": "2026-09-29T09:12:44.118Z"
}
}Path parameters
idstringrequired- The
mb_…publicId.
Response fields
mailboxobject- The mailbox, with the fields listed under Create a mailbox.
Status codes
| Status | Meaning |
|---|---|
| 200 | The mailbox. |
| 401 | Missing or invalid API key. |
| 403 | insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended. |
| 404 | No such mailbox on this team, or it has been deleted. |
Update a mailbox#
Changes only the fields you send. A larger quota is charged before it is saved.
https://api.poststack.dev/mailboxes/:idconst mailbox = await client.mailboxes.update('mb_V1StGXR8Z5jdHi6BmyT', {
status: 'suspended',
});curl -X PATCH https://api.poststack.dev/mailboxes/mb_V1StGXR8Z5jdHi6BmyT \
-H "Authorization: Bearer $POSTSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status":"suspended"}'{
"displayName": "Customer Support",
"forwardTo": "anna@example.com,ops@example.com",
"forwardKeepCopy": true
}{
"mailbox": {
"publicId": "mb_V1StGXR8Z5jdHi6BmyT",
"emailAddress": "support@yourdomain.com",
"displayName": "Customer Support",
"forwardTo": "anna@example.com,ops@example.com",
"forwardMode": "relay",
"forwardKeepCopy": true,
"status": "active",
"updatedAt": "2026-09-29T10:02:11.540Z"
}
}Path parameters
idstringrequired- The
mb_…publicId.
Body parameters
displayNamestring | null- Up to 255 characters; null clears it.
quotaBytesinteger- 1 GB to 50 GB, in bytes.
status"active" | "suspended"- A suspended mailbox cannot sign in over IMAP, POP3, SMTP or webmail. Set active to restore it.
webhookEnabledboolean- false stops the email.inbound webhook for mail to this mailbox; true resumes it.
forwardTostring | null- 1–10 comma-separated addresses, max 1024 characters. null or "" turns forwarding off.
forwardMode"relay" | "wrapped"- See Forwarding.
forwardKeepCopyboolean- Also deliver forwarded mail to this mailbox.
signatureHtmlstring | null- Max 8 KB, sanitised on save. null clears it.
signatureTextstring | null- Max 2 KB. null clears it.
assignedUserIdinteger | null- Give the mailbox to one team member (null makes it shared). Only a team owner or admin signed in to the dashboard can set this; an API key gets 403.
Response fields
mailboxobject- The updated mailbox.
Status codes
| Status | Meaning |
|---|---|
| 200 | Updated. |
| 400 | Validation failed, or assignedUserId is not a member of the team. |
| 401 | Missing or invalid API key. |
| 402 | A quota raise could not be paid for. |
| 403 | insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended. |
| 403 | assignedUserId set with an API key or OAuth token, or by a member who is not an owner/admin. |
| 404 | No such mailbox on this team. |
| 429 | More than 10 requests a minute to this endpoint from the same key. Retry-After says how many seconds to wait. |
| 503 | Billing could not be reached for a quota raise. Nothing changed. |
Change a mailbox password#
Sets a new password. Mail clients must be given it, and every webmail session signed in with the old one ends.
https://api.poststack.dev/mailboxes/:id/passwordawait client.mailboxes.changePassword('mb_V1StGXR8Z5jdHi6BmyT', 'new-correct-horse-battery');curl -X POST https://api.poststack.dev/mailboxes/mb_V1StGXR8Z5jdHi6BmyT/password \
-H "Authorization: Bearer $POSTSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"password":"new-correct-horse-battery"}'{
"password": "new-correct-horse-battery"
}{
"success": true
}Path parameters
idstringrequired- The
mb_…publicId.
Body parameters
passwordstringrequired- 8 characters to 72 bytes (UTF-8).
Response fields
successboolean- Always true on 200.
Status codes
| Status | Meaning |
|---|---|
| 200 | Changed. |
| 400 | Password too short or longer than 72 bytes. |
| 401 | Missing or invalid API key. |
| 403 | insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended. |
| 404 | No such mailbox on this team. |
| 429 | More than 10 requests a minute to this endpoint from the same key. Retry-After says how many seconds to wait. |
Delete a mailbox#
Marks the mailbox deleted: it can no longer sign in, disappears from lists, and its billing stops. The address cannot be reused.
https://api.poststack.dev/mailboxes/:idawait client.mailboxes.delete('mb_V1StGXR8Z5jdHi6BmyT');curl -X DELETE https://api.poststack.dev/mailboxes/mb_V1StGXR8Z5jdHi6BmyT \
-H "Authorization: Bearer $POSTSTACK_API_KEY"{
"success": true
}Path parameters
idstringrequired- The
mb_…publicId.
Response fields
successboolean- Always true on 200.
Status codes
| Status | Meaning |
|---|---|
| 200 | Deleted (also for a mailbox that was already deleted). |
| 401 | Missing or invalid API key. |
| 403 | insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended. |
| 404 | No such mailbox on this team. |
| 429 | More than 10 requests a minute to this endpoint from the same key. Retry-After says how many seconds to wait. |
Unread counts#
Cached INBOX unread counts for every active mailbox on the team, in one call.
https://api.poststack.dev/mailboxes/unread-summarycurl https://api.poststack.dev/mailboxes/unread-summary \
-H "Authorization: Bearer $POSTSTACK_API_KEY"{
"items": [
{ "mailboxId": 42, "mailboxPublicId": "mb_V1StGXR8Z5jdHi6BmyT", "inboxUnread": 3 }
]
}Response fields
items[].mailboxIdinteger- Numeric mailbox id.
items[].mailboxPublicIdstringmb_…id.items[].inboxUnreadinteger | null- Unread in INBOX at the last sweep; null if not yet measured.
Status codes
| Status | Meaning |
|---|---|
| 200 | The counts. |
| 401 | Missing or invalid API key. |
| 403 | insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended. |
Aliases#
An alias is an extra address that delivers into an existing mailbox — for example info@yourdomain.com into support@yourdomain.com. It costs nothing and has no password; to send as the alias address you need a mailbox of its own.
Create an alias#
Adds an address that delivers into a mailbox on the same team.
https://api.poststack.dev/mailboxes/aliasesconst alias = await client.mailboxes.createAlias({
domainId: 12,
localPart: 'info',
destinationMailboxId: 'mb_V1StGXR8Z5jdHi6BmyT',
});curl -X POST https://api.poststack.dev/mailboxes/aliases \
-H "Authorization: Bearer $POSTSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"domainId":"dom_4f8a2c9e1b","localPart":"info","destinationMailboxId":"mb_V1StGXR8Z5jdHi6BmyT"}'{
"domainId": "dom_4f8a2c9e1b",
"localPart": "info",
"destinationMailboxId": "mb_V1StGXR8Z5jdHi6BmyT"
}{
"alias": {
"id": 9,
"teamId": 7,
"domainId": 12,
"aliasAddress": "info@yourdomain.com",
"destinationMailboxId": 42,
"createdAt": "2026-09-29T09:20:03.771Z"
}
}Body parameters
domainIdstring | integerrequired- The alias’s domain:
dom_…or numeric id. localPartstringrequired- 1–64 characters of
a–z A–Z 0–9 . _ % + -. destinationMailboxIdstringrequired- The
mb_…publicId of the mailbox that receives the mail.
Response fields
alias.idinteger- Alias id, used to delete it.
alias.teamIdinteger- Owning team.
alias.domainIdinteger- Numeric domain id.
alias.aliasAddressstring- The full alias address.
alias.destinationMailboxIdinteger- Numeric id of the destination mailbox.
alias.createdAtstring (ISO 8601)- Creation time.
Status codes
| Status | Meaning |
|---|---|
| 201 | Created. |
| 400 | Validation failed. |
| 401 | Missing or invalid API key. |
| 403 | insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended. |
| 404 | Domain or destination mailbox not found on this team. |
| 409 | A mailbox or alias with this address already exists. |
| 429 | More than 10 requests a minute to this endpoint from the same key. Retry-After says how many seconds to wait. |
List aliases#
Your team's aliases, newest first.
https://api.poststack.dev/mailboxes/aliasesconst { data } = await client.mailboxes.listAliases();curl https://api.poststack.dev/mailboxes/aliases \
-H "Authorization: Bearer $POSTSTACK_API_KEY"{
"data": [
{
"id": 9,
"aliasAddress": "info@yourdomain.com",
"destinationMailboxId": 42,
"createdAt": "2026-09-29T09:20:03.771Z",
"destinationMailbox": { "id": 42, "emailAddress": "support@yourdomain.com" },
"domain": { "id": 12, "name": "yourdomain.com" }
}
],
"meta": { "page": 1, "perPage": 20, "total": 1, "totalPages": 1 }
}Query parameters
pageintegerdefault1- Page number, from 1.
per_pageintegerdefault20- 1–100.
Response fields
data[]object[]- Aliases as above, plus
destinationMailbox: { id, emailAddress }anddomain: { id, name }. metaobjectpage,perPage,total,totalPages.
Status codes
| Status | Meaning |
|---|---|
| 200 | The page of aliases. |
| 400 | Invalid page or per_page. |
| 401 | Missing or invalid API key. |
| 403 | insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended. |
Delete an alias#
Removes the alias. Mail to that address stops being delivered into the mailbox.
https://api.poststack.dev/mailboxes/aliases/:idawait client.mailboxes.deleteAlias(9);curl -X DELETE https://api.poststack.dev/mailboxes/aliases/9 \
-H "Authorization: Bearer $POSTSTACK_API_KEY"{
"success": true
}Path parameters
idintegerrequired- The numeric alias id.
Response fields
successboolean- Always true on 200.
Status codes
| Status | Meaning |
|---|---|
| 200 | Deleted. |
| 400 | The id is not a positive integer. |
| 401 | Missing or invalid API key. |
| 403 | insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended. |
| 404 | Alias not found on this team. |
| 429 | More than 10 requests a minute to this endpoint from the same key. Retry-After says how many seconds to wait. |
Shared mailboxes#
A mailbox can be opened by another mailbox on the same team. The grantee sees it in their own IMAP client under Shared/<owner address>/, logging in with their own password. A share covers every folder in the mailbox. Who has access is managed here or in the dashboard, not from a mail client.
| access | What the grantee can do |
|---|---|
read | See every folder and read messages. Nothing they open is marked read for the owner. |
write | Also add, flag, move and delete messages, and mark them read. |
admin | Everything in write, plus create, rename and delete folders. |
Share a mailbox#
Gives another mailbox access to this one.
https://api.poststack.dev/mailboxes/:id/sharescurl -X POST https://api.poststack.dev/mailboxes/mb_V1StGXR8Z5jdHi6BmyT/shares \
-H "Authorization: Bearer $POSTSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"sharedWithMailboxId":"mb_9dKq2LmXw0Pz","access":"write"}'{
"sharedWithMailboxId": "mb_9dKq2LmXw0Pz",
"access": "write"
}{
"share": {
"id": 3,
"mailboxId": 42,
"sharedWithMailboxId": 57,
"access": "write",
"createdAt": "2026-09-29T09:31:40.002Z",
"updatedAt": "2026-09-29T09:31:40.002Z"
}
}Path parameters
idstringrequired- The mailbox being shared (
mb_…).
Body parameters
sharedWithMailboxIdstringrequired- The
mb_…publicId of the mailbox that gets access. access"read" | "write" | "admin"defaultread- Access level.
Response fields
share.idinteger- Share id.
share.mailboxIdinteger- Numeric id of the shared mailbox.
share.sharedWithMailboxIdinteger- Numeric id of the grantee.
share.accessstring- Access level.
share.createdAtstring (ISO 8601)- Creation time.
share.updatedAtstring (ISO 8601)- Last change.
Status codes
| Status | Meaning |
|---|---|
| 201 | Shared. |
| 400 | Validation failed. |
| 401 | Missing or invalid API key. |
| 403 | insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended. |
| 404 | Either mailbox not found on this team. |
| 409 | Already shared with that mailbox. Revoke it first to change the level. |
| 422 | A mailbox cannot be shared with itself, or the mailbox is suspended. |
| 429 | More than 10 requests a minute to this endpoint from the same key. Retry-After says how many seconds to wait. |
| 500 | The mail server refused the grant. Nothing was saved; try again. |
List shares#
Who has access to this mailbox.
https://api.poststack.dev/mailboxes/:id/sharescurl https://api.poststack.dev/mailboxes/mb_V1StGXR8Z5jdHi6BmyT/shares \
-H "Authorization: Bearer $POSTSTACK_API_KEY"{
"shares": [
{
"id": 3,
"sharedWithMailboxId": 57,
"sharedWithEmail": "anna@yourdomain.com",
"access": "write",
"createdAt": "2026-09-29T09:31:40.002Z"
}
]
}Path parameters
idstringrequired- The
mb_…publicId.
Response fields
shares[].idinteger- Share id.
shares[].sharedWithMailboxIdinteger- Numeric id of the grantee.
shares[].sharedWithEmailstring- The grantee’s address.
shares[].accessstring- Access level.
shares[].createdAtstring (ISO 8601)- When it was granted.
Status codes
| Status | Meaning |
|---|---|
| 200 | The shares (an empty array when there are none). |
| 401 | Missing or invalid API key. |
| 403 | insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended. |
| 404 | No such mailbox on this team. |
Revoke a share#
Removes a grantee's access. Succeeds even if there was no share.
https://api.poststack.dev/mailboxes/:id/shares/:targetIdcurl -X DELETE https://api.poststack.dev/mailboxes/mb_V1StGXR8Z5jdHi6BmyT/shares/mb_9dKq2LmXw0Pz \
-H "Authorization: Bearer $POSTSTACK_API_KEY"{
"success": true
}Path parameters
idstringrequired- The shared mailbox (
mb_…). targetIdstringrequired- The grantee mailbox (
mb_…).
Response fields
successboolean- Always true on 200.
Status codes
| Status | Meaning |
|---|---|
| 200 | Revoked (or there was nothing to revoke). |
| 401 | Missing or invalid API key. |
| 403 | insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended. |
| 404 | Either mailbox not found on this team. |
| 429 | More than 10 requests a minute to this endpoint from the same key. Retry-After says how many seconds to wait. |
| 500 | The mail server refused the change. The share is still in place. |
Mail filters#
Filters run on the server as mail is delivered, so they apply no matter which client reads the mailbox. The rule set is replaced as a whole, because its order is part of its meaning: rules run top to bottom, and a matching fileinto, discard or stop ends processing for that message.
Spam filing runs first: a message the spam filter flags goes to Junk and your rules do not see it. Everything else reaches your rules unchanged.
namestringrequired- 1–100 characters, for your own reference.
field"from" | "to" | "cc" | "subject" | "any_recipient"required- The header to test.
any_recipientchecks To, Cc and Bcc. operator"contains" | "not_contains" | "is" | "is_not"required- How
valueis compared. valuestringrequired- 1–500 characters.
action"fileinto" | "addflag" | "discard" | "stop"requiredfileintomoves the message to the folder named inactionArg(created if it does not exist) and stops.addflagsets the IMAP flag inactionArg(e.g.\Flagged) and continues.discarddrops the message silently and stops.stopends processing, leaving the message in INBOX.actionArgstring | null- Folder or flag, up to 200 characters. An
addflagwithout one is skipped; afileintowithout one keeps the message in INBOX and stops processing. enabledbooleandefaulttrue- A disabled rule is kept but skipped.
List filters#
The mailbox's rules, in the order they run.
https://api.poststack.dev/mailboxes/:id/filtersconst rules = await client.mailboxes.listFilters('mb_V1StGXR8Z5jdHi6BmyT');curl https://api.poststack.dev/mailboxes/mb_V1StGXR8Z5jdHi6BmyT/filters \
-H "Authorization: Bearer $POSTSTACK_API_KEY"{
"rules": [
{
"id": 18,
"publicId": "mfr_b7Tq1ZcV0sLm",
"mailboxId": 42,
"position": 0,
"name": "Invoices",
"enabled": true,
"field": "subject",
"operator": "contains",
"value": "Invoice",
"action": "fileinto",
"actionArg": "Invoices",
"options": null,
"createdAt": "2026-09-29T09:40:00.000Z",
"updatedAt": "2026-09-29T09:40:00.000Z"
}
]
}Path parameters
idstringrequired- The
mb_…publicId.
Response fields
rules[]object[]- Each rule’s fields above, plus
id,publicId(mfr_…),mailboxId,position(0-based),options,createdAtandupdatedAt.
Status codes
| Status | Meaning |
|---|---|
| 200 | The rules (an empty array when there are none). |
| 401 | Missing or invalid API key. |
| 403 | insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended. |
| 404 | No such mailbox on this team. |
Replace filters#
Replaces every rule with the array you send, in that order. An empty array removes them all.
https://api.poststack.dev/mailboxes/:id/filtersawait client.mailboxes.setFilters('mb_V1StGXR8Z5jdHi6BmyT', [
{ name: 'Invoices', field: 'subject', operator: 'contains', value: 'Invoice', action: 'fileinto', actionArg: 'Invoices' },
]);curl -X PUT https://api.poststack.dev/mailboxes/mb_V1StGXR8Z5jdHi6BmyT/filters \
-H "Authorization: Bearer $POSTSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"rules":[{"name":"Invoices","field":"subject","operator":"contains","value":"Invoice","action":"fileinto","actionArg":"Invoices"}]}'{
"rules": [
{
"name": "Invoices",
"field": "subject",
"operator": "contains",
"value": "Invoice",
"action": "fileinto",
"actionArg": "Invoices"
}
]
}Path parameters
idstringrequired- The
mb_…publicId.
Body parameters
rulesobject[]required- Up to 50 rules, shaped as described above.
Response fields
rules[]object[]- The saved rules, in run order.
Status codes
| Status | Meaning |
|---|---|
| 200 | Saved. The new rules apply to the next message delivered. |
| 400 | Validation failed, or more than 50 rules. |
| 401 | Missing or invalid API key. |
| 403 | insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended. |
| 404 | No such mailbox on this team. |
| 429 | More than 10 requests a minute to this endpoint from the same key. Retry-After says how many seconds to wait. |
Other tools#
The same operations are in the SDK (client.mailboxes.create / list / get / update / delete / changePassword / createAlias / listAliases / deleteAlias / listFilters / setFilters / getSignature / setSignature / listShares / grantShare / revokeShare / unreadSummary), the CLI (poststack mailboxes list | get | create | update | delete | filters) and the MCP server (create_mailbox, list_mailboxes, get_mailbox, update_mailbox, delete_mailbox, change_mailbox_password, list_mailbox_filters, set_mailbox_filters). Shares and the unread summary are not in the CLI or the MCP server.
Set up a mailbox end to end#
Verify the domain and enable inbound
Follow Domains, then turn on inbound and publish the MX record
inbound.poststack.dev.Create the mailbox
From the dashboard or
POST /mailboxes. Note thepublicIdand give the password to the person who will use it.Connect a client or open webmail
Enter the settings from Connect a mail client, or sign in at
mail.poststack.dev.Send a test both ways
Mail the address from another account, then reply. If the reply bounces with a
550, check the From address matches the mailbox.
Troubleshooting#
The client says the password is wrong, but it works in webmail
Check the username is the full address, and that the client is using SSL/TLS on 993 (IMAP), 995 (POP3) or 465 (SMTP) — not 143, 110 or 25. A suspended mailbox fails every login, so check status too.
Sending fails with 550 From address must be …
The client is sending with a different From address (an identity or alias). A mailbox login can only send as its own address; create a mailbox for the other address.
Sending stops after a while with 450 Rate limit exceeded
The mailbox has sent 60 messages within the hour. Clients keep the message and retry; for bulk or automated mail use an API key with the SMTP relay or the API instead.
Every sent message appears twice in Sent
PostStack already stores a copy in Sent. Turn off “save sent messages on the server” in the client.
Mail to the address never arrives
Check the domain’s MX record points at inbound.poststack.dev and that inbound is enabled on the domain, then look in Junk and in any folder a filter moves mail to. If forwardKeepCopy is false, forwarded mail is intentionally not kept.
POST /mailboxes returns 402
Mailboxes are paid for before they exist. Subscribe to a paid plan with a payment method (or update a declined card) in Billing, then retry.