--- phase: 04-cli-scaffolding-i18n-and-mail plan: 03 subsystem: mail tags: [postcard, mail, goldmark, go-mail, smtp, wintercms] requires: - phase: 04-cli-scaffolding-i18n-and-mail provides: pact.HasMailTemplates, party.Activate Register-then-Boot order - phase: 01-framework-kernel-foundation provides: backpack.Publish/Lookup, compass mail.* and SUMMER_MAIL__ layering provides: - postcard Mailer with Send(ctx, Message) through memory, log, and SMTP drivers - Winter-shaped template and layout parser (INI, ==, Markdown body) - Goldmark HTML with unsafe HTML disabled and header/address validation - hello plugin mail templates, -en sibling, and hello layout affects: [04-04-tests, plugin-porting, I18N-03] tech-stack: added: [github.com/yuin/goldmark@v1.8.6, github.com/wneessen/go-mail@v0.8.1] patterns: - html/template on Markdown then Goldmark HTML; substituted Markdown is the text part - HasMailTemplates catalogs validate at Boot and publish Mailer before Boot - mail.driver selects memory, log, or smtp with explicit TLS including opt-in NoTLS key-files: created: - postcard/templates.go - postcard/mailer.go - postcard/drivers.go - postcard/assets/default.htm - postcard/mailer_test.go - examples/hello/plugins/base/views/mail/hello.htm - examples/hello/plugins/base/views/mail/hello-en.htm - examples/hello/plugins/base/views/mail/layouts/hello.htm - examples/hello/config/mail.yaml modified: - party/registry.go - party/registry_test.go - examples/hello/plugins/base/plugin.go - examples/hello/hello_test.go - go.mod - go.sum key-decisions: - "Goldmark v1.8.6 is used without html.WithUnsafe; final HTML is rejected if script/iframe, event handlers, or javascript/vbscript/data schemes remain" - "postcard.Mailer is published after Register and before Boot; each HasMailTemplates catalog is validated at that plugin's Boot transition" - "mail.smtp.tls defaults to mandatory STARTTLS; none/notls is opt-in for Mailpit and is never inferred" patterns-established: - "Dotted names vendor.plugin::mail.name map to views/mail/.htm; short layout aliases resolve only within the owning plugin" - "Send takes the full dotted name including any -en suffix and performs no locale lookup" - "Driver errors return to Send with one postcard wrap and no retry; SMTP errors must not include credentials or bodies" requirements-completed: [I18N-03] duration: 6min completed: 2026-09-18 --- # Phase 4 Plan 03: postcard mail Summary **Winter-shaped mail templates and layouts render safe Markdown/HTML and send through memory, log, or go-mail SMTP selected by `mail.*`** ## Performance - **Duration:** 6 min - **Started:** 2026-09-18T11:56:07Z - **Completed:** 2026-09-18T12:02:31Z - **Tasks:** 3 - **Files modified:** 15 ## Accomplishments - `postcard` parses INI headers, `==` separators, and Markdown bodies; `html/template` substitutes vars, Goldmark renders HTML, and the substituted Markdown is the text part - Plugins register dotted template names and short layout aliases; missing files and unknown layouts fail `party: boot ` with the full name - `postcard.Mailer` is app-scoped; hello sends `golem15.hello::mail.hello` and `hello-en` through backpack, using the plugin `hello` layout - `mail.driver` selects memory, log, or smtp (`SUMMER_MAIL__` overrides); SMTP uses explicit TLS, address/CRLF checks, and context-aware go-mail send without retries ## Task Commits Each task was committed atomically: 1. **Task 1: Render and send one registered template through memory** - `b4fd833` (feat) 2. **Task 2: Register plugin layouts and publish the mailer at Boot** - `1e51597` (feat) 3. **Task 3: Add config-selected log and SMTP delivery** - `1b72456` (feat) **Plan metadata:** (this commit) ## Files Created/Modified - `postcard/templates.go` - Winter parser, catalog registration, Goldmark render, HTML safety - `postcard/mailer.go` - Message/Mailer, Activate, BootPlugin, config-selected driver - `postcard/drivers.go` - Driver interface plus memory, log, SMTP, and FailDriver - `postcard/assets/default.htm` - Neutral framework layout - `postcard/mailer_test.go` - render, layout, and driver smokes - `party/registry.go` - Publish mailer before Boot; validate catalogs at Boot - `examples/hello/plugins/base/plugin.go` - HasMailTemplates with hello and hello-en - `examples/hello/plugins/base/views/mail/` - Distinct default-locale and -en templates plus hello layout - `examples/hello/config/mail.yaml` - memory driver and documented smtp/NoTLS shape - `go.mod` / `go.sum` - goldmark v1.8.6 and go-mail v0.8.1 ## Decisions Made - Goldmark stays on v1 (v2 is still a new line); unsafe HTML stays disabled and final HTML is re-checked - Default layout is internal (`layout = default` or omitted); plugins cannot alias `default` - TLS empty/mandatory maps to go-mail `TLSMandatory`; `none`/`notls` is explicit Mailpit opt-in; `starttls` is opportunistic only when named - Mailpit real SMTP receipt stays Plan 04 (D-19); this plan uses an in-process FailDriver and a closed-port SMTP error ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 2 - Missing Critical] Three-section layout parser shipped with Task 1** - **Found during:** Task 1 (embedded default layout) - **Issue:** The neutral default layout is header + text wrapper + HTML wrapper, so memory-driver HTML/text wrapping could not wait for Task 2 - **Fix:** Parse two-`==` layouts in `templates.go` in Task 1; Task 2 still added plugin aliases, party Boot validation, and the hello layout - **Files modified:** `postcard/templates.go`, `postcard/assets/default.htm` - **Verification:** `TestMailRenderSmoke` asserts `content-body` from the default layout - **Committed in:** `b4fd833` (Task 1 commit) --- **Total deviations:** 1 auto-fixed (1 missing critical) **Impact on plan:** No behavior divergence from D-06–D-09 or D-18–D-21. Layout parsing was required to honor the Task 1 default-layout artifact. ## Issues Encountered None ## User Setup Required None - no external service configuration required. Production SMTP credentials stay in `mail.*` / `SUMMER_MAIL__`; Mailpit is Plan 04. ## Next Phase Readiness Ready for 04-04 (unit tests and Mailpit). Memory render, layout wrappers, named boot errors, log/SMTP driver contracts, and a failing-driver seam are in place. Do not treat `-short` as SMTP receipt. ## Verification - `go test ./postcard -run 'TestMailRenderSmoke|TestMailLayoutSmoke|TestMailDriverSmoke' -short -count=1` — PASS - `go -C examples/hello test ./...` — PASS - `go vet ./... && go test ./...` — PASS ## Self-Check: PASSED --- *Phase: 04-cli-scaffolding-i18n-and-mail* *Completed: 2026-09-18*