Skip to content

Add WhatsApp verification channel with per-country routing - #2618

Draft
kaladivo wants to merge 1 commit into
mainfrom
whatsapp-verification-channel
Draft

Add WhatsApp verification channel with per-country routing#2618
kaladivo wants to merge 1 commit into
mainfrom
whatsapp-verification-channel

Conversation

@kaladivo

@kaladivo kaladivo commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

What

Login verification codes can now be delivered via WhatsApp (through Prelude's preferred_channel), targeted per country, with SMS remaining the default everywhere else.

How it works

  • Contract (packages/rest-api): InitPhoneVerificationRequest gains optional channel: 'auto' | 'sms' | 'whatsapp'; the response gains sentVia. Old app builds send no channel and keep today's SMS-only behavior — no version gate needed.
  • Server (apps/user-service): new WHATSAPP_PREFERRED_PREFIXES env var (comma-separated country prefixes, e.g. 39 for Italy). channel=auto resolves to WhatsApp for listed prefixes, SMS otherwise; an explicit channel (user tapped "send via …") is honored as-is. Prelude routes WhatsApp→SMS automatically when the number has no WhatsApp account, so no per-user detection is needed.
  • Mobile: first attempt sends auto. The code screen shows channel-aware copy (WhatsApp copy hedges about the SMS fallback), and after 20 s a link appears to re-send via the other channel. Resend keeps the current channel.
  • Metrics: USER_LOGGED_IN now includes the delivery channel, so WhatsApp vs SMS conversion can be compared per country to tune the prefix list.
  • Cleanup: removed the dead VERIFICATION_PROVIDER config (validated but never read).

Rollout notes

  • WhatsApp delivery needs one-time ops setup before enabling: Vexl WABA connected in the Prelude dashboard (Meta business verification + sender number + WhatsApp billing). Until then, keep WHATSAPP_PREFERRED_PREFIXES empty/unset — everything behaves exactly as today.
  • New localization keys were added to base.json + en-base.json (en is type-required to be complete) — they need to be uploaded to Crowdin before the next crowdin download.
  • Redis verification states gained an optional channel field; old in-flight states decode fine.

Tests

  • apps/user-service jest suite: 41/41 passing, including new channel-resolution cases (legacy/auto/explicit × prefix in/out of list).
  • pnpm turbo:typecheck, turbo:format, turbo:lint all clean.

Follow-ups (out of scope)

  • Silent Network Auth as a future channel value (needs Prelude client SDK + dispatch_id plumbing).
  • Privacy: raw phone number is stored as sid in Redis and logged in OTel span attributes in prelude.ts (pre-existing) — worth a separate fix.
  • Delete dead twilioErrors.ts (2.5 MB, zero importers).

Summary by CodeRabbit

  • New Features

    • Added WhatsApp as an available verification-code delivery channel.
    • Automatically selects WhatsApp or SMS based on phone country prefix preferences.
    • Users can switch the resend channel after a short delay.
    • Verification screens now show channel-specific instructions and resend options.
    • Login responses indicate whether the code was sent via SMS or WhatsApp.
  • Documentation

    • Added configuration guidance for preferred WhatsApp country prefixes.

Login codes can now be delivered via WhatsApp through Prelude's
preferred_channel option. The server picks the channel: clients send
channel=auto and prefixes listed in WHATSAPP_PREFERRED_PREFIXES get
WhatsApp first (Prelude falls back to sms automatically when the number
has no WhatsApp). Everyone else keeps sms, with a switch-channel link
shown on the code screen after 20s. Old app builds send no channel field
and keep the current sms-only behavior.

Also removes the dead VERIFICATION_PROVIDER config and reports the
delivery channel in the USER_LOGGED_IN metric.
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Verification initialization now supports SMS and WhatsApp channel selection. The backend resolves and propagates channels, persists them in verification state, reports them in metrics, and returns sentVia. The mobile login flow supports automatic selection, channel-specific resend actions, and localized WhatsApp messaging.

Changes

Verification channel delivery

Layer / File(s) Summary
Channel contracts and routing configuration
packages/rest-api/..., apps/user-service/src/configs.ts, example.env.local, tooling/dev/*, apps/user-service/.env.test, dev.config.ts
Verification requests and responses now carry channel fields, and WhatsApp country-prefix configuration is parsed and exposed to user-service environments.
Channel resolution and verification creation
apps/user-service/src/utils/*, apps/user-service/src/routes/login/handlers/initVerificationHandler.ts, apps/user-service/src/routes/eraseUser/..., apps/user-service/src/__tests__/login/*, apps/user-service/src/__tests__/eraseUser/*
Requested channels resolve to SMS or WhatsApp, provider creation receives the resolved channel, responses include sentVia, and channel-selection behavior is tested.
Channel persistence and login metrics
apps/user-service/src/routes/login/domain.ts, apps/user-service/src/routes/login/handlers/*, apps/user-service/src/metrics.ts
Verification state stores an optional channel, and successful login metrics include the delivery channel.
Mobile channel selection and resend UI
apps/mobile/src/components/LoginFlow/*, packages/localization/*
The mobile flow submits automatic channel selection, supports delayed alternate-channel resend, and displays channel-specific localized messaging.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PhoneNumberScreen
  participant VerificationCodeScreen
  participant initPhoneVerificationAtom
  participant initVerificationHandler
  participant PreludeService
  PhoneNumberScreen->>initPhoneVerificationAtom: submit phone number with auto channel
  initPhoneVerificationAtom->>initVerificationHandler: initialize verification
  initVerificationHandler->>PreludeService: create verification with resolved channel
  PreludeService-->>initVerificationHandler: verification SID
  initVerificationHandler-->>VerificationCodeScreen: sentVia and verification ID
  VerificationCodeScreen->>initPhoneVerificationAtom: resend via alternate channel
Loading

Suggested reviewers: samtremko

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding WhatsApp verification and country-based routing.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch whatsapp-verification-channel

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.

@kaladivo
kaladivo marked this pull request as draft July 25, 2026 07:58
@greptile-apps

greptile-apps Bot commented Jul 25, 2026

Copy link
Copy Markdown

Greptile Summary

Adds WhatsApp as a preferred phone-verification channel with country-based routing.

  • Extends the shared login contract with requested and reported verification channels.
  • Routes automatic verification through WhatsApp for configured country prefixes while preserving SMS for legacy clients and account erasure.
  • Adds mobile channel-aware copy, alternate-channel resend controls, and delivery-channel login metrics.
  • Adds verification-state compatibility, configuration, localization, development setup, and server tests.

Confidence Score: 3/5

The PR should not merge until fallback deliveries stop being presented and measured as though Prelude used the preferred WhatsApp channel.

Prelude may deliver an SMS after receiving a WhatsApp preference, but the service persists and returns the preference as sentVia, causing incorrect mobile guidance and conversion attribution.

Files Needing Attention: apps/user-service/src/routes/login/handlers/initVerificationHandler.ts, apps/user-service/src/utils/prelude.ts, apps/mobile/src/components/LoginFlow/components/VerificationCodeScreen.tsx, apps/user-service/src/metrics.ts

Important Files Changed

Filename Overview
apps/user-service/src/utils/prelude.ts Passes the preferred channel to Prelude and permits provider fallback, but does not produce an effective delivery channel for downstream consumers.
apps/user-service/src/routes/login/handlers/initVerificationHandler.ts Resolves channel routing and creates verification state, but reports the preference as sentVia despite provider fallback.
apps/mobile/src/components/LoginFlow/components/VerificationCodeScreen.tsx Adds channel-specific messaging and resend controls whose correctness depends on sentVia representing actual delivery.
apps/user-service/src/routes/login/domain.ts Adds backward-compatible optional channel fields to transient verification state.
packages/rest-api/src/services/user/contracts.ts Adds optional requested-channel and sentVia fields while retaining compatibility with older clients.
apps/user-service/src/metrics.ts Adds channel attribution to successful-login metrics, inheriting the inaccurate preferred-channel value in fallback cases.
packages/localization/base.json Adds source strings for WhatsApp delivery and alternate-channel resend UI.
packages/localization/en-base.json Adds the corresponding complete English translations.

Sequence Diagram

sequenceDiagram
  participant M as Mobile
  participant U as User service
  participant P as Prelude
  participant R as Redis
  participant X as Metrics
  M->>U: initVerification(phone, auto)
  U->>U: Resolve preferred channel by country
  U->>P: create(preferred_channel)
  P-->>M: Deliver via preferred channel or fallback
  U->>R: Store verification and preferred channel
  U-->>M: verificationId, expirationAt, sentVia
  M->>U: verifyCode
  U->>R: Carry channel into challenge state
  M->>U: verifyChallenge
  U->>X: USER_LOGGED_IN(country, channel)
Loading

Reviews (1): Last reviewed commit: "Add WhatsApp verification channel with p..." | Re-trigger Greptile

return new InitPhoneVerificationResponse({
expirationAt: fromMilliseconds(expirationAt),
verificationId: verificationState.id,
sentVia: channel,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Fallback channel is misreported

When Prelude falls back from a WhatsApp preference to SMS, this returns and persists the preferred channel as sentVia, causing the mobile app to show WhatsApp-specific guidance and successful SMS deliveries to be counted as WhatsApp conversions.

Knowledge Base Used:

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 945952d9fa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

timestamp: new Date(),
name: USER_LOGGED_IN,
attributes: {countryPrefix},
attributes: {countryPrefix, channel: channel ?? 'sms'},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Record the actual delivery channel in login metrics

When a WhatsApp-preferred dispatch falls back to SMS—as prelude.ts lines 68–70 explicitly expect—the stored channel remains whatsapp, so this line records the eventual login as WhatsApp-delivered. Consequently, USER_LOGGED_IN aggregates cannot accurately compare WhatsApp and SMS conversion and will misclassify every fallback; record the provider's actual selected channel if available, or label and analyze this value as the preferred-channel cohort instead.

Useful? React with 👍 / 👎.

@github-actions

Copy link
Copy Markdown

📱 Preview on the staging app

Open Vexl (stage) → Account → Scan QR code and scan this:

PR preview QR code

Preview link: stagingapp.vexl.it://link/?type=load-pr-preview&channel=pr-2618&version=1.44.2

Channel pr-2618
Commit 945952d
Runtime 77f44836b5e2405e4714fa5c1d429edbe924da56, fb1946380846f99f559e5df9b72bb73724682d57
Dashboard update group

The preview only loads into staging builds with a matching runtime — if this PR changes native code, ship a new staging build first. To go back to the staging channel: debug screen → "Clear PR preview".

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/user-service/src/routes/login/domain.ts (1)

25-44: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

staticCodeVerification never persists the resolved channel, silently skewing USER_LOGGED_IN metrics.

Only the twilioSmsVerification variant gained channel: Schema.optional(VerificationChannel); staticCodeVerification did not. Since initVerificationHandler's dummy-code branches compute and return sentVia: channel but can't persist it on this variant, verifyCodeHandler's loginData.type === 'twilioSmsVerification' ? loginData.channel : undefined always yields undefined for dummy-code logins, and reportUserLoggedIn in metrics.ts then falls back to 'sms' regardless of the actually-resolved channel. Any dummy/test number under a WhatsApp-preferred prefix will be miscounted in metrics.

🔧 Proposed fix to add and forward the channel field
   Schema.Struct({
     id: PhoneNumberVerificationId,
     type: Schema.Literal('staticCodeVerification'),
     code: Schema.String,
     phoneNumber: HashedPhoneNumber,
     countryPrefix: CountryPrefix,
     expiresAt: UnixMilliseconds,
+    // optional - states written before channel support was added lack it
+    channel: Schema.optional(VerificationChannel),
   })

Then forward channel in both makeStaticCodeVerificationState calls in initVerificationHandler.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/user-service/src/routes/login/domain.ts` around lines 25 - 44, Add an
optional VerificationChannel field to the staticCodeVerification variant of
PhoneVerificationState, then update both makeStaticCodeVerificationState calls
in initVerificationHandler to persist the resolved channel alongside the
existing dummy-code state fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/user-service/src/utils/prelude.ts`:
- Around line 14-19: Update the PreludeOperations.createVerification contract
and initVerificationHandler flow to return the effective delivery channel
alongside the verification SID. Validate the returned channel with
Schema.decodeUnknown, then use that validated value for persisted channel state
and sentVia instead of the requested channel, ensuring WhatsApp fallback to SMS
is propagated correctly.

In `@packages/localization/en-base.json`:
- Around line 304-307: Remove the manually added verification-code keys from
en-base.json and add them through the repository’s localization workflow, using
base.json as the canonical source and regenerating/uploading the locale seed so
sibling *-base.json files remain workflow-generated.

---

Outside diff comments:
In `@apps/user-service/src/routes/login/domain.ts`:
- Around line 25-44: Add an optional VerificationChannel field to the
staticCodeVerification variant of PhoneVerificationState, then update both
makeStaticCodeVerificationState calls in initVerificationHandler to persist the
resolved channel alongside the existing dummy-code state fields.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 55f42bc1-9aea-4b49-849c-d3855f66f3b9

📥 Commits

Reviewing files that changed from the base of the PR and between c834592 and 945952d.

📒 Files selected for processing (22)
  • apps/mobile/src/components/LoginFlow/api/initPhoneVerificationAtom.ts
  • apps/mobile/src/components/LoginFlow/components/PhoneNumberScreen.tsx
  • apps/mobile/src/components/LoginFlow/components/VerificationCodeScreen.tsx
  • apps/user-service/.env.test
  • apps/user-service/src/__tests__/eraseUser/initEraseUser.test.ts
  • apps/user-service/src/__tests__/login/initVerification.test.ts
  • apps/user-service/src/configs.ts
  • apps/user-service/src/metrics.ts
  • apps/user-service/src/routes/eraseUser/handlers/initEraseUser.ts
  • apps/user-service/src/routes/login/domain.ts
  • apps/user-service/src/routes/login/handlers/initVerificationHandler.ts
  • apps/user-service/src/routes/login/handlers/verifyChallengeHandler.ts
  • apps/user-service/src/routes/login/handlers/verifyCodeHandler.ts
  • apps/user-service/src/utils/prelude.ts
  • apps/user-service/src/utils/smsVerificationUtils.ts
  • dev.config.ts
  • example.env.local
  • packages/localization/base.json
  • packages/localization/en-base.json
  • packages/rest-api/src/services/user/contracts.ts
  • tooling/dev/secrets.ts
  • tooling/dev/services.ts

Comment on lines 14 to 19
export interface PreludeOperations {
createVerification: (
phoneNumber: E164PhoneNumber,
requestHeaders: CommonHeaders
requestHeaders: CommonHeaders,
channel: VerificationChannel
) => Effect.Effect<SmsVerificationSid, UnableToSendVerificationSmsError>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
fd -a 'prelude\.ts$|verification.*\.ts$|sms.*\.ts$|whatsapp.*\.ts$' . | sed 's#^\./##' | head -100

echo "== prelude outline =="
ast-grep outline apps/user-service/src/utils/prelude.ts --view expanded || true

echo "== prelude relevant lines =="
cat -n apps/user-service/src/utils/prelude.ts | sed -n '1,180p'

echo "== createVerification usages =="
rg -n "createVerification|sentVia|VerificationChannel|SmsVerificationSid|PreludeOperations" -S apps packages . --glob '!node_modules' --glob '!dist' --glob '!build' | head -250

Repository: vexl-it/vexl

Length of output: 31833


🌐 Web query:

Prelude.so verification.create response fields preferred_channel fallback channel WhatsApp SMS

💡 Result:

In the Prelude.so Verify v2 API, the verification.create endpoint supports preferred channel routing via the options object [1][2]. While there is no explicit fallback_channel parameter in the API request schema, Prelude employs intelligent, automatic fallback mechanisms that default to other available channels (such as SMS) if the preferred channel is unavailable [3][2]. Request Structure To specify a preferred channel when initiating a verification, include the preferred_channel field within the options object in your API request [2]. Example JSON request body: { "target": { "type": "phone_number", "value": "+30123456789" }, "options": { "preferred_channel": "whatsapp" } } Supported Channels You can specify several channels for the preferred_channel parameter, including but not limited to: - whatsapp [2] - sms [2] - rcs [2] - telegram [2] - viber [2] - voice [2] Functionality 1. Preferred Channel: When you set a preferred_channel, the system attempts to deliver the verification code through that specific channel first [2]. 2. Intelligent Fallback: If the requested channel is unavailable or delivery fails, Prelude automatically falls back to other available channels to ensure the message is delivered [2]. This process is managed by Prelude's routing engine, which evaluates real-time delivery conditions [2][4]. 3. Dashboard Configuration: In addition to API parameters, you can configure allowed channels and specific fallback strategies (e.g., cost-first or conversion-first) per market directly within the Prelude Dashboard [2]. For more granular control over message delivery, including explicit templates or media attachments, the Notify API also supports the preferred_channel parameter [5][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== lock/package references =="
for f in package.json turbo.json pnpm-lock.yaml yarn.lock package-lock.json; do
  [ -f "$f" ] && { echo "--- $f"; rg -n '"`@prelude.so/sdk`"|`@prelude`\.so/sdk' "$f" -C 3 || true; }
done

echo "== initVerificationHandler relevant lines =="
cat -n apps/user-service/src/routes/login/handlers/initVerificationHandler.ts | sed -n '130,225p'

echo "== smsVerificationUtils relevant lines =="
cat -n apps/user-service/src/utils/smsVerificationUtils.ts | sed -n '1,90p'

echo "== mobile VerificationCodeScreen relevant lines =="
cat -n apps/mobile/src/components/LoginFlow/components/VerificationCodeScreen.tsx | sed -n '90,135p'; cat -n apps/mobile/src/components/LoginFlow/components/VerificationCodeScreen.tsx | sed -n '300,335p'

echo "== tests expected channel logic =="
cat -n apps/user-service/src/__tests__/login/initVerification.test.ts | sed -n '1,155p'

echo "== precise JS behavior probe for requested channel assignment path =="
node - <<'JS'
function presentChannel(requested) {
  // Mirrors the request-side channel resolver: if no channel requested or no country prefix, return sms.
  return requested && requested.channel ? requested.channel : 'sms'
}
const cases = [
  {requested: {channel: 'whatsapp'}},
  {requested: {channel: 'sms'}},
  {requested: null},
  {requested: undefined},
]
for (const c of cases) {
  console.log(JSON.stringify({ c, sentVia: presentChannel(c.requested) }))
}
JS

Repository: vexl-it/vexl

Length of output: 17552


🌐 Web query:

Prelude.so Node SDK verification.create response object channel sent_channel actual channel

💡 Result:

In the Prelude.so Node.js SDK, the verification.create method does not directly return a response object containing a dedicated "sent_channel" or "actual_channel" field in its standard response object [1][2]. The verification.create response typically contains fields such as id, status, method, metadata, and request_id [2]. Prelude's Verify API is designed to manage the full verification lifecycle on "auto-pilot," automatically selecting the most appropriate channel (e.g., SMS, WhatsApp, Viber, etc.) based on the user's context and country, and managing fallbacks without requiring the client to specify or explicitly track the channel in the immediate response object [3][4]. If you need to know which channel was used, this information is typically delivered via webhook events sent to your configured callback URL [5][6][7]. The webhook events are triggered when verification events occur, such as delivery status changes, and would contain the specific details regarding the channel used for that attempt [5][7]. To inspect the raw HTTP response from the Prelude API—which may contain additional headers or metadata not mapped to the primary response object—you can use the.asResponse or.withResponse methods provided by the SDK [1][8]: const { data: verification, response: raw } = await client.verification.create({ target: { type: 'phone_number', value: '+30123456789' } }).withResponse; For specifying a preference during creation, the API parameters support a preferred_channel field (e.g., "sms", "whatsapp", "viber", "zalo", "telegram", "rcs"), which allows you to suggest a channel, though the system ultimately determines the best route [5][7].

Citations:


🌐 Web query:

Prelude.so verification create response channel field "method" "channel"

💡 Result:

In the Prelude Verify v2 API, when creating a verification request via the POST /v2/verification endpoint, you can specify a preferred communication channel by using the preferred_channel field [1][2]. This field is typically nested within the request parameters (often under an options object depending on the specific SDK implementation) [3][1][2]. The preferred_channel field allows you to prioritize a specific delivery method for the verification code. If the chosen channel is unavailable, the system will automatically fall back to other configured and available channels [4]. Supported values for preferred_channel generally include [1][2]: - sms - rcs - whatsapp - viber - zalo - telegram Additionally, while preferred_channel controls the communication medium, the method field (also found in the options object) is used to specify the delivery mode, such as auto, voice, or message (which skips silent verification) [3][1]. To use these channels, ensure they are enabled within your Prelude Dashboard under the Verify API settings [5][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== local npm packages / sdk types =="
for p in node_modules/@prelude.so/sdk node_modules/@types/prelude.so node_modules/prelude.so; do
  if [ -d "$p" ]; then
    echo "--- $p"
    find "$p" -maxdepth 4 -type f \( -name "*.d.ts" -o -name "*.ts" -o -name "*.js" -o -name "package.json" \) | head -80
  else
    echo "--- $p not present"
  fi
done

echo "== all references to prelude sdk types =="
rg -n "verification/create|verificationCreate|PreferredChannel|VerificationCreateParams|withResponse|response.*channel|channel.*response|sent_channel|actual_channel" node_modules packages apps -S --glob '!node_modules/**' || true

echo "== prelude module resolution =="
node - <<'JS'
try {
  console.log(require.resolve('`@prelude.so/sdk`'))
} catch (e) {
  console.log('not resolvable:', e.code)
}
JS

Repository: vexl-it/vexl

Length of output: 10106


Return or fetch Preldue’s actual delivery channel before publishing sentVia.

createVerification returns only a SID, and initVerificationHandler stores channel as both the persisted channel and sentVia. Since preferred_channel: 'whatsapp' can fallback to SMS, the API response can report WhatsApp when the actual delivery method is SMS, affecting resend flow and persisted channel state. Return the effective channel from the response, validate it with Schema.decodeUnknown, and propagate that value; otherwise don’t publish WhatsApp when SMS fallback occurred.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/user-service/src/utils/prelude.ts` around lines 14 - 19, Update the
PreludeOperations.createVerification contract and initVerificationHandler flow
to return the effective delivery channel alongside the verification SID.
Validate the returned channel with Schema.decodeUnknown, then use that validated
value for persisted channel state and sentVia instead of the requested channel,
ensuring WhatsApp fallback to SMS is propagated correctly.

Comment on lines +304 to +307
"loginFlow.v2.verificationCode.textWhatsapp": "We sent it via WhatsApp to",
"loginFlow.v2.verificationCode.whatsappFallbackHint": "Don’t use WhatsApp? The code will arrive as a text message instead.",
"loginFlow.v2.verificationCode.sendViaWhatsapp": "Didn’t get the code? Send it via WhatsApp",
"loginFlow.v2.verificationCode.sendViaSms": "Didn’t get the code? Send it via SMS",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Route this en-base.json change through the localization workflow.

These new keys are being added directly to a sibling *-base.json file. Keep base.json canonical and regenerate/upload this locale seed through the repository’s localization process rather than hand-editing it. As per coding guidelines and retrieved learnings, sibling *-base.json files may only be edited through the localization workflow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/localization/en-base.json` around lines 304 - 307, Remove the
manually added verification-code keys from en-base.json and add them through the
repository’s localization workflow, using base.json as the canonical source and
regenerating/uploading the locale seed so sibling *-base.json files remain
workflow-generated.

Sources: Coding guidelines, Learnings

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