Covers: notify_parent() on AdminNotifier, multipart HTML email with inline Swiss QR-bill (qrbill library), CHF 80 fixed payment data, German default language, best-effort error handling. https://claude.ai/code/session_01LjjK7RjKVnC8bETtccfgna
5.8 KiB
Context
When a registration is completed, AdminNotifier.notify_admin() sends an email to the relevant playgroup leaders. The parent receives nothing — no acknowledgement, no summary, no payment details.
The existing AdminNotifier in src/notifications/notifier.py handles both new-registration and update notifications to admins. It uses MIMEMultipart("alternative") and sends via the configured SMTP server. All the SMTP plumbing already works.
This change adds a parent-facing confirmation email triggered at the same point as the admin notification.
Goals / Non-Goals
Goals:
- Parent receives an HTML confirmation email immediately after registration is stored
- Email contains full registration summary (all 13 fields)
- Email contains payment instructions for the CHF 80 registration fee
- Email contains a Swiss QR-bill (QR code image) embedded inline so the parent can pay via banking app or print
Non-Goals:
- Translating the payment section into English (Swiss QR-bill and bank instructions are always in German regardless of language)
- Sending a reminder if the parent hasn't paid (payment tracking is out of scope)
- Generating a full PDF invoice (QR code embedded in HTML email is sufficient)
- Sibling discount handling in the QR-bill amount (CHF 80 is always fixed for the registration fee)
Decisions
1. Extend AdminNotifier vs. New Class
Decision: Add notify_parent() to the existing AdminNotifier class (renamed conceptually; kept in the same file for now).
Rationale: The SMTP plumbing (_send, _smtp_host, credentials) is already there. A notify_parent method reuses all of it. Splitting into a separate class would require duplicating constructor parameters and SMTP setup for no structural benefit at this stage.
Trade-off: AdminNotifier becomes slightly misnamed. Acceptable — the class handles all outbound notification emails. Rename in a future refactor if needed.
2. HTML Email Format
Decision: Send multipart/alternative with both plain-text and HTML parts. The HTML part is the primary view; plain-text is fallback.
Rationale: Matches the existing _send method's MIMEMultipart("alternative") pattern. HTML is needed to embed the QR-bill image inline.
QR image embedding: Use multipart/related wrapping the HTML part, with the QR PNG attached as Content-ID (cid:qrbill). This is the standard approach for inline images that don't appear as attachments.
Structure:
multipart/mixed
└── multipart/alternative
├── text/plain (fallback)
└── multipart/related
├── text/html (references cid:qrbill)
└── image/png (Content-ID: qrbill, inline)
3. Swiss QR-Bill Generation
Decision: Use the qrbill Python library to generate the QR code image.
Rationale: qrbill implements the Swiss QR-bill standard (SIX Group spec) directly. It accepts IBAN, payee address, amount, and currency, and outputs an SVG or PNG. No external services required.
Fixed payment data (hardcoded in the notifier, not in config — this is stable bank data):
- IBAN:
CH14 0900 0000 4930 8018 8 - Payee: Familienverein Fällanden Spielgruppen, c/o Markus Graf, Huebwisstrase 5, 8117 Fällanden
- Amount: CHF 80.00
- Currency: CHF
- Reference type: NON (no structured reference)
Output: PNG bytes, embedded as inline image in HTML email.
Dependency: Add qrbill to pyproject.toml dependencies.
4. Language
Decision: Confirmation email body in German by default. The payment block is always German.
Rationale: The registration schema does not store the parent's detected language. German is the default for all admin-facing and parent-facing content. Adding language detection to the registration model is out of scope for this change.
Future improvement: Store language in metadata during conversation and pass it to the notifier so the body can be in English when detected.
5. Trigger Point
Decision: Call notify_parent() immediately after notify_admin() at the same trigger site — wherever notify_admin is currently called (in src/agent/core.py or equivalent).
Rationale: The parent notification is a direct consequence of the same event (registration completed). No separate trigger or queue needed.
Error isolation: If the parent email fails, log the error but do not fail the registration or block the admin notification. Both notifications are best-effort.
Risks / Trade-offs
qrbill library maturity: Actively maintained, used in production Swiss applications. Risk is low. If the library is unavailable, the QR code can be omitted and the plain IBAN text still enables payment.
Inline image rendering: Some email clients block inline images by default (Outlook, some mobile clients). The plain-text fallback and the raw IBAN text in the HTML body ensure the payment info is always readable even if the QR image is blocked.
SMTP failure for parent email: Parent notification is non-critical (the registration is already stored). Failure is logged as a warning, not an exception.
Language mismatch: A parent who spoke English during chat will receive a German confirmation. Acceptable for MVP given the Swiss German context and the fact that the payment instructions must be in German regardless.
Open Questions
- Should the confirmation email also include the monthly subscription fee (in addition to the CHF 80 registration fee), or only the registration fee QR-bill? The CHF 80 one-time fee is the immediate action required; the monthly fee is recurring and not yet payable. Proposed answer: include both as informational text, but the QR-bill is for CHF 80 only.
- Should the parent's email be CC'd on the admin notification, or kept as a separate send? Proposed answer: separate send — keeps admin and parent content distinct.