Fallback by email
When WebAuthn is not available, use the email fallback. All methods return Result<T, TryMellonError>.
Flow
- Start the flow: send OTP to the user’s email.
- Ask the user for the code.
- Verify the code and get a session token.
- Send the session token to your backend (same as with passkeys).
Example
Both userId and email are required. userId is your external user ID — the same value you pass as externalUserId in signUp/signIn. It is not a TryMellon internal ID. Pass the user’s email address in the email field (used only to deliver the code; not stored).
// 1. Send OTP (userId = external id, email = where to send the code)
const startResult = await client.otp.send({
userId: 'user_123',
email: '[email protected]',
})
if (!startResult.ok) {
console.error(startResult.error)
return
}
// 2. Get code from user
const code = prompt('Enter the code sent by email:')
// 3. Verify
const verifyResult = await client.otp.verify({
userId: 'user_123',
code: code,
})
if (!verifyResult.ok) {
console.error(verifyResult.error)
return
}
// 4. Send to backend
await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionToken: verifyResult.value.sessionToken }),
})
Combined flow (passkey with fallback)
async function authenticateUser(userId: string, email: string) {
if (!TryMellon.isSupported()) {
return await authenticateWithEmail(userId, email)
}
const authResult = await client.signIn({ externalUserId: userId })
if (authResult.ok) return authResult
if (
authResult.error.code === 'PASSKEY_NOT_FOUND' ||
authResult.error.code === 'NOT_SUPPORTED'
) {
return await authenticateWithEmail(userId, email)
}
return authResult
}
async function authenticateWithEmail(userId: string, email: string) {
const startRes = await client.otp.send({ userId, email })
if (!startRes.ok) return startRes
const code = prompt('Enter the code sent by email:')
return await client.otp.verify({ userId, code })
}