One reusable verification primitive, wired into account onboarding. The theme: prove the email before granting access — the same way, everywhere.
Generic OTP email template
A single reusable transactional email now backs every "enter this code" flow instead of each feature inventing its own.
- New WiredMessage template
email_otp("Generic OTP / Verification Code") with variablesname,otp_code,purpose,expires_in,app_name. It surfaces automatically in the Super-Admin Email Templates tab. - New helper
EmailService.send_otp_email(...)delivers the code; the caller still owns generation, hashing, and verification. - Reusable for team-member invites (live), email-change confirmation, withdrawal authorization, workspace transfer, and any future sensitive-action confirmation.
Sub-account onboarding: OTP gate (parity with employees)
Creating a sub-account now requires the candidate email to be confirmed via a one-time code first — the same flow employees already use.
- New
POST /workspaces/{id}/sub-accounts/send-otpand/verify-otp(owner-only). Send returns a clear error if the email provider rejects, rather than silently succeeding. - OTP infrastructure moved into a dedicated store keyed by
(purpose, parent, email)so employee and sub-account flows can't cross-verify. Same 10-minute code / 15-minute verified window, same 5-attempt cap. - The Sub-Accounts panel now uses a 3-step modal (email → code → details) mirroring the employee modal, with resend and change-email controls.
---

