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
unpaginated — page 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;
nullif it has neither - target_type: String |
mailbox,group, orlist - 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 emptyaliasesarray 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
domainbelow. - domain: String (optional) | used only when
addresscontains no@. Ignored entirely for a fully qualifiedaddress. Whenaddresshas no@and nodomainis 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, ortarget_emailis 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.comis not interpreted as a format extension the way a default path segment would interpret it.DELETE /api/mailspace/$ID/aliases/sales@example.comis 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_idyou 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,
nullif unset - member_count: Integer | the number of member mailboxes, never
null. The counts are read strictly, so an unreadable count answers503rather 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.
- id: String | mail-server principal id — this is the
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
memberson this response
- all fields from List Groups except
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 single404here 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
descriptionwhen no separatedescriptionis given. - email: String (required) | the group's address. A bare local part is qualified — see
domain. - domain: String (optional) | used only when
emailcontains no@; ignored for a fully qualifiedemail. Defaults to the mailspace's primary domain. - description: String (optional) | display name. Used in place of
namefor this field when it has a value — but an explicit""is not honoured on create: it falls back toname, so a blankdescriptionhere does not produce a blank display name. Clearing it is aPATCH(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,nameandemailcome back populated,descriptioncomes back as the value you just wrote — including""when you cleared it,- an omitted
aliasescomes 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.
descriptionis the display name —namenever 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_idyou 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,
nullif unset - members: Array |
Array<String>of recipient addresses, which may include addresses outside this mailspace - member_count: Integer | the size of
members
- id: String | mail-server principal id — the
Errors
- 503
mailing_lists_unavailable| the list read could not be performed. The read is strict, so an emptymailing_listsarray 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
- mailing_list: Object
- all fields from List Mailing Lists
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 this404— 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
descriptionwhen no separatedescriptionis given. - email: String (required) | the list's address. A bare local part is qualified — see
domain. - domain: String (optional) | used only when
emailcontains no@; ignored for a fully qualifiedemail. Defaults to the mailspace's primary domain. - description: String (optional) | display name. Used in place of
namefor this field when it has a value — but an explicit""is not honoured on create: it falls back toname, so a blankdescriptionhere does not produce a blank display name. Clearing it is aPATCH(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,nameandemailcome back populated,descriptioncomes back as the value you just wrote — including""when you cleared it,- an omitted
aliasescomes back as the aliases that are actually still there, - an omitted
memberscomes back as the recipients that are actually still there. An omittedmembersskips 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.
descriptionis the display name —namenever 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_emailsanswers200with an emptymasked_emailsarray — indistinguishable from a mailspace that simply has none. POST …/masked_emailsfails with422masked_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
:guidyou address it by - stalwart_id: String | the mail server's own opaque id, for correlation only
- email: String | the masked address itself
- description: String |
nullif 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,
nullif unset - url: String |
nullif unset - created_by: String | free-text provenance note,
nullif unset - created_at: String | ISO 8601, server-set,
nullif the server did not supply it - expires_at: String | ISO 8601, server-set and read-only; usually
null
- id: String | the local row's GUID — this is the
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
truewhen 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)
- masked_email: Object
- all fields from List Masked Emails
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 |