Skip to main content

Workspaces

Workspaces provide multi-tenant isolation within Posta. Every resource — templates, SMTP servers, domains, contacts, API keys — belongs to exactly one workspace, and a workspace is shared with as many or as few people as you invite.

Concepts

Personal space

A workspace is provisioned for you when you sign up, named after you and owned by you. It is an ordinary workspace in every respect: rename it, invite people to it, or delete it once you have another. There is no separate "personal" workspace type.

One workspace is special. The system workspace is created on first boot, owned by the first administrator, and holds platform-managed resources. It is flagged system: true, admits only platform administrators, and cannot be renamed or deleted.

The system workspace

Its job is to give the platform's own mail somewhere to belong. Posta sends password resets, email verification, sign-in alerts, workspace invitations, and daily reports on its own behalf; that mail needs an SMTP server that belongs to the operator rather than to a tenant.

On boot, the POSTA_SYSTEM_SMTP_* settings are provisioned as an ordinary SMTPServer inside this workspace, labelled System SMTP. Because it is an ordinary workspace-scoped server, it appears under SMTP Servers in the dashboard, can be tested from there, and the normal delivery pipeline can send through it with no special cases.

Ownership of that row is split:

FieldOwnerOn restart
Host, port, username, password, encryptionConfigurationRe-synced from the environment
Name, status, retry and recipient limitsOperatorLeft as you set them

That is what makes credential rotation a matter of changing the environment and restarting, while leaving you free to relabel the server without a restart undoing it.

Two consequences worth knowing:

  • The provisioned server cannot be deleted (409). It is recreated from configuration on the next restart regardless, and platform mail depends on it.
  • Disabling it does not stop platform mail. Password resets and security alerts must reach you even when something is misconfigured, so the notification path uses the row's connection settings without consulting its status. Whether the platform sends at all is governed by whether POSTA_SYSTEM_SMTP_* is configured.

A server you add to the system workspace yourself is left alone: the provisioned one is found by an internal marker, never by name or position.

Workspaces

A workspace is an isolated environment where team members collaborate. Resources created within a workspace are only visible to members of that workspace. Each workspace has:

  • A unique name and slug (a URL-friendly identifier)
  • An owner (the creator)
  • Members, each with a role
  • A default language
  • Isolated operational resources (templates, SMTP servers, domains, API keys, contacts, subscribers, campaigns, emails, webhooks, etc.)

Roles

Posta defines four workspace roles. Permissions are cumulative:

RoleView resourcesCreate / edit resourcesManage members & invitationsDelete workspace
ownerYesYesYesYes
adminYesYesYesNo
editorYesYesNoNo
viewerYesNoNoNo

There is exactly one owner per workspace: the creator. The owner role cannot be assigned through invitations or role updates, and the owner cannot be removed or have their role changed.

note

Operational settings, the workspace audit log, and the GDPR data-management endpoints require the admin level or higher (owner or admin). Resource read/write follows the table above.

API usage

Workspace management uses JWT bearer authentication (the dashboard/UI token), not API keys.

Authorization: Bearer <jwt>

Workspace context header

