The mail module serves mail domains, OAuth app configurations per provider, connected mail accounts (OAuth and IMAP), and messages with sync, send, drafts, attachments, read state, trash, and threads. All 28 endpoints sit under /api/v1/mail. For business meaning see the Mail module page.
Group facts¶
| Item | Value | Source |
|---|---|---|
| Base path | /api/v1/mail |
src/cluster_erp/core/module_base.py:37-38, src/cluster_erp/modules/mail/module.py:42 |
| Routers | 4 router objects in 4 files; listed in the section Endpoint summary | src/cluster_erp/modules/mail/module.py:34 |
| Endpoints | 28, counted from the route decorators and add_api_route calls of the routers that register_routes mounts |
src/cluster_erp/modules/mail/accounts/routes.py, src/cluster_erp/modules/mail/domains/routes.py, src/cluster_erp/modules/mail/messages/routes.py ... |
| Authentication | Bearer header or session cookie, resolved by get_current_user; see Authentication and headers |
src/cluster_erp/modules/auth/guards.py:85-167 |
| Permission guard | 28 of 28 routes declare require_permission |
src/cluster_erp/modules/auth/guards.py:205-240 |
| App entitlement gate | require_app("mail") on every mounted router |
src/cluster_erp/modules/mail/module.py:42 |
| Error format | JSON body {"error": {...}}; see Errors |
src/cluster_erp/server/error_handler.py:33-46 |
Endpoint summary¶
One row per sub-module folder. The router prefix is added to the base path.
| Sub-module | Routers | Endpoints | Router source |
|---|---|---|---|
domains |
router (/domains) |
5 | src/cluster_erp/modules/mail/domains/routes.py:21 |
providers |
router (/oauth-apps) |
3 | src/cluster_erp/modules/mail/providers/routes.py:27 |
accounts |
router (/accounts) |
7 | src/cluster_erp/modules/mail/accounts/routes.py:30 |
messages |
router (/messages) |
13 | src/cluster_erp/modules/mail/messages/routes.py:33 |
Mail domains endpoints¶
Mail domain records.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/mail/domains/ |
list_domains |
mail_domain.read |
none | search, limit, offset |
MailDomainListResponse |
200 (default) | src/cluster_erp/modules/mail/domains/routes.py:24 |
GET |
/api/v1/mail/domains/{domain_id} |
get_domain |
mail_domain.read |
none | none | MailDomainRead |
200 (default) | src/cluster_erp/modules/mail/domains/routes.py:43 |
POST |
/api/v1/mail/domains/ |
create_domain |
mail_domain.create |
MailDomainCreate |
none | MailDomainRead |
201 |
src/cluster_erp/modules/mail/domains/routes.py:55 |
PUT |
/api/v1/mail/domains/{domain_id} |
update_domain |
mail_domain.update |
MailDomainUpdate |
none | MailDomainRead |
200 (default) | src/cluster_erp/modules/mail/domains/routes.py:65 |
DELETE |
/api/v1/mail/domains/{domain_id} |
delete_domain |
mail_domain.delete |
none | none | none | 204 |
src/cluster_erp/modules/mail/domains/routes.py:78 |
OAuth app configurations endpoints¶
Provider OAuth app configurations: list, upsert, and remove.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/mail/oauth-apps/ |
list_provider_configs |
mail_provider.read |
none | none | MailProviderListResponse |
200 (default) | src/cluster_erp/modules/mail/providers/routes.py:30 |
PUT |
/api/v1/mail/oauth-apps/{provider} |
upsert_provider_config |
mail_provider.update |
MailProviderUpsert |
none | MailProviderRead |
200 (default) | src/cluster_erp/modules/mail/providers/routes.py:44 |
DELETE |
/api/v1/mail/oauth-apps/{provider} |
delete_provider_config |
mail_provider.delete |
none | none | none | 204 |
src/cluster_erp/modules/mail/providers/routes.py:57 |
Mail accounts endpoints¶
Connected accounts, available providers, OAuth start and callback, IMAP connect, and disconnect.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/mail/accounts/ |
list_accounts |
mail_account.read |
none | none | MailAccountListResponse |
200 (default) | src/cluster_erp/modules/mail/accounts/routes.py:33 |
GET |
/api/v1/mail/accounts/providers |
get_providers |
mail_account.read |
none | none | ProviderInfo |
200 (default) | src/cluster_erp/modules/mail/accounts/routes.py:47 |
POST |
/api/v1/mail/accounts/oauth/{provider}/start |
oauth_start |
mail_account.connect |
none | none | OAuthStartResponse |
200 (default) | src/cluster_erp/modules/mail/accounts/routes.py:66 |
POST |
/api/v1/mail/accounts/oauth/callback |
oauth_callback |
mail_account.connect |
OAuthCallbackRequest |
none | MailAccountRead |
200 (default) | src/cluster_erp/modules/mail/accounts/routes.py:99 |
POST |
/api/v1/mail/accounts/imap |
connect_imap |
mail_account.connect |
ImapConnectRequest |
none | MailAccountRead |
200 (default) | src/cluster_erp/modules/mail/accounts/routes.py:166 |
GET |
/api/v1/mail/accounts/{account_id} |
get_account |
mail_account.read |
none | none | MailAccountRead |
200 (default) | src/cluster_erp/modules/mail/accounts/routes.py:185 |
DELETE |
/api/v1/mail/accounts/{account_id} |
disconnect_account |
mail_account.delete |
none | none | none | 204 |
src/cluster_erp/modules/mail/accounts/routes.py:197 |
Messages endpoints¶
Sync, send, drafts, attachments, read state, trash, threads, list, and read.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
POST |
/api/v1/mail/messages/sync |
sync_messages |
mail_message.sync |
none | account_id |
MailSyncResponse |
200 (default) | src/cluster_erp/modules/mail/messages/routes.py:43 |
POST |
/api/v1/mail/messages/send |
send_message |
mail_message.send |
MailSendRequest |
none | MailMessageSummary |
200 (default) | src/cluster_erp/modules/mail/messages/routes.py:57 |
POST |
/api/v1/mail/messages/drafts |
save_draft |
mail_message.create |
MailDraftRequest |
none | MailMessageSummary |
200 (default) | src/cluster_erp/modules/mail/messages/routes.py:84 |
POST |
/api/v1/mail/messages/drafts/{draft_id}/discard |
discard_draft |
mail_message.delete |
none | none | none | 204 |
src/cluster_erp/modules/mail/messages/routes.py:105 |
GET |
/api/v1/mail/messages/{message_id}/attachments |
list_attachments |
mail_message.read |
none | none | list[MailAttachmentRead] |
200 (default) | src/cluster_erp/modules/mail/messages/routes.py:119 |
POST |
/api/v1/mail/messages/{draft_id}/attachments |
upload_attachment |
mail_message.update |
multipart file |
none | MailAttachmentRead |
201 |
src/cluster_erp/modules/mail/messages/routes.py:130 |
POST |
/api/v1/mail/messages/{draft_id}/attachments/{attachment_id}/remove |
remove_attachment |
mail_message.update |
none | none | none | 204 |
src/cluster_erp/modules/mail/messages/routes.py:151 |
GET |
/api/v1/mail/messages/{message_id}/attachments/{attachment_id}/content |
download_attachment |
mail_message.read |
none | none | Response |
200 (default) | src/cluster_erp/modules/mail/messages/routes.py:162 |
POST |
/api/v1/mail/messages/{message_id}/read |
set_read_state |
mail_message.update |
MailReadState |
none | none | 204 |
src/cluster_erp/modules/mail/messages/routes.py:185 |
POST |
/api/v1/mail/messages/{message_id}/trash |
trash_message |
mail_message.update |
none | none | none | 204 |
src/cluster_erp/modules/mail/messages/routes.py:197 |
GET |
/api/v1/mail/messages/{message_id}/thread |
message_thread |
mail_message.read |
none | none | list[MailMessageSummary] |
200 (default) | src/cluster_erp/modules/mail/messages/routes.py:208 |
GET |
/api/v1/mail/messages/ |
list_messages |
mail_message.read |
none | folder, account_id, search, limit, offset |
MailMessageListResponse |
200 (default) | src/cluster_erp/modules/mail/messages/routes.py:220 |
GET |
/api/v1/mail/messages/{message_id} |
get_message |
mail_message.read |
none | none | MailMessageDetail |
200 (default) | src/cluster_erp/modules/mail/messages/routes.py:243 |
Error codes¶
The table lists the error codes that code under src/cluster_erp/modules/mail/ raises, found by a syntax-tree scan for AppError(...), the factories in src/cluster_erp/core/errors.py (not_found, conflict, protected, permission_denied, privilege_escalation, check_version), and HTTPException(...). A status of 400 is the AppError default (src/cluster_erp/core/errors.py:23). Guard errors (401, 403), the entitlement codes, and validation errors (422) apply to every route and are listed on Errors.
| Code | HTTP status | Raised in | First site |
|---|---|---|---|
RESOURCE_NOT_FOUND |
404 |
accounts, domains, messages, providers |
src/cluster_erp/modules/mail/accounts/routes.py:193 |
VALIDATION_ERROR |
400, 404, 500, 502 |
accounts, messages, providers |
src/cluster_erp/modules/mail/accounts/routes.py:77 |
Events emitted¶
The table lists the contract classes that code under src/cluster_erp/modules/mail/ passes directly to EventBus.emit, found by a syntax-tree scan. Events built in a variable first are not counted. The contracts are described in Contracts reference.
None found by the scan.
Related pages¶
Verified against commit 92f8051 on 2026-11-13.