Integrate campaign delivery with hierarchical mail profiles

This commit is contained in:
2026-07-28 19:33:12 +02:00
parent 38af25ee88
commit 89ae14c032
13 changed files with 699 additions and 108 deletions

View File

@@ -14,11 +14,12 @@ from govoplan_campaign.backend.campaign.mail_profile_boundary import (
CampaignMailProfileBoundaryError,
assert_campaign_uses_mail_profile_reference,
campaign_mail_profile_id,
campaign_mail_resource_ids,
)
from govoplan_campaign.backend.integrations import MailProfileError, files_integration, mail_integration
from govoplan_campaign.backend.path_security import CampaignPathSecurityError, assert_server_safe_campaign_paths
SNAPSHOT_VERSION = "5"
SNAPSHOT_VERSION = "6"
class ExecutionSnapshotError(RuntimeError):
@@ -41,6 +42,10 @@ class ExecutionSnapshot(BaseModel):
campaign_version_id: str
campaign_json_sha256: str
mail_profile_id: str
smtp_server_id: str | None = None
smtp_credential_id: str | None = None
imap_server_id: str | None = None
imap_credential_id: str | None = None
created_at: str
build_token: str | None = None
built_at: str | None = None
@@ -75,12 +80,17 @@ def profile_delivery_summary(session: Session, version: CampaignVersion) -> dict
campaign = session.get(Campaign, version.campaign_id)
if campaign is None:
raise ExecutionSnapshotError("Campaign not found for mail-server profile resolution")
references = campaign_mail_resource_ids(raw_json)
try:
return mail.campaign_profile_delivery_summary(
session,
tenant_id=campaign.tenant_id,
campaign_id=campaign.id,
profile_id=profile_id,
smtp_server_id=references["smtp_server_id"],
smtp_credential_id=references["smtp_credential_id"],
imap_server_id=references["imap_server_id"],
imap_credential_id=references["imap_credential_id"],
)
except MailProfileError as exc:
raise ExecutionSnapshotError(str(exc)) from exc
@@ -102,6 +112,19 @@ def _assert_snapshot_profile_matches_version(version: CampaignVersion, snapshot:
"The campaign's Mail profile reference differs from the built execution snapshot. "
"Revalidate and rebuild the campaign before delivery."
)
references = campaign_mail_resource_ids(raw_json)
for key in (
"smtp_server_id",
"smtp_credential_id",
"imap_server_id",
"imap_credential_id",
):
configured = references[key]
if configured and configured != getattr(snapshot, key):
raise ExecutionSnapshotError(
"The campaign's Mail server or credential selection differs from the built "
"execution snapshot. Revalidate and rebuild before delivery."
)
def _assert_version_mail_profile_boundary(raw_json: dict[str, Any]) -> None:
@@ -163,6 +186,10 @@ def create_execution_snapshot(
smtp_transport_revision: str,
imap_transport_revision: str | None,
delivery: DeliveryConfig,
smtp_server_id: str | None = None,
smtp_credential_id: str | None = None,
imap_server_id: str | None = None,
imap_credential_id: str | None = None,
jobs: Iterable[CampaignJob] = (),
build_summary: dict[str, Any] | None = None,
) -> tuple[dict[str, Any], str]:
@@ -176,6 +203,10 @@ def create_execution_snapshot(
campaign_version_id=version.id,
campaign_json_sha256=_sha256(raw_json),
mail_profile_id=mail_profile_id,
smtp_server_id=smtp_server_id,
smtp_credential_id=smtp_credential_id,
imap_server_id=imap_server_id,
imap_credential_id=imap_credential_id,
build_token=str(summary.get("build_token") or "") or None,
built_at=str(summary.get("built_at") or "") or None,
job_count=len(job_list),
@@ -316,14 +347,18 @@ def ensure_execution_snapshot(
)
if not jobs:
raise ExecutionSnapshotError("Campaign version has no built jobs; rebuild it before delivery")
revisions = profile_transport_revisions(session, version)
if not revisions["smtp"]:
summary = profile_delivery_summary(session, version)
if not summary.get("smtp_transport_revision"):
raise ExecutionSnapshotError("The selected Mail profile has no SMTP transport revision")
payload, digest = create_execution_snapshot(
version,
mail_profile_id=profile_id,
smtp_transport_revision=revisions["smtp"],
imap_transport_revision=revisions["imap"],
smtp_server_id=summary.get("smtp_server_id"),
smtp_credential_id=summary.get("smtp_credential_id"),
imap_server_id=summary.get("imap_server_id"),
imap_credential_id=summary.get("imap_credential_id"),
smtp_transport_revision=summary["smtp_transport_revision"],
imap_transport_revision=summary.get("imap_transport_revision"),
delivery=config.delivery,
jobs=jobs,
build_summary=version.build_summary if isinstance(version.build_summary, dict) else {},