Skip to content

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 /mailboxes answers 403.
  • 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:

ProtocolServerPortSecurity
IMAP (incoming)poststack.dev993SSL/TLS
POP3 (incoming)poststack.dev995SSL/TLS
SMTP (outgoing)poststack.dev465SSL/TLS
SMTP (alternative)poststack.dev587STARTTLS (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 (PLAIN or LOGIN), 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.

Passwords are limited to 72 bytes

A mailbox password must be 8 characters to 72 bytes of UTF-8 (an “æ” counts as two). That is the length bcrypt reads, and the API refuses a longer one with a 400 rather than store a password that mail clients could never log in with.

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 fetch https://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 at https://poststack.dev/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress=you@yourdomain.com for 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.dev and _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 From address must be exactly the mailbox address (case-insensitive). Anything else is refused with 550 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 Sent folder, 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"default relay
relay sends the message on unchanged — same subject, bodies and attachments, with open and click tracking forced off so links are not rewritten. wrapped sends it as a forward, with a Fwd: subject and the original headers quoted in the body.
forwardKeepCopybooleandefault true
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"
  }'

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.

POSThttps://api.poststack.dev/mailboxes
Request
import { 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',
});
{
  "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 of a–z A–Z 0–9 . _ % + -.
passwordstringrequired
8 characters to 72 bytes (UTF-8).
displayNamestring
Up to 255 characters.
quotaBytesintegerdefault 1073741824
1 GB to 50 GB, in bytes. Storage above 1 GB is billed per GB.
webhookEnabledbooleandefault true
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.publicIdstring
mb_… id. Every /mailboxes/:id route 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

StatusMeaning
201Created. The body is the new mailbox.
400Validation failed, or the domain is not verified / has inbound disabled.
401Missing or invalid API key.
402No paid subscription with a payment method, the subscription is not active, or the card was declined.
403insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended.
403Free plan (mailboxes need a paid plan).
404Domain not found on this team.
409A mailbox or alias with this address already exists.
429More than 10 requests a minute to this endpoint from the same key. Retry-After says how many seconds to wait.
503Billing could not be reached. Nothing was created; retry.

List mailboxes#

Your team's mailboxes, newest first. Deleted mailboxes are not included.

GEThttps://api.poststack.dev/mailboxes
Request
const { data, meta } = await client.mailboxes.list({ page: 1, per_page: 50 });
{
  "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

pageintegerdefault 1
Page number, from 1.
per_pageintegerdefault 20
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 mailbox above, plus domain: { id, name }.
meta.pageinteger
Current page.
meta.perPageinteger
Page size used.
meta.totalinteger
Total mailboxes matching.
meta.totalPagesinteger
Number of pages.

Status codes

StatusMeaning
200The page of mailboxes.
400Invalid page or per_page.
401Missing or invalid API key.
403insufficient_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.

GEThttps://api.poststack.dev/mailboxes/:id
Request
const mailbox = await client.mailboxes.get('mb_V1StGXR8Z5jdHi6BmyT');
{
  "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

StatusMeaning
200The mailbox.
401Missing or invalid API key.
403insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended.
404No 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.

PATCHhttps://api.poststack.dev/mailboxes/:id
Request
const mailbox = await client.mailboxes.update('mb_V1StGXR8Z5jdHi6BmyT', {
  status: 'suspended',
});
{
  "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

StatusMeaning
200Updated.
400Validation failed, or assignedUserId is not a member of the team.
401Missing or invalid API key.
402A quota raise could not be paid for.
403insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended.
403assignedUserId set with an API key or OAuth token, or by a member who is not an owner/admin.
404No such mailbox on this team.
429More than 10 requests a minute to this endpoint from the same key. Retry-After says how many seconds to wait.
503Billing 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.

POSThttps://api.poststack.dev/mailboxes/:id/password
Request
await client.mailboxes.changePassword('mb_V1StGXR8Z5jdHi6BmyT', '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

StatusMeaning
200Changed.
400Password too short or longer than 72 bytes.
401Missing or invalid API key.
403insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended.
404No such mailbox on this team.
429More 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.

DELETEhttps://api.poststack.dev/mailboxes/:id
Request
await client.mailboxes.delete('mb_V1StGXR8Z5jdHi6BmyT');
{
  "success": true
}

Path parameters

idstringrequired
The mb_… publicId.

Response fields

successboolean
Always true on 200.

Status codes

StatusMeaning
200Deleted (also for a mailbox that was already deleted).
401Missing or invalid API key.
403insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended.
404No such mailbox on this team.
429More than 10 requests a minute to this endpoint from the same key. Retry-After says how many seconds to wait.

Deletion is a soft delete, and the address stays taken: creating a mailbox or alias at the same address afterwards answers 409. If the mailbox was the domain’s catch-all destination, catch-all is switched off for that domain.

Unread counts#

Cached INBOX unread counts for every active mailbox on the team, in one call.

GEThttps://api.poststack.dev/mailboxes/unread-summary
Request
curl 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[].mailboxPublicIdstring
mb_… id.
items[].inboxUnreadinteger | null
Unread in INBOX at the last sweep; null if not yet measured.

Status codes

StatusMeaning
200The counts.
401Missing or invalid API key.
403insufficient_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.

POSThttps://api.poststack.dev/mailboxes/aliases
Request
const alias = await client.mailboxes.createAlias({
  domainId: 12,
  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

StatusMeaning
201Created.
400Validation failed.
401Missing or invalid API key.
403insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended.
404Domain or destination mailbox not found on this team.
409A mailbox or alias with this address already exists.
429More 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.

GEThttps://api.poststack.dev/mailboxes/aliases
Request
const { data } = await client.mailboxes.listAliases();
{
  "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

pageintegerdefault 1
Page number, from 1.
per_pageintegerdefault 20
1–100.

Response fields

data[]object[]
Aliases as above, plus destinationMailbox: { id, emailAddress } and domain: { id, name }.
metaobject
page, perPage, total, totalPages.

Status codes

StatusMeaning
200The page of aliases.
400Invalid page or per_page.
401Missing or invalid API key.
403insufficient_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.

DELETEhttps://api.poststack.dev/mailboxes/aliases/:id
Request
await client.mailboxes.deleteAlias(9);
{
  "success": true
}

Path parameters

idintegerrequired
The numeric alias id.

Response fields

successboolean
Always true on 200.

Status codes

StatusMeaning
200Deleted.
400The id is not a positive integer.
401Missing or invalid API key.
403insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended.
404Alias not found on this team.
429More 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.

accessWhat the grantee can do
readSee every folder and read messages. Nothing they open is marked read for the owner.
writeAlso add, flag, move and delete messages, and mark them read.
adminEverything in write, plus create, rename and delete folders.

Share a mailbox#

Gives another mailbox access to this one.

POSThttps://api.poststack.dev/mailboxes/:id/shares
Request
curl -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"}'
{
  "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"default read
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

StatusMeaning
201Shared.
400Validation failed.
401Missing or invalid API key.
403insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended.
404Either mailbox not found on this team.
409Already shared with that mailbox. Revoke it first to change the level.
422A mailbox cannot be shared with itself, or the mailbox is suspended.
429More than 10 requests a minute to this endpoint from the same key. Retry-After says how many seconds to wait.
500The mail server refused the grant. Nothing was saved; try again.

List shares#

Who has access to this mailbox.

GEThttps://api.poststack.dev/mailboxes/:id/shares
Request
curl 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

StatusMeaning
200The shares (an empty array when there are none).
401Missing or invalid API key.
403insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended.
404No such mailbox on this team.

Revoke a share#

Removes a grantee's access. Succeeds even if there was no share.

DELETEhttps://api.poststack.dev/mailboxes/:id/shares/:targetId
Request
curl -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

StatusMeaning
200Revoked (or there was nothing to revoke).
401Missing or invalid API key.
403insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended.
404Either mailbox not found on this team.
429More than 10 requests a minute to this endpoint from the same key. Retry-After says how many seconds to wait.
500The 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_recipient checks To, Cc and Bcc.
operator"contains" | "not_contains" | "is" | "is_not"required
How value is compared.
valuestringrequired
1–500 characters.
action"fileinto" | "addflag" | "discard" | "stop"required
fileinto moves the message to the folder named in actionArg (created if it does not exist) and stops. addflag sets the IMAP flag in actionArg (e.g. \Flagged) and continues. discard drops the message silently and stops. stop ends processing, leaving the message in INBOX.
actionArgstring | null
Folder or flag, up to 200 characters. An addflag without one is skipped; a fileinto without one keeps the message in INBOX and stops processing.
enabledbooleandefault true
A disabled rule is kept but skipped.

List filters#

The mailbox's rules, in the order they run.

GEThttps://api.poststack.dev/mailboxes/:id/filters
Request
const rules = await client.mailboxes.listFilters('mb_V1StGXR8Z5jdHi6BmyT');
{
  "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, createdAt and updatedAt.

Status codes

StatusMeaning
200The rules (an empty array when there are none).
401Missing or invalid API key.
403insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended.
404No such mailbox on this team.

Replace filters#

Replaces every rule with the array you send, in that order. An empty array removes them all.

PUThttps://api.poststack.dev/mailboxes/:id/filters
Request
await client.mailboxes.setFilters('mb_V1StGXR8Z5jdHi6BmyT', [
  { 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

StatusMeaning
200Saved. The new rules apply to the next message delivered.
400Validation failed, or more than 50 rules.
401Missing or invalid API key.
403insufficient_scope — the key is a sending_access key; mailboxes need a full_access key. Also returned when the team is suspended.
404No such mailbox on this team.
429More 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#

  1. Verify the domain and enable inbound

    Follow Domains, then turn on inbound and publish the MX record inbound.poststack.dev.

  2. Create the mailbox

    From the dashboard or POST /mailboxes. Note the publicId and give the password to the person who will use it.

  3. Connect a client or open webmail

    Enter the settings from Connect a mail client, or sign in at mail.poststack.dev.

  4. 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.

Next steps

Was this page helpful?

Related

Mailboxes and webmail overviewHosted IMAP/POP3 mailboxes on your own domain, with webmail at mail.poststack.dev.