[GH-ISSUE #6078] Sending email verification manually doesn't work properly #10417

Closed
opened 2026-04-13 06:32:15 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @gokhantaskan on GitHub (Nov 18, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6078

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

emailVerification: {
  sendOnSignUp: true,
  // sendOnSignIn: true, => remove it from the config and use `client.sendVerificationEmail`
  sendVerificationEmail: async ({ user, url }) => {
    console.log("==================== EMAIL VERIFICATION ====================");
    console.log("Registering user:", user.email);
    console.log("Verification URL:", url);
    console.log("============================================================");
  },
},

Current vs. Expected behavior

When I use the sendOnSignIn option, the sendVerificationEmail function is triggered, but the callbackURL is always set to "/" and can't be changed. Therefore, I decided to remove sendOnSignIn from the configuration and manually trigger sendVerificationEmail({ email: formState.email, callbackURL: "/auth/sign-in" }) if the email is not verified. However, I'm now encountering an error message that states:

{ "code": "YOU_CAN_ONLY_SEND_A_VERIFICATION_EMAIL_TO_AN_UNVERIFIED_EMAIL", "message": "You can only send a verification email to an unverified email." }

What version of Better Auth are you using?

^1.3.34

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:33:36 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6030",
    "release": "25.1.0",
    "cpuCount": 12,
    "cpuModel": "Apple M3 Pro",
    "totalMemory": "18.00 GB",
    "freeMemory": "0.15 GB"
  },
  "node": {
    "version": "v24.3.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.6.2"
  },
  "frameworks": null,
  "databases": null,
  "betterAuth": {
    "version": "Unknown",
    "config": null
  }
}

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"

export const auth = betterAuth({
  appURL: env.APP_URL,
  basePath: "/api/auth",
  appName: env.APP_NAME,
  secret: env.AUTH_SECRET,
  trustedOrigins: env.AUTH_TRUSTED_ORIGINS.split(",").map(origin => origin.trim()),
  database: drizzleAdapter(db, {
    provider: "pg",
    camelCase: false,
  }),
  plugins: [openAPI()],
  advanced: {
    database: {
      generateId: () => crypto.randomUUID(),
    },
  },
  emailAndPassword: {
    enabled: true,
    autoSignIn: false,
    requireEmailVerification: true,
  },
  emailVerification: {
    sendOnSignUp: true,
    sendVerificationEmail: async ({ user, url }) => {
      console.log("==================== EMAIL VERIFICATION ====================");
      console.log("Registering user:", user.email);
      console.log("Verification URL:", url);
      console.log("============================================================");
    },
  },
});

Additional context

No response

Originally created by @gokhantaskan on GitHub (Nov 18, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6078 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce ```js emailVerification: { sendOnSignUp: true, // sendOnSignIn: true, => remove it from the config and use `client.sendVerificationEmail` sendVerificationEmail: async ({ user, url }) => { console.log("==================== EMAIL VERIFICATION ===================="); console.log("Registering user:", user.email); console.log("Verification URL:", url); console.log("============================================================"); }, }, ``` ### Current vs. Expected behavior When I use the `sendOnSignIn` option, the `sendVerificationEmail` function is triggered, but the `callbackURL` is always set to "/" and can't be changed. Therefore, I decided to remove `sendOnSignIn` from the configuration and manually trigger `sendVerificationEmail({ email: formState.email, callbackURL: "/auth/sign-in" })` if the email is not verified. However, I'm now encountering an error message that states: ```json { "code": "YOU_CAN_ONLY_SEND_A_VERIFICATION_EMAIL_TO_AN_UNVERIFIED_EMAIL", "message": "You can only send a verification email to an unverified email." } ``` ### What version of Better Auth are you using? ^1.3.34 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:33:36 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6030", "release": "25.1.0", "cpuCount": 12, "cpuModel": "Apple M3 Pro", "totalMemory": "18.00 GB", "freeMemory": "0.15 GB" }, "node": { "version": "v24.3.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.6.2" }, "frameworks": null, "databases": null, "betterAuth": { "version": "Unknown", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ appURL: env.APP_URL, basePath: "/api/auth", appName: env.APP_NAME, secret: env.AUTH_SECRET, trustedOrigins: env.AUTH_TRUSTED_ORIGINS.split(",").map(origin => origin.trim()), database: drizzleAdapter(db, { provider: "pg", camelCase: false, }), plugins: [openAPI()], advanced: { database: { generateId: () => crypto.randomUUID(), }, }, emailAndPassword: { enabled: true, autoSignIn: false, requireEmailVerification: true, }, emailVerification: { sendOnSignUp: true, sendVerificationEmail: async ({ user, url }) => { console.log("==================== EMAIL VERIFICATION ===================="); console.log("Registering user:", user.email); console.log("Verification URL:", url); console.log("============================================================"); }, }, }); ``` ### Additional context _No response_
GiteaMirror added the lockedbug labels 2026-04-13 06:32:15 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 18, 2025):

