Skip to content

Mailspace addresses

These endpoints manage what mail can be addressed to inside one mailspace: aliases, distribution groups, mailing lists, and masked (throwaway) forwarding addresses. Buying, inspecting, resizing and deleting the mailspace itself is covered by the plan-level endpoints on Mailspace.

Every path on this page begins /api/mailspace/:mailspace_id/…, where :mailspace_id is the mailspace GUID from GET /api/mailspace.

OAuth scopes: reads require mailspace:read, writes require mailspace:write. Session and API-key credentials bypass scope checks entirely (see OAuth). An OAuth token that lacks the required scope is refused with 403 {"error":"insufficient_scope", ...} — note that this is the OAuth error envelope, not the {"errors":[...],"code":"..."} envelope every other failure on this page uses. See Scope Enforcement Errors.

Everything on this page is read live from the mail server on every request rather than from a local mirror. All index endpoints here are unpaginatedpage and per_page have no effect — but they are not unbounded; see below.

An index is capped, and an empty list is not proof of emptiness

A principal read is capped at 500 rows and masked emails at 2000, and anything past the cap is dropped with nothing in the response to say so. Neither cap is scoped the way it looks — read on, and see Masked Emails for the 2000, which is applied server-wide rather than to this mailspace.

The 500 is not a cap per principal type. Mailboxes and groups are the same kind of object on the mail server, told apart by a type marker, and one capped query fetches both — the split into mailboxes and groups happens after the 500 rows have already been chosen. So a mailspace with 500 or more mailboxes can answer 200 with zero groups, and with no group aliases in the alias inventory, while its groups exist and work perfectly. member_count is under-reported for the same reason: it is tallied from that same capped set of principals, so members outside the window are not counted. Mailing lists are a separate object with a cap of their own, so they are unaffected by how many mailboxes there are.

The cap is the one way these lists are quietly short. A read that could not be performed is reported rather than hidden: GET .../aliases, GET .../groups and GET .../mailing_lists are strict and answer 503 with a code naming the failed read — aliases_unavailable, groups_unavailable, mailing_lists_unavailable — instead of 200 with no rows. So an empty collection on those three is authoritative, and you can reconcile against it. GET .../masked_emails is the exception and still answers 200 with zero rows when its read fails.

Listing groups has a second code, group_members_unavailable, for the case where the groups came back but the member counts did not — the endpoint refuses to report member_count: 0 for every group when it could not ask. See Read failures.

Reading one group or mailing list by id behaves the opposite way. A failed read is indistinguishable from "no such principal", and answering anything softer would confirm an id that may belong to another tenant, so it fails closed to 404 — never an object with empty fields. A single 404 there is therefore not proof the group or list is gone; the index is the authoritative check.

One flat address namespace per mailspace

Mailbox addresses, alias addresses, group addresses and mailing-list addresses all occupy the same namespace across the whole mailspace, and that namespace includes every principal's aliases. No two of them can be the same address: an address that is merely an alias of a mailing list is unavailable for a new mailbox, group or alias.

Creating an alias on an address that is already taken is refused before anything is written, with 422 invalid_alias and a message naming what holds the address (a mailbox, a group, or a mailing list). Creating a group or a mailing list on a taken address is refused by the mail server itself, which surfaces as 422 group_create_failed / mailing_list_create_failed.

Request Guards

Every endpoint on this page inherits the same gate chain, applied in this order before the action runs. The first gate that fails answers the request. The OAuth scope check runs before all of them, so an under-scoped token never reaches these at all.

Applies to Condition Response
all mail hosting is not configured on the platform 503 stalwart_unavailable
all :mailspace_id unknown, or not visible to your credential 404, empty body
writes user has no edit permission on the mailspace's own workspace 403 not_authorized
all mailspace is on hold (staff block or unpaid-invoice hold) 403 mailspace_suspended
writes mailspace is soft-deleted (pending deletion) 403 pending_delete
all mailspace is not provisioned yet 409 not_provisioned

A write is decided by the HTTP verb, not the endpoint

The two write gates decide on the request method: GET and HEAD pass straight through, everything else is gated. So a view-only member holding a mailspace:write token can list aliases, groups, mailing lists and masked addresses perfectly well, and gets 403 not_authorized on every POST, PATCH and DELETE — the token's scope is satisfied, the member's permission is not.

HEAD is treated exactly like GET, so probing a read endpoint with HEAD returns the same status the GET would.

