- Enrich BRouter response with per-point 3D coordinates and segment boundary tracking (EnrichedRoute interface) - ColoredRoute component: plain, elevation gradient (green→yellow→red), and surface color modes with invisible wide polyline for click targeting - Click-to-split: click on route polyline inserts waypoint at nearest point, mapped to correct segment via boundary indices - MidpointHandles: draggable CircleMarkers at route segment midpoints for reshaping, hidden below zoom 12, opaque on hover - Color mode toggle (select) synced via Yjs routeData - i18n keys for color mode labels (en + de) - Unit tests for segment boundary tracking (13 tests) - E2E tests for enriched route response and color mode toggle Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
62 lines
2.4 KiB
Markdown
62 lines
2.4 KiB
Markdown
## Context
|
|
|
|
The Journal has passkey auth (primary) and magic link login (fallback). Magic
|
|
links work in dev (auto-redirect) but in production the link is only logged
|
|
to console — users never receive it. No email infrastructure exists.
|
|
|
|
## Goals / Non-Goals
|
|
|
|
**Goals:**
|
|
- Actually deliver magic link emails in production
|
|
- Send a welcome email after registration
|
|
- Provider-agnostic email interface (easy to swap providers)
|
|
- HTML templates with plain-text fallback
|
|
- Easy to add new email types in the future (just add a template + call)
|
|
- Dev mode: still log to console / return devLink (no real emails)
|
|
|
|
**Non-Goals:**
|
|
- Marketing emails or newsletters
|
|
- Email preferences / unsubscribe (only transactional)
|
|
- Inline CSS / complex email layouts (keep it simple text-focused)
|
|
- Queuing or retry logic (provider handles delivery)
|
|
|
|
## Decisions
|
|
|
|
### D1: SMTP via nodemailer
|
|
|
|
Use [nodemailer](https://nodemailer.com) with SMTP — standard, provider-agnostic,
|
|
works with any SMTP server. Single dependency: `nodemailer` npm package.
|
|
Configured via `SMTP_URL` env var (e.g., `smtp://user:pass@mail.example.com:587`).
|
|
|
|
**Alternative considered**: Resend API. Simpler initial setup but creates vendor
|
|
dependency. SMTP is a standard protocol that works with any provider and aligns
|
|
with the self-hostable philosophy.
|
|
|
|
### D2: Email module in apps/journal/app/lib/email.server.ts
|
|
|
|
Not a shared package — only the Journal sends emails. Keep it simple:
|
|
- `sendEmail(to, subject, html, text)` — low-level sender
|
|
- `sendMagicLink(email, link)` — template wrapper
|
|
- `sendWelcome(email, username)` — template wrapper
|
|
|
|
In dev mode, `sendEmail` logs to console instead of calling Resend.
|
|
|
|
### D3: Inline HTML templates
|
|
|
|
Simple functions returning HTML strings. No template engine. Each email type
|
|
is a function: `magicLinkTemplate(link)`, `welcomeTemplate(username)`. Plain
|
|
text generated by stripping tags.
|
|
|
|
### D4: Sender domain: noreply@trails.cool
|
|
|
|
Requires DNS records (DKIM/SPF/DMARC) for deliverability. Add TXT records
|
|
via Terraform or manually in Hetzner DNS.
|
|
|
|
## Risks / Trade-offs
|
|
|
|
- **SMTP server required** → Must provide an SMTP server. Can use any provider
|
|
(Mailgun, Amazon SES, Postfix, etc.) or a simple relay.
|
|
- **DNS records required** → Must add SPF/DKIM/DMARC records for deliverability.
|
|
- **No built-in retry** → nodemailer doesn't queue. If SMTP is down, the send
|
|
fails. Acceptable for current scale — welcome emails are fire-and-forget,
|
|
magic links can be re-requested.
|