feat: add durable mail delivery outbox

This commit is contained in:
2026-07-30 14:26:53 +02:00
parent dec5a4e350
commit b8029c24d6
13 changed files with 1913 additions and 17 deletions
+119
View File
@@ -20,6 +20,9 @@ from govoplan_mail.backend.schemas import (
MailCredentialUpdateRequest,
MailImapFolderListResponse,
MailImapFolderResponse,
MailDeliveryCommandResponse,
MailDeliveryReconcileRequest,
MailDeliveryResendRequest,
MailMailboxBootstrapResponse,
MailImapTestRequest,
MailMailboxAttachmentResponse,
@@ -79,6 +82,14 @@ from govoplan_mail.backend.mail_profiles import (
)
from govoplan_mail.backend.config import ImapConfig, SmtpConfig
from govoplan_mail.backend.runtime import get_registry
from govoplan_mail.backend.delivery_outbox import (
MailDeliveryError,
delivery_command_diagnostics,
delivery_command_summary,
get_delivery_command,
reconcile_delivery_command,
resend_delivery_command,
)
from govoplan_mail.backend.server_hierarchy import (
MailHierarchyContext,
MailServerHierarchyError,
@@ -120,6 +131,114 @@ DEFAULT_MAILBOX_MESSAGE_LIMIT = 50
CAPABILITY_ADDRESSES_LOOKUP = "addresses.lookup"
@router.get(
"/delivery-commands/{command_id}",
response_model=MailDeliveryCommandResponse,
)
def get_mail_delivery_command(
command_id: str,
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(get_api_principal),
):
"""Return the business-safe status of one tenant Mail command."""
_require_scope(principal, "mail:profile:read")
try:
command = get_delivery_command(
session,
tenant_id=principal.tenant_id,
command_id=command_id,
)
except MailDeliveryError as exc:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(exc),
) from exc
return MailDeliveryCommandResponse(result=delivery_command_summary(command))
@router.get(
"/delivery-commands/{command_id}/diagnostics",
response_model=MailDeliveryCommandResponse,
)
def get_mail_delivery_command_diagnostics(
command_id: str,
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(get_api_principal),
):
"""Return bounded recipient and attempt evidence to diagnostic actors."""
_require_scope(principal, "mail:delivery:diagnostic")
try:
result = delivery_command_diagnostics(
session,
tenant_id=principal.tenant_id,
command_id=command_id,
)
except MailDeliveryError as exc:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(exc),
) from exc
return MailDeliveryCommandResponse(result=result)
@router.post(
"/delivery-commands/{command_id}/reconcile",
response_model=MailDeliveryCommandResponse,
)
def reconcile_mail_delivery_command(
command_id: str,
payload: MailDeliveryReconcileRequest,
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(get_api_principal),
):
_require_scope(principal, "mail:delivery:reconcile")
try:
result = reconcile_delivery_command(
session,
tenant_id=principal.tenant_id,
command_id=command_id,
decision=payload.decision,
evidence_reference=payload.evidence_reference,
note=payload.note,
user_id=principal.user.id,
)
except MailDeliveryError as exc:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=str(exc),
) from exc
return MailDeliveryCommandResponse(result=result)
@router.post(
"/delivery-commands/{command_id}/resend",
response_model=MailDeliveryCommandResponse,
)
def resend_mail_delivery_command(
command_id: str,
payload: MailDeliveryResendRequest,
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(get_api_principal),
):
_require_scope(principal, "mail:delivery:reconcile")
try:
result = resend_delivery_command(
session,
tenant_id=principal.tenant_id,
command_id=command_id,
idempotency_key=payload.idempotency_key,
user_id=principal.user.id,
)
except MailDeliveryError as exc:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=str(exc),
) from exc
return MailDeliveryCommandResponse(result=result)
def _capability_payload(value: object) -> dict[str, Any]:
if dataclasses.is_dataclass(value):
return dataclasses.asdict(value)