Reads survive the retention window; writes do not

A soft-deleted (pending-deletion) mailspace keeps answering reads for its whole retention window — you can still enumerate its aliases, groups, lists and masked addresses. Every mutation is refused with 403 pending_delete until the mailspace is restored. See Delete a Mailspace.

A held mailspace behaves differently: mailspace_suspended blocks reads as well as writes. A mailspace that is pending deletion is exempt from that gate, so the two codes never both apply.

An unknown or out-of-scope :mailspace_id answers 404 with an empty body, not the usual error envelope — a GUID belonging to another workspace has to be indistinguishable from one that does not exist.


Aliases

An alias is an extra address that delivers to an existing mailbox, group or mailing list. It is not an object on the mail server — it is an entry in the aliases field of the principal it points at — so there is no id to address it by, and the alias inventory is a derived join across all three principal types.

An alias may only point at a primary address, never at another alias, so alias chains are always exactly one level deep.

List Aliases

GET /api/mailspace/:mailspace_id/aliases

Every alias in the mailspace, joined across mailboxes, groups and mailing lists and sorted by alias address. A principal's primary address is never returned here — only its aliases.

Params
  • q: String (optional) | case-insensitive substring filter, matched against either the alias address or its target address
Returned Params
  • aliases: Array
    • address: String | the alias itself, lower-cased
    • target_email: String | the primary address it delivers to
    • target_name: String | the target's description, falling back to the local part the mail server stores; null if it has neither
    • target_type: String | mailbox, group, or list
    • target_id: String | the mail-server principal id of the target

Every key is always present; a value that is unknown is null.

Errors
  • 503 aliases_unavailable | the mail server could not be read. The three reads behind this list share one fate, so an empty aliases array is authoritative — see Read failures
  • plus the shared gates

Create an Alias

POST /api/mailspace/:mailspace_id/aliases

Points a new alias at an existing mailbox, group or mailing list. Returns 201 Created.

The write is a read-modify-write of the target's whole alias set, so the target's existing aliases are preserved.

Params
  • address: String (required) | the alias address. A bare local part is qualified for you — see domain below.
  • domain: String (optional) | used only when address contains no @. Ignored entirely for a fully qualified address. When address has no @ and no domain is given, the mailspace's primary domain is used.
  • target_email: String (required) | the primary address of the mailbox, group or mailing list the alias should deliver to. An alias is not accepted as a target.
curl -X POST \
  -H "Authorization: Bearer $CLOUDPRESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"address": "hello@example.com", "target_email": "ann@example.com"}' \
  https://my.cloudpress.com/api/mailspace/$MAILSPACE_ID/aliases
Returned Params (201 Created)

The service returns no object — the write patches the target principal's alias set — so the response echoes the pair that was established:

  • alias: Object
    • address: String | the alias, lower-cased
    • target_email: String | the target primary address, lower-cased
Errors
  • 400 address_blank | no alias address supplied
  • 400 target_blank | no target address supplied
  • 422 invalid_alias | a genuine rejection: the alias domain is not one of this mailspace's domains, the address is already in use anywhere in the mailspace, the address is not a valid email address, or target_email is not a mailbox, group or mailing list here
  • 503 aliases_unavailable | one of those checks could not be made, or the write itself got no answer. Retriable, and indeterminate on the write leg — the alias may have been created
  • plus the shared gates

invalid_alias means the mail server answered and refused

All four validations are live reads, and a read that could not be performed answers 503 aliases_unavailable instead — so this code never stands in for "we could not ask". That matters most on the domain check, which sits first: an unreachable mail server used to make it report that the domain "isn't one of this mailspace's domains", a confident and actionable-looking claim about your own configuration that CloudPress was in no position to make.

errors[0] is curated text rather than the mail server's own string, so there is nothing in it to parse. Branch on code.

Because the 503 also covers the write, re-read List Aliases rather than reporting a failure to a user. The write replaces the target's whole alias set, so repeating it is safe.

Delete an Alias

DELETE /api/mailspace/:mailspace_id/aliases/:address

Removes one alias. The principal it pointed at, and every other alias on that principal, are left alone. Returns 200.

The address itself is the key — there is no alias id to use. Matching is case-insensitive, and a :address that is a principal's primary address is 404 unknown_alias, never a deletion — a primary address is not an alias, and removing it would leave the mailbox, group or mailing list without an address of its own. Delete the object itself instead.

