Announcing 6-digit codes for email verification and password recovery_
Appwrite Auth can email a 6-digit code for email verification and password recovery, so mobile and desktop apps can handle both without a redirect page or a deep link.

Email verification and password recovery in Appwrite Auth have worked with links. You pass a redirect URL, Appwrite emails a link to it, and a page at that URL reads userId and secret from the query string. On the web, that page is another route in your app. In a mobile or desktop app, you have to set up a deep link or host a page to catch the redirect.
Appwrite Auth can also email a 6-digit code for both flows. Your app asks for the code instead of waiting for a redirect, so verification and recovery happen without leaving your app.
Codes for email verification and password recovery
- Four Account methods.
createEmailVerificationOTPandupdateEmailVerificationOTPverify a signed-in user's email.createRecoveryOTPandupdateRecoveryOTPreset a forgotten password. - 6-digit codes that last 15 minutes. Each code works once. A wrong, expired, or used code fails with
user_invalid_token. - No redirect URL. There's no verification page, reset page, or deep link to build.
- An optional security phrase. Appwrite can add a random phrase to the email and the response, so your users can tell the email came from their own request.
Codes work on Appwrite Cloud and in self-hosted Appwrite 2.3 and later, in the Web, Flutter, Apple, Android, and React Native SDKs.
Verify an email with a code
You can send a verification code once the user is signed in, and it goes to the email address on their account. createEmailVerificationOTP returns a token that holds the user's ID. Pass that ID and the code from the email to updateEmailVerificationOTP.
import { Client, Account } from "appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>');
const account = new Account(client);
// Email a 6-digit code to the signed-in user
const token = await account.createEmailVerificationOTP();
// Confirm the code from the email
await account.updateEmailVerificationOTP({
userId: token.userId,
secret: '<CODE>'
});
import 'package:appwrite/appwrite.dart';
final client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>');
final account = Account(client);
// Email a 6-digit code to the signed-in user
final token = await account.createEmailVerificationOTP();
// Confirm the code from the email
await account.updateEmailVerificationOTP(
userId: token.userId,
secret: '<CODE>',
);
import Appwrite
let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.setProject("<PROJECT_ID>")
let account = Account(client)
// Email a 6-digit code to the signed-in user
let token = try await account.createEmailVerificationOTP()
// Confirm the code from the email
_ = try await account.updateEmailVerificationOTP(
userId: token.userId,
secret: "<CODE>"
)
import io.appwrite.Client
import io.appwrite.services.Account
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
.setProject("<PROJECT_ID>")
val account = Account(client)
// Email a 6-digit code to the signed-in user
val token = account.createEmailVerificationOTP()
// Confirm the code from the email
account.updateEmailVerificationOTP(
userId = token.userId,
secret = "<CODE>"
)
import { Client, Account } from 'react-native-appwrite';
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>');
const account = new Account(client);
// Email a 6-digit code to the signed-in user
const token = await account.createEmailVerificationOTP();
// Confirm the code from the email
await account.updateEmailVerificationOTP({
userId: token.userId,
secret: '<CODE>'
});
Once the code matches, Appwrite marks the email as verified.
Reset a password with a code
Recovery doesn't need a session. Call createRecoveryOTP with the email address your user enters. Then pass the user ID from the returned token, the code, and the new password to updateRecoveryOTP.
const token = await account.createRecoveryOTP({
email: 'email@example.com'
});
await account.updateRecoveryOTP({
userId: token.userId,
secret: '<CODE>',
password: '<NEW_PASSWORD>'
});
final token = await account.createRecoveryOTP(
email: 'email@example.com',
);
await account.updateRecoveryOTP(
userId: token.userId,
secret: '<CODE>',
password: '<NEW_PASSWORD>',
);
let token = try await account.createRecoveryOTP(
email: "email@example.com"
)
_ = try await account.updateRecoveryOTP(
userId: token.userId,
secret: "<CODE>",
password: "<NEW_PASSWORD>"
)
val token = account.createRecoveryOTP(
email = "email@example.com"
)
account.updateRecoveryOTP(
userId = token.userId,
secret = "<CODE>",
password = "<NEW_PASSWORD>"
)
const token = await account.createRecoveryOTP({
email: 'email@example.com'
});
await account.updateRecoveryOTP({
userId: token.userId,
secret: '<CODE>',
password: '<NEW_PASSWORD>'
});
When no active account has that email address, Appwrite doesn't send an email, but it still returns a token with a random user ID. A message like "If an account exists for this email, a code is on its way" works in both cases.
Appwrite rejects a new password that fails your project's password strength, password dictionary, or password history policy. With breached password rejection on, it also rejects passwords found in known data breaches. A rejected password doesn't use up the code, so your user can try a different one. The reset doesn't sign anyone in, so create a session with the new password afterwards.
Show a security phrase
Set phrase to true in either send call, and Appwrite adds a random phrase like "Polite raccoon" to the email and to the phrase field of the returned token. Show it next to the code input. If the phrase in your app matches the one in the email, your user knows the email belongs to the request they made.
Get started with verification and recovery codes
Update to the latest Appwrite client SDK, then add a code input to your email verification and password reset screens.
Resources
Call account.createEmailVerificationOTP() while the user is signed in, and Appwrite emails them a 6-digit code that lasts 15 minutes. Pass the user ID from the returned token and the code to account.updateEmailVerificationOTP() to mark the email as verified. See Verify with a code.
account.createRecoveryOTP() emails a 6-digit code to the address you pass and returns a token with the user's ID. account.updateRecoveryOTP() takes that ID, the code, and the new password. Appwrite rejects a new password that fails your project's password strength, password dictionary, or password history policy, and rejects breached passwords when that option is on. The reset doesn't create a session. See Recover with a code.
Links suit web apps, where the redirect lands on a page in your app. Codes suit mobile apps, desktop apps, and anything without a hosted page, because there's no redirect URL, reset page, or deep link to set up.
Appwrite doesn't send an email, but it still returns a token with a random user ID. A message like "If an account exists for this email, a code is on its way" works in both cases. Confirming with that random ID fails with user_not_found rather than user_invalid_token, so show the same "invalid code" message for both.
15 minutes, and each code works once. Sending a new code doesn't cancel earlier ones, so any code that hasn't expired still works. A wrong, expired, or used code fails with user_invalid_token.
It's an optional random phrase, such as "Polite raccoon", that Appwrite adds to the email and to the returned token when you set phrase to true. Show it in your app next to the code input. When the two match, your user knows the email belongs to the request they made.





