feat: provide governed notification email delivery

This commit is contained in:
2026-07-30 17:42:07 +02:00
parent b8029c24d6
commit 0cb6719b91
4 changed files with 203 additions and 2 deletions
+102
View File
@@ -1,10 +1,13 @@
from __future__ import annotations
from dataclasses import dataclass
from email.message import EmailMessage
from email.utils import formatdate, make_msgid
from typing import Any
from sqlalchemy.orm import Session
from govoplan_core.core.mail import NotificationMailDeliveryRequest
from govoplan_core.core.modules import ModuleContext
from govoplan_mail.backend.mail_profiles import (
MailProfileError,
@@ -554,3 +557,102 @@ def delivery_outbox_capability(context: ModuleContext):
configure_runtime(settings=context.settings)
return MailDeliveryOutboxCapability()
class MailNotificationDeliveryCapability:
"""Submit notification email to the Mail-owned durable outbox."""
def submit_notification_mail(
self,
session: Session,
request: NotificationMailDeliveryRequest,
) -> dict[str, object]:
profile_id = str(request.mail_profile_id or "").strip()
from_address = str(request.from_address or "").strip()
if not profile_id or not from_address:
return {
"status": "paused",
"provider": "mail.notificationDelivery",
"error": (
"Notification email requires a Mail profile and sender "
"selected by tenant policy."
),
}
try:
transport = campaign_profile_delivery_summary(
session,
tenant_id=request.tenant_id,
profile_id=profile_id,
smtp_server_id=request.smtp_server_id,
smtp_credential_id=request.smtp_credential_id,
)
except MailProfileError:
return {
"status": "paused",
"provider": "mail.notificationDelivery",
"error": (
"The selected notification Mail profile is unavailable or "
"blocked by effective policy."
),
}
if not transport.get("smtp_available"):
return {
"status": "paused",
"provider": "mail.notificationDelivery",
"error": "The selected notification Mail profile has no usable SMTP transport.",
}
message = EmailMessage()
message["Date"] = formatdate(localtime=True)
message["Message-ID"] = make_msgid()
message["Subject"] = request.subject
message["From"] = from_address
message["To"] = request.recipient
message.set_content(request.body_text)
if request.body_html:
message.add_alternative(request.body_html, subtype="html")
from govoplan_mail.backend.delivery_outbox import submit_delivery_command
command = submit_delivery_command(
session,
tenant_id=request.tenant_id,
command_type="notification",
source_module="notifications",
source_resource_type="notification",
source_resource_id=request.notification_id,
source_version_id=None,
idempotency_key=f"notification:{request.notification_id}",
profile_id=profile_id,
smtp_server_id=(
str(transport.get("smtp_server_id"))
if transport.get("smtp_server_id")
else request.smtp_server_id
),
smtp_credential_id=(
str(transport.get("smtp_credential_id"))
if transport.get("smtp_credential_id")
else request.smtp_credential_id
),
message_bytes=bytes(message),
envelope_from=from_address,
envelope_recipients=[request.recipient],
from_header=from_address,
expected_smtp_transport_revision=str(
transport["smtp_transport_revision"]
),
)
return {
"status": "accepted",
"provider": "mail.delivery_outbox",
"external_message_id": command["id"],
"delivery_status": command["status"],
"duplicate": bool(command.get("duplicate")),
}
def notification_delivery_capability(
context: ModuleContext,
) -> MailNotificationDeliveryCapability:
configure_runtime(settings=context.settings)
return MailNotificationDeliveryCapability()