Building the URL for a dotted address

The :address segment is constrained to "one or more characters that are not /", which is what lets a full email address through. Two consequences for a client:

  • @ and . need no escaping, and the match is greedy — so a trailing .com is not interpreted as a format extension the way a default path segment would interpret it. DELETE /api/mailspace/$ID/aliases/sales@example.com is the correct request.
  • An address containing a / cannot be reached at all; the segment stops at the first slash.
curl -X DELETE \
  -H "Authorization: Bearer $CLOUDPRESS_TOKEN" \
  https://my.cloudpress.com/api/mailspace/$MAILSPACE_ID/aliases/sales@example.com
Returned Params
  • deleted: Boolean | always true
  • address: String | the alias that was removed, lower-cased
  • target_email: String | the primary address of the principal that held it
Errors
  • 404 unknown_alias | the mail server was asked and no principal in this mailspace carries that alias. Authoritative
  • 422 alias_remove_failed | the alias was found but the write was rejected. errors[0] is curated text, not the mail server's own string
  • 503 aliases_unavailable | the owner lookup could not be performed, or the write got no answer. No write is attempted on the lookup path; on the write path the removal may have landed. Removing an alias that is already gone is a no-op, so a retry is safe
  • plus the shared gates

The 404 is authoritative, and that is a deliberate change of shape

The owner lookup runs before any write and is strict, so unknown_alias means the mail server was asked. A lookup that could not be performed answers 503 instead: a 404 is a terminal answer, and it must not be given for a question that was never asked.


Groups

A distribution group is a principal on the mail server with its own address; mail sent to it is delivered to each member. A group has no local database row, so it is addressed by its mail-server principal id — the :stalwart_id segment, which is the id field of every group object below.

A group may only contain mailboxes hosted in this mailspace

Group membership is by principal, so an address the mail server cannot resolve here would be dropped silently. The whole write is refused instead, with 422 group_create_failed / group_update_failed. To include an outside address, use a mailing list.

:stalwart_id is checked against your tenant

The mail server's by-id calls carry no tenant filter, so show, update and destroy confirm the principal belongs to this mailspace's tenant before touching it, and answer 404 unknown_group otherwise. An id that the mail server does not know, and an id belonging to another workspace, are the same 404 — and so is an id the mail server could not be asked about at all. The check fails closed: an unreadable principal is not proof of ownership, and a softer answer would confirm another tenant's id.

List Groups

GET /api/mailspace/:mailspace_id/groups

Params
  • q: String (optional) | case-insensitive substring filter, matched against the group's primary address or description. Aliases and the stored local part are not searched.
Returned Params
  • groups: Array
    • id: String | mail-server principal id — this is the :stalwart_id you address the group by
    • name: String | the local part the mail server stores. This is not the display name and cannot be changed.
    • email: String | primary address
    • aliases: Array | Array<String> of the group's alias addresses
    • description: String | the display name, null if unset
    • member_count: Integer | the number of member mailboxes, never null. The counts are read strictly, so an unreadable count answers 503 rather than a false zero — but the count is tallied from the same 500-row principal window as the listing, so on a mailspace at or past that cap it can be lower than the real membership. Read the group itself for a membership you are going to act on.

Members themselves are not included here — read one group for those.

Errors
  • 503 groups_unavailable | the group list could not be read, so nothing about this mailspace's groups is known. This is what a full mail-server outage answers
  • 503 group_members_unavailable | the groups came back but the member counts did not. Nothing is wrong with the groups themselves — retry
  • plus the shared gates

Two codes, and which one you get says which read failed

Neither is an empty array: this endpoint does not degrade to an empty 200. The split exists so that "we know nothing about your groups" and "we know your groups but not how many members they have" are never reported as the same thing. See Read failures.

View a Group

GET /api/mailspace/:mailspace_id/groups/:stalwart_id

Returned Params
  • group: Object
    • all fields from List Groups except member_count, plus:
    • members: Array | Array<String> of the member mailbox addresses
    • member_count: Integer | the size of members on this response
Errors
  • 404 unknown_group | no such group in this mailspace's tenant. Also the answer when the by-id lookup could not be read — unlike the index, that one read is tolerant and fails closed, so a single 404 here is not proof the group is gone
  • 503 group_members_unavailable | the mail server could not be asked for the members. The group exists and the rest of it was readable — retry. See Read failures.
  • plus the shared gates

