synology_apm_repo.sdk.units.saas.mail module

MailProvider/ArchiveMailProvider: M365/GWS Mail via mail_table + the shared X-ABL-ID fragment-reassembly engine (see build_eml), built as a SaasWorkloadProvider config. Both platforms share this module’s tree/reassembly code — the content_list/META JSON shape (fragment_id/type/file_name/content_id/object_id/ size) is close enough that one code path covers both.

M365’s mail_table.mail_id has no UNIQUE constraint (multiple historical version rows can coexist, and the current tree/listing query does not de-duplicate or tie-break between them); GWS’s does (single current row only).

M365 Mail’s tree is normally RecursiveGroupFlatTree over mail_folder_table’s own real, nested folder hierarchy (_open_m365_folder_tree) — every real folder shows up, including one with zero backed-up messages; see _open_m365_folder_tree for when it degrades to the older, flat, item-driven SyntheticGroupedTree instead (folder names resolved separately via _m365_folder_names). GWS has no folder hierarchy at all, only many-to-many labels, surfaced as an extra_attrs field rather than a tree grouping (_gws_mail_labels), so it always uses the flat tree. Archive Mail is a real, separate M365-only mailbox with a schema byte-for-byte identical to regular Mail’s, resolved via the object-name index alone — see ArchiveMailProvider.

synology_apm_repo.sdk.units.saas.mail.MAIL_CONFIG = SaasWorkloadConfig(root_name='Mail', leaf_kind=<UnitKind.MAIL: 'mail'>, tables=('mail_table',), tree_factory=<function _make_build_tree.<locals>._build_tree>, assemble=<function _assemble>, object_names={'mail_table': ('mail_db', 'group_mail_db'), 'mail_folder_table': ('mail_folder_db', 'group_mail_folder_db')}, extra_attrs=<function _mail_extra_attrs>, group_attrs=<function SaasWorkloadConfig.<lambda>>, leaf_size=<function SaasWorkloadConfig.<lambda>>)

SaasWorkloadConfig behind MailProvider — regular Mail, resolved via mail_db/group_mail_db.

synology_apm_repo.sdk.units.saas.mail.ARCHIVE_MAIL_CONFIG = SaasWorkloadConfig(root_name='Archive', leaf_kind=<UnitKind.MAIL: 'mail'>, tables=('mail_table',), tree_factory=<function _make_build_tree.<locals>._build_tree>, assemble=<function _assemble>, object_names={'mail_table': ('archive_mail_db',), 'mail_folder_table': ('archive_mail_folder_db',)}, extra_attrs=<function _archive_mail_extra_attrs>, group_attrs=<function SaasWorkloadConfig.<lambda>>, leaf_size=<function SaasWorkloadConfig.<lambda>>)

SaasWorkloadConfig behind ArchiveMailProvider — M365’s separate archive_mail_db mailbox.

async synology_apm_repo.sdk.units.saas.mail.MailProvider(repo, version, saas_streams, *, shared=None)

Constructor-style factory over MAIL_CONFIG — callable exactly like a constructor (await MailProvider(repo, version, saas_streams)), with an optional shared context passed straight through to SaasWorkloadProvider.create for M365’s multi-candidate USER_EXCHANGE/GROUP_EXCHANGE dispatch.

async synology_apm_repo.sdk.units.saas.mail.ArchiveMailProvider(repo, version, saas_streams, *, shared=None)

M365’s Archive mailbox — a real, separate mail_table coexisting with regular Mail in the same USER_EXCHANGE version, with a schema byte-for-byte identical to regular Mail’s — implemented generically from that schema regardless of whether any given account’s Archive folder is ever populated (the same precedent Teams/Chat’s own not-yet-populated message content follows).

Raises UnsupportedDataFormatError whenever the object-name index doesn’t resolve archive_mail_db for this version — there is no scan fallback — so units/dispatch.py’s “try every candidate” dispatch simply omits Archive from that version’s sibling set rather than risk cross-attributing mail between the two. Not offered for GROUP_EXCHANGE: its own additional_meta never has an archive_mail_db entry.