1. Účel a scope
TalkIDE posílá z BE následující transactional emaily uživatelům:
| Trigger | Použité šablona | UC |
|---|---|---|
| Forgot password reset link | password-reset | UC-01005 |
| Waitlist join confirmation | waitlist-confirmation | UC-11001 |
| Waitlist invite | waitlist-invite | UC-11004 (TBC pojmenování) |
| Spending limit warning (80% / 100%) (stub) | TBD | UC-10005 |
| Hosting dunning (PAST_DUE notice, suspended notice) (plánováno) | TBD | UC-10015 (DP-3) |
| GDPR export ready download link | TBD | UC-01010 deletion / export flow |
Out-of-scope: marketing emails (newsletter, product updates) — řeší se mimo platformu (Mailchimp / dedicated marketing tool). Tento dokument se zabývá výhradně transactional e-maily generovanými uživatelovou akcí.
2. Architektura
flowchart LR
UC[Use case<br/>ForgotPassword / Waitlist / ...]
SVC[EmailService<br/>render template + audit]
SENDER{EmailSender<br/>interface}
ML[MailgunEmailSender<br/>profile: production]
NOOP[NoopEmailSender<br/>profile: !production]
LOG[(EmailLogRepository<br/>email_log table)]
MG[Mailgun HTTP API<br/>api.mailgun.net]
UC --> SVC
SVC --> SENDER
SVC -- audit --> LOG
SENDER -.profile binding.-> ML
SENDER -.profile binding.-> NOOP
ML -- HTTPS POST multipart --> MG
2.1 EmailSender interface
Provider abstraction. Dvě beany podmíněné Spring profilem (NIKDY @Profile("prod") — viz CLAUDE.md
incident #194):
| Bean | Profile | Chování |
|---|---|---|
MailgunEmailSender | @Profile("production") | Skutečné HTTP POST na Mailgun |
NoopEmailSender | @Profile("!production") | Loguje payload do BE loggeru, nic neodesílá |
Lokální dev, integration testy a Testcontainers smoke všechny běží v !production → žádné
reálné emaily se neposílají.
2.2 Audit log — email_log tabulka
Každý pokus o odeslání (úspěšný i selhaný) zapisuje jeden řádek do email_log. Účel:
- Debugging doručitelnosti (kterému uživateli kdy co odešlo).
- Postmortem support („proč mi nepřišel reset link” → grep podle recipient).
- Budoucí billing usage tracking pokud bude email per-tenant fakturován.
Schéma (viz EmailLogEntity + migrace 0028-create-email-log.xml):
| Sloupec | Typ | Note |
|---|---|---|
id | bigint identity | PK |
type | varchar(50) | Enum EmailType.name() (např. PASSWORD_RESET, WAITLIST_CONFIRMATION) |
recipient | varchar(255) | Cílový email |
subject | varchar(500) | Subject po renderování šablony |
provider_message_id | varchar(255) NULL | Mailgun message-id (z API response); NULL při SENT na Noop, NULL při FAILED |
status | varchar(20) | EmailStatus.name(): SENT / FAILED |
error | text NULL | Mailgun error string nebo exception message při FAILED |
created_at | timestamp | Pokus o odeslání |
Status je prostý string (žádný DB enum, žádný check constraint — ADR-025 § 6). To umožní
přidat nové stavy bez DB migrace (např. BOUNCED po implementaci webhook handleru).
2.3 Šablony
Šablony jsou Mustache (.mustache) v talkide-be/src/main/resources/templates/email/.
Renderování v EmailService.render(template, variables) — totéž jako scaffold templates
v ProjectScaffoldService. Subject i body se renderují ze stejné šablony, oddělené na
základě konvence (první řádek = subject, zbytek = body) nebo dvou souborů per type.
Variables jsou typed Kotlin data class per EmailType — žádný stringly-typed map. Únik
nedefinované proměnné je compile-time error.
3. Konfigurace
3.1 Spring properties
# application.yaml (default = nepoužitelné v prod, jen pro startup)
talkide:
email:
provider: mailgun
from-address: noreply@mail.talkide.app
from-name: TalkIDE
mailgun:
base-url: https://api.mailgun.net # US region; EU: https://api.eu.mailgun.net
domain: mail.talkide.app
# api-key injektován z ENV / Secret, NIKDY v repo
# application-production.yaml
talkide:
email:
mailgun:
api-key: ${MAILGUN_API_KEY}
3.2 K8s Secret
mailgun-secret v ns talkide-prod (přes External Secrets Operator nebo přímý kubectl create secret — v alfa zatím manual). BE pod mountuje jako env var MAILGUN_API_KEY.
3.3 Sending doména DNS
DNS záznamy pro mail.talkide.app musí být Mailgunem ověřené před prvním prod odesláním
(viz ADR-025 § 3 — SPF TXT, DKIM TXT, tracking CNAME).
Postup pro setup je v talkide-infra runbooks.
4. Bezpečnost a observability
4.1 PII
Recipient email a subject jsou PII. email_log je tedy součástí GDPR data subject access
request scope. Při account deletion (UC-01010 deletion flow, viz CLAUDE.md be#138) email_log
záznamy pro daného usera se anonymizují (recipient → deleted-<userId>@deleted.local,
provider_message_id ponechán pro auditní trail).
4.2 Rate limiting
Per-recipient rate limit pro password reset (UC-01005) je řešen v AuthRateLimitAttempt
tabulce a AuthRateLimitFilter — ne v emailové vrstvě. EmailService žádný vlastní rate
limit nemá; pokud volající business logika udělá burst (např. dunning pipeline na 1000
tenantů), Mailgun absorpční kapacita je dost (1k+ msg/s pro tier účet).
4.3 Metrics & alerts
email_logSUM(status=‘FAILED’) / HOUR — alert nad práh (TBD pro DevOps).- Mailgun dashboard má vlastní delivery metrics (delivered, bounced, opened).
- BE logger zachytává každý FAILED s exception trace.
4.4 Idempotence
Aktuálně EmailService neimplementuje idempotence — pokud business logika zavolá send()
dvakrát, uživatel dostane dva e-maily. Akceptovatelné v aktuálních case (každý trigger je
přirozeně sólo akce). Pokud by nový case potřeboval idempotenci (např. retry safe scheduler),
přidá se idempotency_key sloupec do email_log + UNIQUE constraint, EmailService udělá
SELECT-then-INSERT s explicit check.
5. Známé limitace
| Limitace | Stav |
|---|---|
| Webhook handlery pro delivered/bounced/failed | Out-of-scope MVP. Status v email_log zůstává na SENT i pokud Mailgun následně reportoval bounce. |
| Multi-locale templates (en/cs) | TBD — aktuálně jen en šablony. User locale (UserEntity.locale) je k dispozici v render contextu. |
| HTML + plain-text alternativa | Aktuálně jen plain-text. HTML šablony budou až s designově sjednocenými emaily (post-alpha). |
| Per-tenant sender domain | Out-of-scope MVP. Všechny TalkIDE-platform emaily chodí z noreply@mail.talkide.app. |
| BYO SMTP / per-tenant provider | Enterprise feature, out-of-scope. |
6. Vztah k jiným specifikacím a UC
| Reference | Vztah |
|---|---|
| ADR-025 | Architektonické rozhodnutí, zdroj pravdy |
| UC-01005 | První produkční konzument — password reset link |
| UC-11001 | Druhý produkční konzument — waitlist confirmation |
| model/README.md | EmailLog entita |
| limitations.md | Web-wide známé limitace |
Thanks for the feedback.