Skip to content

feat(otp-worker): notify company@meridiona.com on sign-up or email change - #934

Merged
Akarsh-Hegde merged 4 commits into
pre-mainfrom
feat/otp-account-event-notify
Sep 5, 2026
Merged

feat(otp-worker): notify company@meridiona.com on sign-up or email change#934
Akarsh-Hegde merged 4 commits into
pre-mainfrom
feat/otp-account-event-notify

Conversation

@Akarsh-Hegde

Copy link
Copy Markdown
Member

Summary

  • Whenever /otp/verify succeeds, the Worker now fires a fire-and-forget SES notification to NOTIFY_EMAIL (company@meridiona.com) saying either "new sign-up: <email>" or "email changed: <old> -> <new>".
  • The client (confirm_account_otp) sends its locally-known previous email as an optional previousEmail field in the verify request — purely informational, never a security input.

Why this design

Reuses the existing /otp/verify flow rather than adding a new endpoint or putting SES credentials on the client. The Worker already holds the only SES credentials (server-side, least-privilege IAM), and already has this exact fire-and-forget pattern for the rate-limit alert (ctx.waitUntil, never awaited inline, so a failed notification never affects the verify response). Inherits the existing send/verify rate limits, so it can't be used to spam the notify inbox independently of those.

resolveAccountEvent (pure, unit-tested in ses.test.ts) decides the event kind:

  • no previous email -> sign-up
  • previous email differs -> email changed (old -> new)
  • previous email is identical to the new one -> no-op, nothing sent (re-entering the same address via "Change email")

This app has no session/re-login concept — every /otp/verify call is either a genuine one-time capture or a deliberate "Change email" action, so there's no repeat-noise case to dedup against (unlike ALERT_EMAIL's once-per-day flag).

Where to see the full picture

Per-event notifications land in an inbox — not a browsable directory. The existing PostHog integration (tray/src-tauri/src/analytics/mod.rs, distinct_id = the signed-in email) remains the source of truth for the full list/count of captured emails; this is a complementary real-time heads-up, not a replacement.

Changes

  • infra/otp-worker/src/ses.ts: AccountEvent type, resolveAccountEvent, buildAccountEventBody/Html, sendAccountEventEmail.
  • infra/otp-worker/src/index.ts: handleVerify takes ctx: ExecutionContext, fires the notification on the verified outcome only.
  • infra/otp-worker/wrangler.jsonc / worker-configuration.d.ts: new NOTIFY_EMAIL var (both envs).
  • tray/src-tauri/src/commands/otp.rs: confirm_account_otp reads commands::account::read_account_email() and includes it as previousEmail; new build_verify_body pure helper.
  • infra/otp-worker/README.md: new "Account-event notification" section.

Test plan

  • npx vitest run in infra/otp-worker/ — 107/107 passing (8 new)
  • npx tsc --noEmit clean
  • cargo test --workspace — 446/446 passing (2 new)
  • cargo clippy --workspace --all-targets -- -D warnings clean
  • Deploy the Worker to staging + production (wrangler deploy) — code changes here have no effect until deployed
  • Manual: sign up with a fresh email on staging, confirm company@meridiona.com receives the sign-up notification; use "Change email" and confirm the changed-email notification

…ange

On a successful /otp/verify, the Worker now fires a fire-and-forget SES
notification to NOTIFY_EMAIL telling the company an install signed up or
changed its verified email (old -> new). The client sends its locally-known
previous email as an optional, purely-informational previousEmail field;
the Worker never treats it as a security input. Rides on ctx.waitUntil
exactly like the existing rate-limit alert, so a failed notification send
never affects the verify response itself.
@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Team

Run ID: e0561c3d-18b7-4afd-92a7-e03377bc22b1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The marketing site has sent this exact notification since June via Resend
(Meridian Sign-ins <notify@meridiona.com> -> adithya@meridiona.com, subject
"New sign-up: <email>"). Routing the desktop app's copy through the same
provider keeps web and desktop sign-ups in one inbox with one sender
identity instead of splitting them across two providers by accident of
which codebase emitted them.

This does not reverse the SES-over-Resend decision: that was specifically
about OTP code delivery, where Resend's 100/day free tier cannot cover
hundreds of user-facing sends. An internal notification to one address is a
couple of dozen a day at most. OTP codes and the rate-limit alert stay on
SES, and ses.ts goes back to being purely SES.

Body and subject now mirror the website's format (plain text, no HTML
part), so both sources thread together. The Rust client is unchanged - the
previousEmail plumbing is transport-agnostic.
A test sign-up on staging is not a real one. Delivering "New sign-up:
<test address>" to the team inbox makes the production notification less
trustworthy every time someone exercises staging, so staging now notifies
akarsh@ and production remains the only channel reaching adithya@.
Reverts the staging-only notify split and moves the rate-limit alert off
akarsh@ as well, so both emails reach the same team inbox on both channels.
@Akarsh-Hegde
Akarsh-Hegde merged commit 38ea0d0 into pre-main Sep 5, 2026
16 checks passed
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