Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Business modules

Module

Mail module (mail)

Module page for mail, which connects external mail accounts, caches and sends messages, keeps mail domains and OAuth apps, and owns the system mailbox contract.

The mail module in src/cluster_erp/modules/mail/ lets a user connect an external mail account and read and write mail inside the product. It keeps the connected accounts, a local cache of messages and attachments, the mail domains an administrator defines, and the OAuth applications for providers that sign in through OAuth. It also answers SendTransactionalEmail, the contract every other module uses to send a system e-mail. This page describes transport and sign-in at mechanism level only.

At a glance

Item Value Source
Package src/cluster_erp/modules/mail/ folder
Module class Module src/cluster_erp/modules/mail/module.py:18
name / version mail / 0.1.0 src/cluster_erp/modules/mail/module.py:19-25
API prefix /api/v1/mail (ModuleBase.api_prefix), with the app gate require_app("mail") src/cluster_erp/core/module_base.py:37-38, src/cluster_erp/modules/mail/module.py:34-45
Sub-modules 4: accounts, domains, messages, providers; plus transport.py, oauth.py, imap_verify.py, crypto.py, privacy_handlers.py folder listing
Tables 5: mail_domains, mail_provider_configs, mail_accounts, mail_messages, mail_attachments __tablename__ in the four models.py files
Entities in metadata 3 keys in entities src/cluster_erp/modules/mail/module.py:143-147
Routes 28 route decorators in the package grep for @...router.get/post/put/patch/delete
Actions 0 ActionMeta entries grep "ActionMeta(" in the package
Migrations 4 files files in migrations/versions/ whose text contains one of the five table names
Contracts consumes 3, produces 0 see the section "Events and queries"
Web app presence System catalogue app mail with its own screen; mail settings under Settings src/cluster_erp/modules/app_store/catalog.json (entry mail), webapp/js/mail.js, webapp/js/mail-settings.js
Desktop presence generic widgets only src/cluster_erp/desktop/modules/ has no mail folder

What the module does

  • Connects a mail account of kind google, microsoft, or imap for the signed-in user. Google and Microsoft accounts connect through an OAuth authorization-code flow; IMAP accounts connect with an address and an app password, verified by a real login before the account is saved (src/cluster_erp/modules/mail/oauth.py:1-15, src/cluster_erp/modules/mail/imap_verify.py:1-8).
  • Syncs the inbox of an account into a local cache and keeps attachments in storage (src/cluster_erp/modules/mail/messages/sync.py:1-3, src/cluster_erp/modules/mail/messages/sync.py:126-203).
  • Sends mail, saves drafts with attachments, marks messages read, moves them to trash, and shows threads (src/cluster_erp/modules/mail/messages/routes.py:43-252).
  • Lets an administrator define mail domains with server coordinates and register OAuth applications per provider (src/cluster_erp/modules/mail/domains/models.py:9-30, src/cluster_erp/modules/mail/providers/models.py:9-35).
  • Sends system e-mail for other modules through the platform SMTP settings (src/cluster_erp/modules/mail/module.py:72-103).

Sub-modules

Sub-module Purpose Main entities Router Source
accounts Connected accounts, OAuth start and callback, IMAP connect MailAccount /accounts src/cluster_erp/modules/mail/accounts/routes.py:30
domains Administrator-defined mail domains MailDomain /domains src/cluster_erp/modules/mail/domains/routes.py:21
messages Cached messages, drafts, attachments, sync, send MailMessage, MailAttachment /messages src/cluster_erp/modules/mail/messages/routes.py:33
providers OAuth applications per provider MailProviderConfig /oauth-apps src/cluster_erp/modules/mail/providers/routes.py:27

Data model

