Team
Share an organization's inboxes with the people you work with.
An organization can have more than one person in it. Each person signs in with their own email address, gets their own keys, and can use the inboxes they were given — so a support address can be worked by the whole team instead of through one shared key.
Roles
| Role | Can |
|---|---|
owner |
Everything. The address the organization was opened with; there is one, and it cannot be removed or demoted. |
admin |
Everything the owner can, including inviting and removing people. |
member |
Read, reply, draft and label mail in their inboxes. Cannot change the organization — no keys, domains, webhooks, inboxes, lists or people. |
A member can be given every inbox (the default) or only some. Owners and admins always see every inbox.
Inviting someone
curl -X POST https://api.carlyemail.com/v0/members \
-H "Authorization: Bearer $CARLYEMAIL_API_KEY" \
-H 'content-type: application/json' \
-d '{"email": "priya@rivercycles.co.uk", "role": "member",
"inbox_ids": ["support@rivercycles.co.uk"]}'
They get an email saying who added them and which inboxes they can use. There
is no link to click: they sign in with that address — at the console, with the
CLI, or through POST /v0/agent/sign-in — and the invitation becomes a
membership then. Until they do, their status is invited.
Only owners and admins can invite, and only once the owner email is confirmed. Each plan has a cap on people, the owner included — see limits.
Changing and removing people
PATCH /v0/members/{member_id} changes a role, inboxes or name; only the fields
you send change. DELETE /v0/members/{member_id} removes someone and revokes
every key they held, including chat clients they connected. Anyone can remove
themselves.
GET /v0/members lists everyone. Every role can read it, because assigning
work starts with knowing who is on the team.
Signing in to more than one organization
One address can belong to several organizations — its own, and teams it was
invited to. Signing in lands in the one it owns, or else the one used most
recently; pass organization_id to POST /v0/agent/sign-in/verify to choose.
GET /v0/auth/me lists them all, and POST /v0/auth/switch with an
organization_id returns a key there, without another code:
curl -X POST https://api.carlyemail.com/v0/auth/switch \
-H "Authorization: Bearer $CARLYEMAIL_API_KEY" \
-H 'content-type: application/json' \
-d '{"organization_id": "org_..."}'
A chat client connected over OAuth with a teammate's address joins their organization as them, with their role and inboxes.
Keys for people and keys for agents
A key from signing in speaks for that person: their role and inbox access
apply to everything it does. Keys made with POST /v0/api-keys speak for the
organization, as they always have, and are how you hand one agent one inbox —
see authentication.
A member limited to several inboxes is asked to name one (inbox_id_required)
wherever a listing would otherwise cover the whole organization, such as
GET /v0/threads. Over MCP, reading without an inbox returns the newest mail
from all of theirs.
Who did what
Everything the organization sends records who sent it, as sent_by on the
message: a person (type: "member", with member_id, name and email) or a
key made for an agent (type: "api_key", with api_key_id and the key's
name). Name your agents' keys and each shows up as itself.
"sent_by": { "type": "member", "member_id": "mem_...", "name": "Priya Shah", "email": "priya@rivercycles.co.uk" }
Drafts carry created_by and updated_by, and a scheduled draft is sent as
whoever wrote it. Label changes in an inbox's history
(GET /v0/inboxes/{inbox_id}/events) carry actor, which is how one teammate
or agent can tell that another already handled a message.
Mail received from outside, and anything sent before this was recorded, has no
sent_by.
Working the inbox together
Assigning. Hand a conversation to a person, to an agent's key, or to
yourself, and take it back with null:
curl -X PATCH https://api.carlyemail.com/v0/inboxes/support@rivercycles.co.uk/threads/thread_... \
-H "Authorization: Bearer $CARLYEMAIL_API_KEY" \
-H 'content-type: application/json' \
-d '{"assignee_id": "mem_..."}'
assignee_id takes a member_id, an api_key_id, or "me". Mail is never
handed to someone who cannot open its inbox. Every conversation carries
assignee, and listings filter with ?assignee=me, ?assignee=none for the
unassigned, or an id. Over MCP, update_thread takes assignee_id and
list_threads takes assignee. A new message on a conversation leaves it with
whoever has it.
Notes. Leave a note on a conversation for the team. Notes are never sent, never delivered to webhooks, and come back with the conversation, so an agent reading it sees what a teammate wrote:
curl -X POST https://api.carlyemail.com/v0/inboxes/support@rivercycles.co.uk/threads/thread_.../notes \
-H "Authorization: Bearer $CARLYEMAIL_API_KEY" \
-H 'content-type: application/json' \
-d '{"text": "Customer since 2019. Refund approved."}'
Over MCP it is add_note. Whoever wrote a note can delete it, and so can an
owner or admin.
Someone is already replying. GET on a conversation lists drafts: the
unsent replies started on it, with created_by and updated_by. The console
shows "Priya is writing a reply" so two people do not answer the same email.