Change Email
Faable Auth provides a built-in flow for users to update the email address on their account. The flow is designed around proof of ownership: the new email is never applied until the user clicks a verification link, and an optional second step can require the old email to confirm as well — useful for high-risk accounts.
You can drive this flow from your application’s profile/settings screen with two HTTP calls.
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /user/:user_id/change-email | Start the flow. Sends the verification email to the new address. |
GET | /change-email-verify?ticket=… | Public entry point for the link in the verification email. |
Both endpoints are exposed by your auth tenant (e.g. https://your-tenant.faable.app or your custom domain).
Authentication & limits
POST /user/:user_id/change-emailrequires either anAuthorization: Bearer <access_token>header or a valid session cookie. The caller must match theuser_idin the path — users can only change their own email.- Re-authentication is not required; an active session is enough.
- The endpoint is rate-limited to 5 requests per minute per IP.
GET /change-email-verifyis public (the ticket itself is proof) and rate-limited to 5 requests per 10 seconds.
Starting the flow
POST /user/usr_123/change-email
Authorization: Bearer <access_token>
Content-Type: application/json
{
"new_email": "new@example.com",
"verification_mode": "old_and_new",
"redirect_uri": "https://app.example.com/settings/email-changed"
}| Field | Required | Description |
|---|---|---|
new_email | yes | The address the user wants to switch to. |
verification_mode | no | new_only or old_and_new. Overrides the tenant default — see Verification modes. |
redirect_uri | no | Where to send the user after they click the link. The status of the operation is appended as `?status=applied |
Response:
{
"status": "verification_sent",
"ticket_id": "tkt_abc",
"verification_mode": "old_and_new"
}Behind the scenes Faable creates a verification ticket (TTL 24h) and emails a confirmation link to the new address. Starting a new flow for the same user automatically revokes any pending ticket from a previous attempt.
Verification modes
Two modes are supported, chosen at the tenant level via the account setting email_change_verification_mode (see Configuring the tenant default):
new_only (default)
A single confirmation link is sent to the new email. When the user clicks it:
- The email swap is applied:
user.emailupdates anduser.email_verifiedis set totrue. - The user is redirected to
redirect_uriwith?status=applied. - A security alert email is sent to the old address so the user notices if the change wasn’t them.
old_and_new
Two confirmation steps:
- The first link goes to the new email. Clicking it issues a secondary ticket and emails it to the old address. The user is redirected to
redirect_uriwith?status=pending_old. - The second link goes to the old email. Clicking it finalizes the swap and redirects to
redirect_uriwith?status=applied.
This mode is meant for accounts where account takeover via a compromised new mailbox would be especially damaging.
Policy escalation
The verification_mode field in the request body can only raise the bar, never lower it:
- If the tenant policy is
old_and_new, passingnew_onlyin the request is ignored — double verification is still required. - If the tenant policy is
new_only, the client can opt intoold_and_newper request (for example, on a security-sensitive screen).
[!TIP] If you don’t set
verification_modeon the request, the tenant default is used.
Handling the redirect
After verification completes (or after step 1 of old_and_new), the user is sent back to your redirect_uri with a status query parameter:
?status= | When |
|---|---|
applied | The email change is final. user.email is updated and verified. |
pending_old | First step of old_and_new done; waiting on the click in the old mailbox. |
If you omit redirect_uri, the user lands on a built-in fallback page at /flow/email-change-done on the auth host.
Errors
| HTTP | Code | Meaning |
|---|---|---|
| 400 | same_as_current_email | The new_email matches the current one (case-insensitive). |
| 409 | email_already_exists | Another user in the same tenant already uses that address. The error is generic on purpose, to avoid email enumeration. |
| 401 | — | Missing/invalid bearer or no session cookie. |
| 403 | — | The authenticated user doesn’t match :user_id. |
Side effects
When the change completes, Faable stamps user.email_change_locked_at with the current timestamp. This prevents the email from being silently overwritten on subsequent OAuth logins (when the upstream IdP returns a different email), keeping the user’s chosen address authoritative.
Configuring the tenant default
The tenant-wide default mode lives on the Account resource as email_change_verification_mode. It accepts new_only (default) or old_and_new, and can be updated via the dashboard or the Account update API:
POST /account/:account_id
Content-Type: application/json
{
"email_change_verification_mode": "old_and_new"
}Notification emails
Two transactional emails are sent during this flow. Both are rendered with the tenant’s branding (logo, colors) and translated into the user’s locale (currently English and Spanish):
- Verification email — sent to the new address (and again to the old address in
old_and_newmode) with the confirmation link. - Security alert — sent to the previous address after the swap completes, so the user can react quickly if the change was unexpected.
You can override either template per-tenant from the dashboard.
Next steps
- Team Invitations — invite users by email with a similar verification flow.
- Logs — audit every email change attempt and delivery.