Email Verification & Password Reset
Set up outgoing email for teenybase auth, then test password reset and email verification end to end.
This guide covers:
- the JSON auth endpoints your frontend calls
- local testing with
mock: true - real-provider testing after deploy
What must already exist
You need:
- a
userstable with theauthextension - auth fields for email, password, and email verification (
...authFieldsalready gives you these) - an
emailconfig inteenybase.ts
1. Add email config
Minimal Resend example:
export default {
appUrl: 'https://myapp.com',
email: {
from: 'My App <noreply@myapp.com>',
variables: {
company_name: 'My App',
company_url: 'https://myapp.com',
company_address: '123 Main St, San Francisco, CA',
company_copyright: 'Copyright 2026 My App',
support_email: 'support@myapp.com',
},
resend: {
RESEND_API_KEY: '$RESEND_API_KEY',
},
},
tables: [
// your tables here
],
} satisfies DatabaseSettingsappUrl is the base URL teenybase uses to build the reset and verification links it embeds in emails. The default templates produce:
{appUrl}/reset-password/{token}for password resets{appUrl}/verify-email/{token}for email verification
Your frontend hosts those two routes, reads the token out of the URL, and calls the matching confirm-* JSON endpoint shown in section 4.
Then add the secret:
RESEND_API_KEY=your-real-provider-keyUse mailgun instead of resend if you prefer Mailgun.
Practical notes:
- use a
fromaddress that your email provider accepts - on Teenybase Cloud, the first
teeny deploy --remoteuploads.prod.varsautomatically - later
.prod.varschanges needteeny secrets --remote --upload
2. Local test with mock email
For local development, switch the provider block to:
email: {
from: 'My App <noreply@myapp.com>',
variables: {
company_name: 'My App',
company_url: 'http://localhost:8787',
company_address: 'Local dev',
company_copyright: 'Local dev',
support_email: 'support@myapp.com',
},
mock: true,
},Run locally:
teeny deploy --local
teeny dev --localnpx teeny deploy --local
npx teeny dev --localTest password reset locally
Request the reset email:
curl -X POST http://localhost:8787/api/v1/table/users/auth/request-password-reset \
-H 'Content-Type: application/json' \
-d '{"email":"alice@example.com"}'Use the user's email address in the email field.
With mock: true, teenybase logs the email instead of sending it. Use the token from the logged reset link, then confirm the reset:
curl -X POST http://localhost:8787/api/v1/table/users/auth/confirm-password-reset \
-H 'Content-Type: application/json' \
-d '{"token":"FROM_EMAIL","password":"newsecret123","passwordConfirm":"newsecret123"}'If your auth config does not use passwordConfirmSuffix, omit passwordConfirm.
Test verification locally
First sign up or log in and keep the returned access token.
Request the verification email:
curl -X POST http://localhost:8787/api/v1/table/users/auth/request-verification \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Then confirm it with the token from the mocked email:
curl -X POST http://localhost:8787/api/v1/table/users/auth/confirm-verification \
-H 'Content-Type: application/json' \
-d '{"token":"FROM_EMAIL"}'4. Custom frontend/API flow
Password reset
Step 1: request the email
await fetch(`${API}/table/users/auth/request-password-reset`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'alice@example.com',
}),
})Step 2: confirm the reset with the token from the email
await fetch(`${API}/table/users/auth/confirm-password-reset`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token,
password: 'newsecret123',
passwordConfirm: 'newsecret123',
}),
})Email verification
Request the verification email while authenticated:
await fetch(`${API}/table/users/auth/request-verification`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
},
})Confirm it after the user clicks the email link:
await fetch(`${API}/table/users/auth/confirm-verification`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token }),
})5. Real provider test after deploy
Once the app is deployed and .prod.vars contains the real provider key:
teeny deploy --remote
teeny statusnpx teeny deploy --remote
npx teeny statusIf you later change .prod.vars:
teeny secrets --remote --uploadnpx teeny secrets --remote --uploadThen run this real end-to-end check:
- Sign up a fresh user on the deployed app.
- From your frontend, call
request-password-resetwith that user's email (orcurlit directly). - Open the real email and click the reset link.
- Your frontend's reset page reads the token from the URL and calls
confirm-password-reset. - Confirm the old password fails and the new password works.
- While authenticated, call
request-verificationfrom your frontend or the API. - Open the real email and click the verification link.
- Your frontend's verify page calls
confirm-verificationand the account becomes verified.
6. What to verify in the email itself
For every real email flow, check:
- the email arrives
- the link opens the deployed reset or verify page successfully
- the reset/verify action succeeds
- the flow works end to end after the click, not just the email delivery
If links point to localhost, your appUrl or deploy setup is still wrong.
If you are on Teenybase Cloud and a link opens on an unexpected internal *.workers.dev host even though the flow still works, treat that as a platform/gateway issue rather than an appUrl mistake. Use this guide together with: