Email verification
Email verification proves that a user controls the address they signed up with. The EmailVerificationPlugin is installed by default; you configure it to deliver links and connect it to password sign-in.
Endpoints
Section titled “Endpoints”| Method | Path | Purpose |
|---|---|---|
POST |
/send-verification-email |
Send a link to {"email":"…","callbackURL":"…"} |
GET |
/verify-email?token=…&callbackURL=… |
Consume the link: marks the email verified, then redirects or returns JSON |
POST /send-verification-email always answers {"status":true} and takes at least 500 ms, whether or not the address is registered or already verified, so it cannot be used to enumerate users. Links look like https://auth.example.com/api/auth/verify-email?token=<jwt>&callbackURL=%2Fdashboard; the token is a JWT signed with the current auth secret and expires after verification_token_expiry (default 1 hour).
On GET /verify-email:
- with a
callbackURL, the browser is redirected there (302) — on failure to…?error=INVALID_TOKEN(or the matching error code); - without one, success returns JSON and failure returns
401with the error code.
Require verified emails
Section titled “Require verified emails”Delivery needs a sender. This example forwards the link through your EmailProvider, requires verification before password sign-in, and re-sends the link when an unverified user tries to sign in:
use crate::auth_schema::AppAuthSchema;use async_trait::async_trait;use alibi::email::EmailProvider;use alibi::plugins::email_verification::SendVerificationEmail;use alibi::plugins::{EmailPasswordPlugin, EmailVerificationConfig, EmailVerificationPlugin};use alibi::sqlx::SqlxStore;use alibi::wire::UserView;use alibi::{AuthConfig, AuthError, AuthResult, BetterAuth};use std::sync::Arc;
struct VerificationMailer(Arc<dyn EmailProvider>);
#[async_trait]impl SendVerificationEmail for VerificationMailer { async fn send(&self, user: &UserView, url: &str, _token: &str) -> AuthResult<()> { let email = user .email .as_deref() .ok_or_else(|| AuthError::bad_request("Email required"))?; self.0 .send(email, "Verify your email", "", &format!("Confirm your address: {url}")) .await }}
async fn build_auth( config: AuthConfig, store: SqlxStore<AppAuthSchema>, mail: Arc<dyn EmailProvider>,) -> AuthResult<BetterAuth<AppAuthSchema>> { let verification = EmailVerificationConfig { send_on_sign_in: true, send_verification_email: Some(Arc::new(VerificationMailer(mail))), ..Default::default() }; let password = EmailPasswordPlugin::new() .enable_signup(true) .require_email_verification(true) .with_email_verification(Arc::new(EmailVerificationPlugin::with_config( verification.clone(), )));
BetterAuth::<AppAuthSchema>::new(config) .store(store) .plugin(password) .plugin(EmailVerificationPlugin::with_config(verification)) .build() .await}With require_email_verification(true):
POST /sign-up/emailcreates the user but does not sign them in — the response is200with"token":nulland no cookie — and sends a verification link.POST /sign-in/emailfor an unverified user fails with403 EMAIL_NOT_VERIFIED(and, withsend_on_sign_in, sends a fresh link).- Following the link verifies the address; the user can now sign in. Set
auto_sign_in_after_verification: trueto issue a session at that moment.
If you have no custom sender but did set an EmailProvider on the builder, verification emails are delivered through it with a default template. Delivery failures fail the request unless you choose another notification policy.
Options
Section titled “Options”EmailVerificationConfig (builder methods of the same names exist on EmailVerificationPlugin):
| Field | Default | Effect |
|---|---|---|
verification_token_expiry |
1 hour | Lifetime of the link |
send_email_notifications |
true |
Master switch for automatic sends |
send_on_sign_up |
unset | Send at sign-up. Unset follows require_email_verification |
send_on_sign_in |
false |
Re-send when an unverified user signs in (needs with_email_verification on the email/password plugin) |
require_verification_for_signin |
false |
Alternative switch to require verification at sign-in |
auto_verify_new_users |
false |
Mark new users verified immediately (for trusted channels) |
auto_sign_in_after_verification |
false |
Create a session when the link is followed and return its token |
send_verification_email |
none | Your SendVerificationEmail; otherwise the builder’s EmailProvider is used |
before_email_verification / after_email_verification |
none | Async hooks Fn(&UserView) -> Future<Output = AuthResult<()>> around the update |
React to verification
Section titled “React to verification”use alibi::plugins::EmailVerificationConfig;use std::sync::Arc;
fn verification() -> EmailVerificationConfig { EmailVerificationConfig { after_email_verification: Some(Arc::new(|user| { let id = user.id.clone(); Box::pin(async move { println!("{id} verified their email"); Ok(()) }) })), ..Default::default() }}An error from before_email_verification stops the verification. OAuth providers that report a verified email, email OTP and magic links also mark the address verified; email OTP can take over the whole flow with override_default_email_verification.
Frontend
Section titled “Frontend”See the official email verification guide.