Create a Group

POST /api/mailspace/:mailspace_id/groups

Returns 201 Created with the same body as View a Group.

Params
  • name: String (required) | display name. Stored as the group's description when no separate description is given.
  • email: String (required) | the group's address. A bare local part is qualified — see domain.
  • domain: String (optional) | used only when email contains no @; ignored for a fully qualified email. Defaults to the mailspace's primary domain.
  • description: String (optional) | display name. Used in place of name for this field when it has a value — but an explicit "" is not honoured on create: it falls back to name, so a blank description here does not produce a blank display name. Clearing it is a PATCH (see below), where "" genuinely clears
  • aliases: Array (optional) | Array<String> of extra addresses for the group
  • members: Array (optional) | Array<String> of mailbox addresses hosted in this mailspace
curl -X POST \
  -H "Authorization: Bearer $CLOUDPRESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Sales", "email": "sales", "members": ["ann@example.com"]}' \
  https://my.cloudpress.com/api/mailspace/$MAILSPACE_ID/groups

The new group's id comes from a read-back, and can be null

The mail server's create call returns no id, so the freshly created group is read back from the tenant's principal list to give you the :stalwart_id you need to address it.

If that read-back does not find it, the response is still 201 — the group was created — but it reports what was submitted rather than what was stored: id is null, aliases is empty and members is empty, regardless of what you sent. Treat a null id as "created, id unknown" and call List Groups to find it. Do not treat it as a failure and retry the create — the address is now taken.

This differs from Update a Group, deliberately. An update falls back to the group as it was before the write plus the values it just wrote, so it always reports a real id. A create has no pre-write principal to fall back on — nothing was there before — so a null id is the honest answer for a resource that now exists, and better than an error for a group that was in fact created.

An alias with an unknown domain is dropped on create, not refused

The group's own address must be on a domain of this mailspace — if it is not, the create fails with 422 group_create_failed. A submitted aliases entry is treated differently: one whose domain is not in this mailspace is silently skipped and the group is still created. Compare the aliases array on the 201 body against what you sent rather than assuming every alias landed. (On Update a Group the same situation is refused outright instead, because an alias write replaces the whole set.)

Errors
  • 400 email_blank | no address supplied (Email can't be blank.)
  • 400 name_blank | no name supplied (Name can't be blank.)
  • 422 group_create_failed | the domain is not one of this mailspace's domains, the address is already in use, a submitted member is not a mailbox in this mailspace, or the members could not be verified against the mail server
  • plus the shared gates

Update a Group

PATCH /api/mailspace/:mailspace_id/groups/:stalwart_id

Returns 200 with the same body as View a Group, re-read from the mail server after the write. A successful PATCH always reports what was saved. When that re-read cannot be performed, the response is still 200 and still describes the group: the pre-write principal is merged with the values that were just written, so

  • id, name and email come back populated,
  • description comes back as the value you just wrote — including "" when you cleared it,
  • an omitted aliases comes back as the aliases that are actually still there.

It is never a null-filled object, and never an empty array for a field the write did not empty. That matters because this endpoint invites a read-modify-write: a client that PATCHed such a body straight back would be sending an explicit empty list, which the mail server honours for real.

The envelope falls back, members does not — and that is on purpose

It looks like an inconsistency and is not. The fields above have a known-true fallback: the write succeeded, so the values that were written are the group's current state. Membership has no such fallback, and an empty array is a legitimate answer for it — an authoritatively empty group — so it has to stay distinguishable from a failure. The members are therefore read strictly, and a read failure answers 503 group_members_unavailable (see Read failures) instead of a 200 claiming the group has no members. The write has already gone through either way: retry the read, never the write.

PATCH is a merge — an omitted key keeps its current value

An omitted aliases or members leaves the group's aliases and membership exactly as they were. Send an explicit empty array to clear either one: "aliases": [] removes every alias, "members": [] evicts every member.

For members that is a property of the mechanism, not a convention: an omitted members is not sent to the mail server at all, so the membership diff is skipped entirely — no read, no write. That is the safety property. The previous implementation read the current membership back and passed it through, and because that read was tolerant, one blip handed the write an empty membership and evicted every member while the API answered 200.

A supplied members array is the complete new membership — the difference against the current membership is applied, so anything not listed is removed.

Params

All optional; every omitted key keeps its current value.

  • description: String | the display name the mail server shows. An explicit "" clears it — the mail server refuses an empty string for this field, so CloudPress sends the explicit null it requires on your behalf.
  • aliases: Array | Array<String>, the complete new alias set
  • members: Array | Array<String>, the complete new membership (mailboxes in this mailspace only)
  • name: String | accepted and ignored. It is the address local part, and the address is immutable. description is the display name — name never reaches it.
  • email: String | ignored. A group's address cannot be changed; create a new group, or add the address as an alias.
Errors
  • 404 unknown_group | no such group in this mailspace's tenant. Nothing is written.
  • 422 group_update_failed | a submitted member is not a mailbox in this mailspace, an alias domain is not one of this mailspace's domains, the members could not be verified, or the mail server rejected the write
  • 503 group_members_unavailable | the write went through and the members could not be read back. Do not re-send the change — re-read the group. See Read failures.
  • plus the shared gates

Delete a Group

DELETE /api/mailspace/:mailspace_id/groups/:stalwart_id

Removes the group principal. Its members' mailboxes are untouched — only the group and its addressing go. Returns 200.

Returned Params
  • deleted: Boolean | always true
  • id: String | the group's mail-server principal id
  • email: String | the group's primary address
Errors
  • 404 unknown_group | no such group in this mailspace's tenant. Nothing is deleted.
  • 422 group_delete_failed | the mail server refused the destroy
  • plus the shared gates

Mailing Lists

A mailing list is a separate principal type whose recipients are a plain address map rather than a set of principals. That is the practical difference from a group: a mailing list may carry addresses this mailspace does not host.

Like groups, a list has no local database row and is addressed by its mail-server principal id (:stalwart_id), with the same tenant-ownership check and the same 404 behavior for an id that is unknown, unreadable, or belongs to another workspace — here reported as unknown_mailing_list. That check fails closed too: a read the mail server could not answer is not proof of ownership.

List Mailing Lists

GET /api/mailspace/:mailspace_id/mailing_lists

Params
  • q: String (optional) | case-insensitive substring filter, matched against the list's primary address or description. Same two fields as the group filter, with the same matching.
Returned Params
  • mailing_lists: Array
    • id: String | mail-server principal id — the :stalwart_id you address the list by
    • name: String | the local part the mail server stores; not the display name, and not changeable
    • email: String | primary address
    • aliases: Array | Array<String> of the list's alias addresses
    • description: String | the display name, null if unset
    • members: Array | Array<String> of recipient addresses, which may include addresses outside this mailspace
    • member_count: Integer | the size of members
Errors
  • 503 mailing_lists_unavailable | the list read could not be performed. The read is strict, so an empty mailing_lists array is authoritative — see Read failures
  • plus the shared gates

View a Mailing List

GET /api/mailspace/:mailspace_id/mailing_lists/:stalwart_id

Returns exactly the fields of a List Mailing Lists row — recipients are already included there, so there is nothing extra to fetch.

Returned Params
Errors
  • 404 unknown_mailing_list | no such list in this mailspace's tenant. As with a single group, the by-id lookup is tolerant and fails closed to this 404 — the index is the authoritative check
  • plus the shared gates

Create a Mailing List

POST /api/mailspace/:mailspace_id/mailing_lists

Returns 201 Created with the same body as View a Mailing List.

Params
  • name: String (required) | display name. Stored as the list's description when no separate description is given.
  • email: String (required) | the list's address. A bare local part is qualified — see domain.
  • domain: String (optional) | used only when email contains no @; ignored for a fully qualified email. Defaults to the mailspace's primary domain.
  • description: String (optional) | display name. Used in place of name for this field when it has a value — but an explicit "" is not honoured on create: it falls back to name, so a blank description here does not produce a blank display name. Clearing it is a PATCH (see below), where "" genuinely clears
  • aliases: Array (optional) | Array<String> of extra addresses for the list
  • members: Array (optional) | Array<String> of recipient addresses. Outside addresses are allowed.

The new list's id comes from a read-back, and can be null

As with groups, the mail server's create returns no id, so the new list is read back to supply the :stalwart_id. If that read-back fails the response is still 201, but id is null and aliases is empty — members echoes what you submitted. Call List Mailing Lists to find the id rather than retrying the create.

As on groups, this differs from Update a Mailing List deliberately: an update can fall back to the list as it was before the write, but a create has no such earlier state to fall back on, so a null id is the honest answer for a list that does now exist.

An alias with an unknown domain is dropped on create, not refused

Exactly as on Create a Group: the list's own address must be on a domain of this mailspace, but a submitted alias whose domain is not is silently skipped and the list is still created. Check the aliases array on the 201 body.

Errors
  • 400 email_blank | no address supplied (Email can't be blank.)
  • 400 name_blank | no name supplied (Name can't be blank.)
  • 422 mailing_list_create_failed | the domain is not one of this mailspace's domains, the address is already in use, or the mail server rejected the create
  • plus the shared gates

Update a Mailing List

PATCH /api/mailspace/:mailspace_id/mailing_lists/:stalwart_id

Returns 200 with the same body as View a Mailing List, re-read after the write. A successful PATCH always reports what was saved. When that re-read cannot be performed, the response is still 200 and still describes the list: the pre-write principal is merged with the values that were just written, so

  • id, name and email come back populated,
  • description comes back as the value you just wrote — including "" when you cleared it,
  • an omitted aliases comes back as the aliases that are actually still there,
  • an omitted members comes back as the recipients that are actually still there. An omitted members skips the recipient sync entirely, so the recipients are unchanged by definition.

It is never a null-filled object, and never an empty array for a field the write did not empty. That matters most here: a client that PATCHed such a body straight back would be sending an explicit empty recipient list, which the mail server honours by emptying the list for real — and a list's recipients may be addresses this mailspace does not host, which makes them harder to reconstruct than a group's, not easier.

PATCH is a merge — an omitted key keeps its current value

An omitted aliases leaves the alias set alone; an omitted members skips the recipient sync entirely. Send an explicit empty array to clear either one. A supplied members array is the complete new recipient list — the difference is applied, so anything not listed is unsubscribed.

Params

All optional; every omitted key keeps its current value.

  • description: String | the display name the mail server shows. An explicit "" clears it — the mail server refuses an empty string for this field, so CloudPress sends the explicit null it requires on your behalf.
  • aliases: Array | Array<String>, the complete new alias set
  • members: Array | Array<String>, the complete new recipient list
  • name: String | accepted and ignored. It is the address local part, and the address is immutable. description is the display name — name never reaches it.
  • email: String | ignored. A list's address cannot be changed.
Errors
  • 404 unknown_mailing_list | no such list in this mailspace's tenant. Nothing is written.
  • 422 mailing_list_update_failed | an alias domain is not one of this mailspace's domains, or the mail server rejected the write
  • plus the shared gates

Delete a Mailing List

DELETE /api/mailspace/:mailspace_id/mailing_lists/:stalwart_id

Removes the list principal. The recipients' own mailboxes are untouched — only the list goes. Returns 200.

Returned Params
  • deleted: Boolean | always true
  • id: String | the list's mail-server principal id
  • email: String | the list's primary address
Errors
  • 404 unknown_mailing_list | no such list in this mailspace's tenant. Nothing is deleted.
  • 422 mailing_list_delete_failed | the mail server refused the destroy
  • plus the shared gates

Masked Emails

A masked email is a throwaway forwarding address: the mail server mints the address, and mail sent to it forwards to a mailbox you nominate. Each one is a mail-server object paired with a local row, and it is the local row's GUID that addresses it — the :guid segment, returned as id.

Requires an Enterprise mail server, and you cannot detect that from the API

Masked emails are an Enterprise feature of the mail server. There is no capability probe, so the API cannot tell you whether the feature is licensed:

  • On a build without it, GET …/masked_emails answers 200 with an empty masked_emails array — indistinguishable from a mailspace that simply has none.
  • POST …/masked_emails fails with 422 masked_email_create_failed, which is the same code an ordinary rejection uses.

If creates keep failing with masked_email_create_failed and the target mailbox definitely exists in this mailspace, ask support whether the feature is available to you rather than treating it as a client bug.

:guid is the tenant gate — and the 2000 cap is not this mailspace's

The mail server's masked-email query is server-wide — it filters by account, never by tenant — so ownership is established from the local rows instead. Every by-id path resolves :guid within this mailspace, and a GUID belonging to another workspace is 404 unknown_masked_email, not 403: a 403 would confirm the row exists.

That is also why the 2000-row cap is not a ceiling on this mailspace. It is applied to the server-wide query, before this mailspace's rows are picked out of the result, so on a busy mail server this mailspace's addresses can fall outside the window and simply not appear — with nothing in the response to say so, and however few of them there are.

List Masked Emails

GET /api/mailspace/:mailspace_id/masked_emails

The live list merged with this mailspace's local rows. An address that exists on the mail server but has no local row here is omitted (it is not ours), and a local row whose mail-server object is gone is omitted too.

Returned Params
  • masked_emails: Array
    • id: String | the local row's GUID — this is the :guid you address it by
    • stalwart_id: String | the mail server's own opaque id, for correlation only
    • email: String | the masked address itself
    • description: String | null if unset
    • enabled: Boolean | always a boolean; an address with no stored value reads as true
    • for_domain: String | the site the address was minted for, null if unset
    • url: String | null if unset
    • created_by: String | free-text provenance note, null if unset
    • created_at: String | ISO 8601, server-set, null if the server did not supply it
    • expires_at: String | ISO 8601, server-set and read-only; usually null

Every key is always present — an unknown value is null, never omitted.

Create a Masked Email

POST /api/mailspace/:mailspace_id/masked_emails

Mints a new masked address. The mail server assigns the address itself — you nominate the mailbox it forwards to, not the address it gets. Returns 201 Created.

Params
  • target_mailbox_email: String (required) | a mailbox in this mailspace that the masked address forwards to. Resolved through the tenant's own domain map, so an address outside this mailspace is rejected.
  • description: String (optional)
  • enabled: Boolean (optional) | defaults to true when the key is omitted
  • for_domain: String (optional) | the site the address is used on
  • url: String (optional)
  • created_by: String (optional) | free-text provenance note
curl -X POST \
  -H "Authorization: Bearer $CLOUDPRESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"target_mailbox_email": "ann@example.com", "description": "shop signup"}' \
  https://my.cloudpress.com/api/mailspace/$MAILSPACE_ID/masked_emails
Returned Params (201 Created)

The object is read back from the mail server so the server-set fields (created_at, expires_at) are populated. If that read-back fails the response is still 201 and reports what was submitted, with created_at and expires_at as null.

Errors
  • 400 target_mailbox_email_blank | no target supplied (Target mailbox email can't be blank.)
  • 422 masked_email_create_failed | there is no such mailbox in this mailspace, or the mail server refused — including a build that does not license the feature
  • plus the shared gates

Delete a Masked Email

DELETE /api/mailspace/:mailspace_id/masked_emails/:guid

Removes the mail-server object and the local row, so the address stops forwarding. Mail already delivered through it is untouched. Returns 200.

Returned Params
  • deleted: Boolean | always true
  • id: String | the GUID that was deleted
  • email: String | the address that was deleted
Errors
  • 404 unknown_masked_email | no such row in this mailspace
  • 422 masked_email_delete_failed | the mail server refused the destroy; the local row is kept
  • plus the shared gates

Error Codes

All errors except the OAuth scope failure use the standard {"errors": [...], "code": "..."} envelope described in Error Responses. An unknown or out-of-scope :mailspace_id is the exception in the other direction: 404 with an empty body and no envelope at all.

Shared by every endpoint on this page — see Request Guards:

Code Status Raised on
stalwart_unavailable 503 all requests
not_authorized 403 POST, PATCH, DELETE
mailspace_suspended 403 all requests
pending_delete 403 POST, PATCH, DELETE
not_provisioned 409 all requests

Per endpoint:

Code Status Raised by
address_blank 400 create an alias
target_blank 400 create an alias
invalid_alias 422 create an alias
aliases_unavailable 503 list aliases, create an alias, delete an alias
unknown_alias 404 delete an alias
alias_remove_failed 422 delete an alias
email_blank 400 create a group, create a mailing list
name_blank 400 create a group, create a mailing list
unknown_group 404 view, update, delete a group
group_create_failed 422 create a group
group_update_failed 422 update a group
group_delete_failed 422 delete a group
groups_unavailable 503 list groups
group_members_unavailable 503 list groups, view a group, update a group
mailing_lists_unavailable 503 list mailing lists
unknown_mailing_list 404 view, update, delete a mailing list
mailing_list_create_failed 422 create a mailing list
mailing_list_update_failed 422 update a mailing list
mailing_list_delete_failed 422 delete a mailing list
target_mailbox_email_blank 400 create a masked email
masked_email_create_failed 422 create a masked email
unknown_masked_email 404 delete a masked email
masked_email_delete_failed 422 delete a masked email