Skip to content

Let a coach join with an email or a mobile in one box - #24

Merged
rvegajr merged 1 commit into
developfrom
feat/email-signin
Oct 2, 2026
Merged

rvegajr merged 1 commit into
developfrom
feat/email-signin

Conversation

@rvegajr

@rvegajr rvegajr commented Oct 2, 2026

Copy link
Copy Markdown
Member

Why

No coach could reliably join. Every path needed a texted code, and text codes reach only +1 numbers. The app enforces that (isDomesticDestination) and so does the relay (SMS_ALLOWED_PREFIXES="+1"). A coach outside the US and Canada, our tester among them, could not get past sign-in. uat and production have 0 coaches and have sent 0 texts.

What changes

  • One box on /signin: "Email or mobile number".
    • An email gets a 6-digit code by email. It works anywhere, the same pattern as BlessBox.
    • A US or Canadian mobile gets the text code as before.
    • A foreign mobile is told "We can't text that number yet. Use your email instead."
    • The code is in the email subject, so it shows in the inbox preview. The body has no link, so SendGrid click tracking can't rewrite anything.
    • The landing-box flow from Say your week on the home page and start taking bookings #20 works through either path.
  • Phone is optional.
    • coach.phone is nullable.
    • coach.email is unique, case-insensitive.
    • A phone join can't claim an email that another coach already has.
  • A coach with no phone still runs the business.
    • The overflow question arrives by email with a link to the schedule.
    • The schedule now shows the question to every coach, with Ask my roster and Not now. This is the web equivalent of replying YES, and the roster is still never asked without the coach.
    • A coach who joined by email adds a mobile there, verified by a text code. A number that belongs to another coach is refused.
  • One guarded email path.
    • sendEmailMessage in src/domain/outbound.ts logs to message_log with channel='email'. SMS ceilings never count emails.
    • Sign-in codes are capped at 5 per address per day.
    • The guard test now fails if anything else sends email.
  • Fixed: src/relay/email.ts had never been called. It sent a SendGrid-shaped body from an unauthenticated noreply@coachatron.com, so the relay would have answered 422. It now uses the relay's native shape. The sender is the relay's shared authenticated address with the name "Coachatron" until COACHATRON_EMAIL_FROM is set.
  • Hardening: session cookies are Secure on https, and codes come from crypto.randomInt.

Migration 0011_email_signin is additive: nullable phone, a unique email index, and a channel column. I checked development before writing it: 1 coach, no duplicate emails. uat and production have 0 coaches.

No thirteenth screen: sign-in is screen 1, and the new cards are POST actions on screen 2.

Tests

test/email-signin.test.ts (8 tests):

  • parsing the one box;
  • the email join end to end, plus a return visit with different capitals;
  • the sender switch;
  • a foreign mobile gets the email hint; the per-address cap; codes are single-use;
  • the duplicate-email guard;
  • landing box → email join → Publish;
  • the email overflow question answered on the schedule, which another coach can't answer;
  • add-a-mobile, including a number another coach already has.

npm test 118/118 on five consecutive full runs. Typecheck, lint (0 errors) and build pass.

After merge (before telling the tester)

Sign in on development and uat with a real inbox, and on uat with a US mobile. Texting in (one Coachatron number) is the next PR.

🤖 Generated with Claude Code

Text codes reach only +1 numbers, so a coach anywhere else could not get
in. An email now gets a 6-digit code by email through the relay, a US or
Canadian mobile still gets a text, and a foreign mobile is pointed to
email. A coach with no phone gets the overflow question by email and
answers it on the schedule screen, and can add a mobile there later.
The relay email client is fixed to the relay's real shape.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@rvegajr
rvegajr merged commit 40caf8a into develop Oct 2, 2026
3 checks passed
rvegajr added a commit that referenced this pull request Oct 2, 2026
…und texts (#27)

Promote develop to staging: one-box email-or-mobile join, signed inbound texts (#24–#26)
rvegajr added a commit that referenced this pull request Oct 2, 2026
… texts (#28)

Promote develop to main: one-box email-or-mobile join, signed inbound texts (#24–#26)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant