Files
ping-mee-self-service-platform/_bmad-output/initiative-ping-mee-portal/epic-platform-and-sign-in/story-scheduler-and-mail-outbox-plan.md
T
ebbe.bassandClaude Sonnet 5.5 20510a73e5 feat: add scheduler and mail outbox
SMTP adapter with TLS and a fake, outbox with retries, backoff, rate limit and restart safety, scheduler tick with job-run records started by gunicorn, and a send_test_mail command.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3t4WL7bd9bdHHNjEsxzqC
2026-10-18 09:18:42 +02:00

2.2 KiB

title, type, ticket, created, status, baseline_revision, route, route_source, risk, review, review_source, context
title type ticket created status baseline_revision route route_source risk review review_source context
Scheduler and mail outbox feature 7 2026-10-18 built 0dc079a full auto medium quick pinned
{project-root}/_bmad-output/architecture/architecture-ping-mee-self-service-platform-2026-10-15/ARCHITECTURE-SPINE.md

Intent

Problem: Later epics must send e-mail (password change notices, confirmation links, announcements) without blocking requests, losing mail on restart or exceeding the provider's limits, and need a place to run repeating jobs.

Approach: adapters/mail is the only code that talks SMTP (TLS always, certificate verified), with an in-memory fake. mailer owns the outbox; other code calls enqueue(template, recipient, context) in its own transaction. scheduler owns JobRun and one 30-second tick that runs idempotent jobs, started in the gunicorn worker by docker/gunicorn.conf.py (AD-11, AD-12, AD-19).

Decisions (made by the build; the spine left them open)

  • Retries: 5 attempts, waits of 1, 5, 15 and 60 minutes. Rate limit: 20 mails a minute. Named constants until the settings registry exists; the IONOS limit is still unknown (spec open question).
  • Mails are rendered when queued, in German formal (MAIL_LANGUAGE), as plain text only. HTML parts wait for the sanitizer (AD-16).
  • The body is removed from the database once the mail is sent or has failed, because it may hold a one-time link.
  • Restart safety: a row is claimed (queued to sending) before sending. A row still sending after 10 minutes is marked failed with an unknown outcome and is not sent again: a lost notice is better than a duplicate.
  • A refused login is treated as temporary, so a corrected password works on the next retry. A dead server stops the run after three consecutive outages.
  • manage.py send_test_mail <address> queues and sends a test mail at once, for the done check.
  • Only the mail.flush job exists; later entries add theirs to JOBS.

Implementation Notes

  • pytest 319 passed, 6 skipped; ruff check clean.
  • Not verified: a real SMTP server and the scheduler thread inside the container (needs the Linux host).