Entity Table Key fields Notes Source
MailDomain mail_domains name, kind, imap_host, imap_port, smtp_host, smtp_port, use_tls, is_active kind is google, microsoft, or imap src/cluster_erp/modules/mail/domains/models.py:9-30, src/cluster_erp/modules/mail/domains/schemas.py:11
MailProviderConfig mail_provider_configs provider, client_id, client_secret_enc, tenant, enabled one row per provider; the secret is stored encrypted and never returned src/cluster_erp/modules/mail/providers/models.py:9-35
MailAccount mail_accounts user_id, provider, email_address, display_name, domain_id, server coordinates, encrypted token and password columns, status, status_detail, last_sync_at one user may connect several accounts src/cluster_erp/modules/mail/accounts/models.py:13-55
MailMessage mail_messages account_id, folder, provider_message_id, thread_id, sender, recipients, subject, snippet, body_text, body_html, sent_at, is_read, has_attachments, threading headers local mirror keyed by account and provider message id src/cluster_erp/modules/mail/messages/models.py:13-57
MailAttachment mail_attachments message_id, filename, mime_type, size_bytes, provider_ref, storage_key, is_inline, content_id metadata; bytes are fetched from the provider or held in storage src/cluster_erp/modules/mail/messages/models.py:60-81

Provider secrets, OAuth tokens, and IMAP app passwords are encrypted at rest with a reversible cipher keyed from a server setting, and are decrypted only to talk to the provider (src/cluster_erp/modules/mail/crypto.py:1-49, src/cluster_erp/modules/mail/transport.py:1-12). The settings are listed on settings reference; secret storage is described on second factor and secret storage.

Account, provider, and message path

flowchart TD
 A["admin: MailProviderConfig per provider"] --> B["POST /accounts/oauth/provider/start"]
 D["admin: MailDomain with server coordinates"] --> I["POST /accounts/imap"]
 B --> C["provider consent, POST /accounts/oauth/callback"]
 C --> ACC["MailAccount: google or microsoft"]
 I --> V["verify_imap_login"]
 V --> ACC2["MailAccount: imap"]
 ACC --> S["MailSyncService.sync_account"]
 ACC2 --> S
 S --> T["transport.fetch_inbox"]
 T --> M["MailMessage and MailAttachment cache"]
 M --> SEND["MailSyncService.send via transport.send_message"]

In prose:

  1. An administrator registers the OAuth application of a provider under /oauth-apps and may define mail domains under /domains (src/cluster_erp/modules/mail/providers/routes.py:27-66, src/cluster_erp/modules/mail/domains/routes.py:21-86).
  2. For Google or Microsoft, the user starts the flow, consents at the provider, and returns to the callback. A signed, short-lived state value binds the flow to the user and the provider. The product receives tokens and never the user's provider password (src/cluster_erp/modules/mail/oauth.py:1-15, src/cluster_erp/modules/mail/accounts/routes.py:66-164).
  3. For IMAP, the user sends the address and an app password; the module performs a real login before saving (src/cluster_erp/modules/mail/accounts/routes.py:166-183, src/cluster_erp/modules/mail/accounts/services.py:83-160).
  4. POST /messages/sync fetches the inbox through the provider transport and upserts it into the cache. A transport error sets the account status to error with the detail; success sets connected and last_sync_at (src/cluster_erp/modules/mail/messages/sync.py:126-203).
  5. Expired access tokens are refreshed with the stored refresh token and written back encrypted (src/cluster_erp/modules/mail/messages/sync.py:83-124).
  6. Sending builds a message and hands it to the provider transport: IMAP accounts send through SMTP, Google and Microsoft through their mail APIs (src/cluster_erp/modules/mail/transport.py:1-12, src/cluster_erp/modules/mail/messages/sync.py:205-286).

Screens and metadata

get_metadata declares a sidebar group Mail with one settings section, Mail Settings, whose only tab is mail_domain (src/cluster_erp/modules/mail/module.py:119-142). The three entities are mail_domain, mail_account, and mail_message (src/cluster_erp/modules/mail/module.py:143-147). The mail app screen is a custom screen of the web app; see mail and profile screens.

Status and actions

The module declares no ActionMeta. An account carries status (default connected) and status_detail (src/cluster_erp/modules/mail/accounts/models.py:48-49). A message lives in a folder, default inbox; drafts use drafts and trash uses trash (src/cluster_erp/modules/mail/messages/models.py:22, src/cluster_erp/modules/mail/messages/sync.py:331-348, src/cluster_erp/modules/mail/messages/sync.py:470-488).