Routes under /api/v1/workspaces/current/* operate against the active workspace, which is resolved from the X-Posta-Workspace-Id header:

X-Posta-Workspace-Id: 1
curl -X GET http://localhost:9000/api/v1/workspaces/current/templates \
-H "Authorization: Bearer <jwt>" \
-H "X-Posta-Workspace-Id: 1"

The header value is the numeric workspace ID. If you are not a member of that workspace the request is rejected with 403. The header is required for every /workspaces/current/* route; omitting it returns 400 X-Posta-Workspace-Id header is required.

caution

The correct header is X-Posta-Workspace-Id. Earlier drafts referred to X-Workspace-ID — that name is wrong and is not recognized by the API.

Omit the header and the request operates on your default workspace — the one you last set with PUT /api/v1/users/me/default-workspace, or the oldest one you belong to. The role you hold in that workspace applies, so a viewer stays a viewer on a header-less request.

Workspace-scoped API keys

API keys created inside a workspace context are bound to that workspace. When you authenticate with such a key, the active workspace is implied by the key itself, so the X-Posta-Workspace-Id header is not needed:

curl -X POST http://localhost:9000/api/v1/emails/send \
-H "Authorization: Bearer <workspace_api_key>" \
-H "Content-Type: application/json" \
-d '{ ... }'

This is how transactional sending and other API-key endpoints stay scoped to the right workspace without a header.

Workspace management endpoints

All endpoints below use JWT auth. Those operating on /current additionally require the X-Posta-Workspace-Id header.

MethodPathHeader requiredDescription
POST/api/v1/workspacesNoCreate a workspace (creator becomes owner)
GET/api/v1/workspacesNoList workspaces the user belongs to
GET/api/v1/workspaces/currentYesGet the active workspace
PUT/api/v1/workspaces/currentYesUpdate name / description / default language
DELETE/api/v1/workspaces/currentYesDelete the workspace
GET/api/v1/workspaces/current/membersYesList members
PUT/api/v1/workspaces/current/members/{member_id}YesUpdate a member's role
DELETE/api/v1/workspaces/current/members/{member_id}YesRemove a member
POST/api/v1/workspaces/current/invitationsYesInvite a user by email
GET/api/v1/workspaces/current/invitationsYesList pending invitations
DELETE/api/v1/workspaces/current/invitations/{id}YesCancel a pending invitation
GET/api/v1/workspaces/invitationsNoList the current user's pending invitations
POST/api/v1/workspaces/invitations/acceptNoAccept an invitation by token
POST/api/v1/workspaces/invitations/declineNoDecline an invitation by token
POST/api/v1/workspaces/invitations/{id}/acceptNoAccept an invitation by ID
POST/api/v1/workspaces/invitations/{id}/declineNoDecline an invitation by ID
GET/api/v1/workspaces/current/planYesGet the effective plan and limits
GET/api/v1/workspaces/current/settingsYesGet operational settings
PUT/api/v1/workspaces/current/settingsYesUpdate operational settings (admin+)
GET/api/v1/workspaces/current/audit-logYesWorkspace audit trail (admin+)

Member and invitation details are documented in Members and Invitations. Operational settings, plan, and the audit log are documented in Settings, Plan, and Audit Log.

Creating a workspace

POST /api/v1/workspaces

Request body:

{
"name": "Acme Inc",
"slug": "acme",
"description": "Marketing and transactional mail",
"default_language": "en",
"seed_defaults": true
}

Only name is required. If slug is omitted it is derived from the name; slugs must contain only lowercase letters, numbers, and hyphens, and must be unique. default_language defaults to en. The caller becomes the workspace owner.

seed_defaults controls whether the new workspace starts with content: a welcome template in English, French and German, a default stylesheet, and the matching languages. It defaults to true, so omitting it gives you a workspace you can send from immediately. Send false when you intend to populate the workspace from an export or the API and do not want the starter template in the way.

Seeding is best effort and runs after the workspace exists, so a workspace is never left uncreated because its starter content failed.

curl -X POST http://localhost:9000/api/v1/workspaces \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{"name": "Acme Inc", "slug": "acme"}'

Response (201):

{
"data": {
"id": 1,
"name": "Acme Inc",
"slug": "acme",
"description": "",
"owner_id": 42,
"role": "owner",
"system": false,
"created_at": "2026-05-31T10:00:00Z"
}
}

Creating a workspace is subject to your plan's workspace quota; exceeding it returns 403. Creating a duplicate slug returns 409.

Listing workspaces

GET /api/v1/workspaces

Returns every workspace the current user is a member of. Each entry carries the caller's role in that workspace and a system flag, which is true only for the built-in platform workspace.

Get, update, and delete the current workspace

GET /api/v1/workspaces/current
PUT /api/v1/workspaces/current
DELETE /api/v1/workspaces/current

PUT accepts any subset of the following; empty fields are left unchanged:

{
"name": "Acme Corporation",
"description": "Updated description",
"default_language": "fr"
}

DELETE removes the workspace and returns 204.

Two workspaces refuse deletion:

  • The system workspace returns 409. It is the built-in platform workspace, created on first boot, owned by the first administrator, and holds platform-managed resources. It cannot be renamed either, and only platform administrators are members.
  • Your last remaining workspace returns 400. Everything in Posta belongs to a workspace, so deleting the only one you belong to would leave you with nowhere to work. Create another first.
curl -X PUT http://localhost:9000/api/v1/workspaces/current \
-H "Authorization: Bearer <jwt>" \
-H "X-Posta-Workspace-Id: 1" \
-H "Content-Type: application/json" \
-d '{"description": "Updated description"}'