Better Auth Ruby does not send email directly. Configure Ruby callables that receive email payloads and the Rack request.
Email Verification
To use one-time-password based verification, use the email OTP plugin. This page covers link-based verification.
Adding Email Verification To Your App
auth = BetterAuth.auth(
base_url: "https://app.example.com/api/auth",
secret: ENV.fetch("BETTER_AUTH_SECRET"),
email_and_password: {
enabled: true
},
email_verification: {
send_verification_email: lambda do |data, request|
AuthMailer.with(
user: data.fetch(:user),
url: data.fetch(:url),
token: data.fetch(:token),
ip: request&.ip
).verify_email.deliver_later
end
}
)The payload includes :user, :url, and :token.
Triggering Email Verification
1. During Sign-Up
Send a verification email when a user signs up:
auth = BetterAuth.auth(
secret: ENV.fetch("BETTER_AUTH_SECRET"),
email_and_password: {enabled: true},
email_verification: {
send_on_sign_up: true,
send_verification_email: ->(data, _request) { AuthMailer.verify_email(data).deliver_later }
}
)When email_and_password.require_email_verification is true, sign-up also sends the verification email.
2. Require Email Verification
Require users to verify before email/password sign-in succeeds:
auth = BetterAuth.auth(
secret: ENV.fetch("BETTER_AUTH_SECRET"),
email_and_password: {
enabled: true,
require_email_verification: true
},
email_verification: {
send_on_sign_in: true,
send_verification_email: ->(data, _request) { AuthMailer.verify_email(data).deliver_later }
}
)With send_on_sign_in, Better Auth sends a fresh verification link when an unverified user attempts to sign in.
3. Manually
auth.api.send_verification_email(
body: {
email: "john@example.com",
callbackURL: "/dashboard"
}
)If the user exists and is unverified, the configured email callback is called. Missing users return the same success response to avoid account enumeration.
Verifying The Email
Verification links point to /verify-email.
auth.api.verify_email(
query: {
token: params[:token],
callbackURL: "/dashboard"
}
)The endpoint marks the user email as verified and redirects to callbackURL when provided.
Auto Sign In After Verification
Set auto_sign_in_after_verification to create a session cookie after verification.
email_verification: {
auto_sign_in_after_verification: true,
send_verification_email: ->(data, _request) { AuthMailer.verify_email(data).deliver_later }
}When calling server-side, use as_response: true or return_headers: true to forward the Set-Cookie header.
Callback Before Email Verification
Run custom logic before a user is marked as verified.
email_verification: {
before_email_verification: lambda do |user, request|
raise BetterAuth::APIError.new("BAD_REQUEST", message: "domain blocked") if user["email"].end_with?("@blocked.test")
end
}Callback After Successful Email Verification
Run side effects after verification succeeds.
email_verification: {
after_email_verification: lambda do |user, request|
Analytics.track("email_verified", user_id: user["id"])
end
}Ruby also supports on_email_verification during the verification flow.
Callback On Duplicate Sign-Up Attempt
Use on_existing_user_sign_up to react when someone attempts to sign up with an existing email.
auth = BetterAuth.auth(
secret: ENV.fetch("BETTER_AUTH_SECRET"),
email_and_password: {
enabled: true,
on_existing_user_sign_up: lambda do |data, request|
SecurityMailer.existing_signup(data.fetch(:user)).deliver_later
end
}
)Password Reset Email
Configure send_reset_password for password reset links.
auth = BetterAuth.auth(
base_url: "https://app.example.com/api/auth",
secret: ENV.fetch("BETTER_AUTH_SECRET"),
email_and_password: {
enabled: true,
send_reset_password: lambda do |data, request|
AuthMailer.with(
user: data.fetch(:user),
url: data.fetch(:url),
token: data.fetch(:token)
).reset_password.deliver_later
end,
on_password_reset: lambda do |user, request|
AuditLog.create!(user_id: user["id"], action: "password_reset")
end,
revoke_sessions_on_password_reset: true
}
)Request a reset email:
auth.api.forget_password(
body: {
email: "john@example.com",
redirectTo: "/reset-password"
}
)Reset the password:
auth.api.reset_password(
body: {
token: params[:token],
newPassword: "new-password123"
}
)