Business rules

  • Every message and account read is scoped to the signed-in user (src/cluster_erp/modules/mail/messages/sync.py:52-81, src/cluster_erp/modules/mail/accounts/services.py:169-184).
  • Discarding a draft sets deleted_at, deleted_by, and the reason draft_discarded on the draft and its attachments (src/cluster_erp/modules/mail/messages/sync.py:331-347).
  • Trash moves a message to the provider's trash and to the local trash folder (src/cluster_erp/modules/mail/messages/sync.py:470-488).
  • Disconnecting an account, deleting a domain, or deleting an OAuth application sets deleted_at (src/cluster_erp/modules/mail/accounts/services.py:185-191, src/cluster_erp/modules/mail/domains/services.py:55-61, src/cluster_erp/modules/mail/providers/services.py:79-87).
  • The system mailbox reports sent=False with the reason not_configured when no SMTP is configured and no_recipient without a recipient. sent=True means that the SMTP server accepted the message, not that it was delivered (src/cluster_erp/modules/mail/module.py:72-103).

Events and queries

The module consumes 3 contracts and produces none, counted as on the fleet module page. The full list is on the contracts reference.

Direction Contract class Handler or emitter Source
subscribes SendTransactionalEmail _handle_transactional_email src/cluster_erp/modules/mail/module.py:47-53, src/cluster_erp/modules/mail/module.py:72-103
subscribes CollectSubjectData, AnonymiseSubject privacy_handlers.py src/cluster_erp/modules/mail/module.py:55-70

The intranet notification fan-out is one caller of SendTransactionalEmail (src/cluster_erp/modules/intranet/notify.py:91-99). The platform side of system mail is on PDF and system mail.

Permissions

Permission key Used by Source
mail_account.read, .connect, .delete accounts src/cluster_erp/modules/mail/accounts/routes.py:33-200
mail_domain.read, .create, .update, .delete domains src/cluster_erp/modules/mail/domains/routes.py:24-81
mail_provider.read, .update, .delete OAuth applications src/cluster_erp/modules/mail/providers/routes.py:30-60
mail_message.read, .create, .update, .delete, .send, .sync messages src/cluster_erp/modules/mail/messages/routes.py:43-246

Privacy, retention, and history

privacy_handlers.py counts the messages that carry a person's addresses. Anonymisation is an operator decision (include_correspondence); when included, the person's name and address are replaced in sender and recipient fields and inside subject, snippet, and bodies (src/cluster_erp/modules/mail/privacy_handlers.py:1-11). See privacy and data-subject requests. The module has no retention_handlers.py. Changes are captured by change history.

Analytics facts

This module has no analytics_facts.py.

Settings and reference data

Mail domains and OAuth applications are the module's reference data, kept by administrators (src/cluster_erp/modules/mail/domains/routes.py:21-86, src/cluster_erp/modules/mail/providers/routes.py:27-66). Secret values are never shown: GET /oauth-apps/ returns whether a secret is set (has_secret), not the secret (src/cluster_erp/modules/mail/providers/models.py:9-16, src/cluster_erp/modules/mail/providers/schemas.py:6-15).

Tests

Found by name (web-client/_e2e_mail*.mjs) and by a grep for modules.mail in tests/.

Kind File Covers
e2e web-client/_e2e_mail.mjs, web-client/_e2e_mailapp.mjs mail settings and the mail app
e2e web-client/_e2e_mail_drafts.mjs drafts and attachments
pytest tests/test_mail_transport.py provider transport parsing and building
pytest tests/test_system_mail.py the system mailbox

Terms introduced

Term Code identifier Definition Turkish term Source
mail account MailAccount An external mailbox a user connected to the product. Mail hesabı src/cluster_erp/modules/mail/accounts/models.py:13
mail domain MailDomain An administrator-defined domain with its provider kind and server coordinates. Mail alan adı src/cluster_erp/modules/mail/domains/models.py:9
system mailbox SendTransactionalEmail The single SMTP identity that the application uses for its own mail, which other modules reach through SendTransactionalEmail. sistem posta kutusu src/cluster_erp/modules/mail/module.py:47-53

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13