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>>)¶
SaasWorkloadConfigbehindMailProvider— regular Mail, resolved viamail_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>>)¶
SaasWorkloadConfigbehindArchiveMailProvider— M365’s separatearchive_mail_dbmailbox.
- 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 optionalsharedcontext passed straight through toSaasWorkloadProvider.createfor M365’s multi-candidateUSER_EXCHANGE/GROUP_EXCHANGEdispatch.
- async synology_apm_repo.sdk.units.saas.mail.ArchiveMailProvider(repo, version, saas_streams, *, shared=None)¶
M365’s Archive mailbox — a real, separate
mail_tablecoexisting with regular Mail in the sameUSER_EXCHANGEversion, 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
UnsupportedDataFormatErrorwhenever the object-name index doesn’t resolvearchive_mail_dbfor this version — there is no scan fallback — sounits/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 forGROUP_EXCHANGE: its ownadditional_metanever has anarchive_mail_dbentry.