tag @better-auth-agent if you would like an answer from the Agent

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3549480390 --> @better-auth-agent[bot] commented on GitHub (Nov 18, 2025): tag @better-auth-agent if you would like an answer from the Agent <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@dosubot[bot] commented on GitHub (Nov 18, 2025):

Hi @gokhantaskan! I'm Dosu and I’m helping the better-auth team.

The error { code: "YOU_CAN_ONLY_SEND_A_VERIFICATION_EMAIL_TO_AN_UNVERIFIED_EMAIL" } happens because Better Auth checks the emailVerified status of the currently logged-in user (the session user), not the target user whose email you want to verify. This means you can only trigger a verification email for your own unverified account, not for another user or as an admin. This is a known limitation and is enforced to prevent abuse and privacy leaks [source].

For the callbackURL always being "/", this is because the verification URL is built using ctx.body.callbackURL (from the request body) or defaults to "/" if not provided. When using sendOnSignIn, the callbackURL is not passed through, so it always falls back to "/" [source].

If you want to send a verification email with a custom callbackURL, you need to make sure that the callbackURL is included in the request body when calling the endpoint. For self-service flows, this works as expected. For admin/manual flows, you would need to either impersonate the user (so the session matches the target user) or implement a custom server-side flow that bypasses the session check [source].

If you need to automate admin-triggered verification emails, consider patching the endpoint logic to check the target user's emailVerified status directly, or handle the email sending logic in your backend.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

<!-- gh-comment-id:3549489317 --> @dosubot[bot] commented on GitHub (Nov 18, 2025): <!-- Greeting --> Hi @gokhantaskan! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> The error `{ code: "YOU_CAN_ONLY_SEND_A_VERIFICATION_EMAIL_TO_AN_UNVERIFIED_EMAIL" }` happens because Better Auth checks the emailVerified status of the currently logged-in user (the session user), not the target user whose email you want to verify. This means you can only trigger a verification email for your own unverified account, not for another user or as an admin. This is a known limitation and is enforced to prevent abuse and privacy leaks [[source]](https://github.com/better-auth/better-auth/issues/5621). For the callbackURL always being "/", this is because the verification URL is built using `ctx.body.callbackURL` (from the request body) or defaults to "/" if not provided. When using `sendOnSignIn`, the callbackURL is not passed through, so it always falls back to "/" [[source]](https://github.com/better-auth/better-auth/blob/b96672393c156a5caf0d0f315379a79d195a948c/packages/better-auth/src/api/routes/email-verification.ts#L39-L422). If you want to send a verification email with a custom callbackURL, you need to make sure that the callbackURL is included in the request body when calling the endpoint. For self-service flows, this works as expected. For admin/manual flows, you would need to either impersonate the user (so the session matches the target user) or implement a custom server-side flow that bypasses the session check [[source]](https://github.com/better-auth/better-auth/issues/4781). If you need to automate admin-triggered verification emails, consider patching the endpoint logic to check the target user's emailVerified status directly, or handle the email sending logic in your backend. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/14c8494f-5444-4bc4-8ee4-9994f7f0ea86?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/14c8494f-5444-4bc4-8ee4-9994f7f0ea86?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/14c8494f-5444-4bc4-8ee4-9994f7f0ea86?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/14c8494f-5444-4bc4-8ee4-9994f7f0ea86?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/14c8494f-5444-4bc4-8ee4-9994f7f0ea86?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/14c8494f-5444-4bc4-8ee4-9994f7f0ea86?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/14c8494f-5444-4bc4-8ee4-9994f7f0ea86?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/6078)
Author
Owner

@gokhantaskan commented on GitHub (Nov 18, 2025):

That was a fantastic answer, and it worked when I signed out and had an empty session.

<!-- gh-comment-id:3549499533 --> @gokhantaskan commented on GitHub (Nov 18, 2025): That was a fantastic answer, and it worked when I signed out and had an empty session.
Author
Owner

@dosubot[bot] commented on GitHub (Nov 18, 2025):

Happy to help! If you run into any more issues or have questions about Better Auth, just let me know.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

<!-- gh-comment-id:3549500162 --> @dosubot[bot] commented on GitHub (Nov 18, 2025): <!-- Answer --> Happy to help! If you run into any more issues or have questions about Better Auth, just let me know. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/94738d8c-5208-4ce9-9ab4-9d2ccd417c2c?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/94738d8c-5208-4ce9-9ab4-9d2ccd417c2c?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/94738d8c-5208-4ce9-9ab4-9d2ccd417c2c?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/94738d8c-5208-4ce9-9ab4-9d2ccd417c2c?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/94738d8c-5208-4ce9-9ab4-9d2ccd417c2c?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/94738d8c-5208-4ce9-9ab4-9d2ccd417c2c?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/94738d8c-5208-4ce9-9ab4-9d2ccd417c2c?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/6078)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10417