Email, i18n & tours
Email adapter (--features email)
A provider-agnostic sender with a dev inbox. Every send is logged to an emails table and shown on the Emails page; with delivery configured it also sends for real. Call it from any mutation:
await ctx.scheduler.runAfter(0, internal.email.send, { to, subject, html });
Providers are tried in priority order and the choice is recorded on every logged send: Resend (RESEND_API_KEY + EMAIL_FROM), then SMTP, then a mock that only logs. Convex sends from a "use node" action (nodemailer for SMTP); Supabase sends from the email Edge Function (denomailer for SMTP).
# Supabase: deploy the function, then set a provider
supabase functions deploy email
supabase secrets set RESEND_API_KEY=re_… EMAIL_FROM=you@domain
# or SMTP:
supabase secrets set SMTP_HOST=smtp.example.com SMTP_PORT=587 SMTP_USER=… SMTP_PASS=…
# Convex: same idea via env
npx convex env set RESEND_API_KEY re_… --prod # or SMTP_HOST …
i18n scaffold (--lang en,de)
Generates src/lib/i18n.tsx — a dependency-free message catalog for YOUR strings. t(key) translates in the current language (fallback: default language, then the key itself), useLang() re-renders on change, and LangSwitcher renders only when 2+ languages are configured. The shell chrome stays English.
Guided tour
Demo pages mount a Tour: selector-targeted steps ([data-tour] on the view switcher, seed button, new button and CSV controls), a positioned card with back/next/skip, and once-per-storageKey persistence in localStorage. Reuse the Tour component for your own pages.