[GH-ISSUE #6436] Unable to handle errors gracefully in sendVerificationEmail callback #27836

Closed
opened 2026-04-17 19:03:52 -05:00 by GiteaMirror · 5 comments
Owner

Originally created by @intermittent-concussions on GitHub (Dec 1, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6436

Is this suited for github?

  • Yes, this is suited for github

The problem

When usingemailVerification, the sendVerificationEmail callback executes after the user is persisted to the database. If the email provider fails (e.g., Resend returns an error), there is no mechanism to handle this gracefully.

To Reproduce

Code Reproduction

emailVerification: {
    sendOnSignUp: true,
    sendVerificationEmail: async ({ user, url }) => {
        const { error } = await resend.emails.send({ ... });

        if (error) {
            // Throwing here causes a generic 500, but user still exists.
            // Not throwing hides the failure from the client.
            throw new Error("Email provider failed"); 
        }
    },
}

Current vs. Expected behavior

Current Behavior

  1. Throwing an error in sendVerificationEmail: Returns a 500 Internal Server Error to the client. The user is left created in the database, but the client assumes the sign-up failed entirely. It is also not possible to ascertain what really happened from just the 500 response.
  2. Catching/Swallowing (not throwing): Returns a success response to the client. The user is created but receives no email, and the client UI behaves as if everything worked perfectly.

Expected Behavior
We should be able to either:

  1. Return a specific error object from the sendVerificationEmail callback that is passed through to the client (indicating user created, but email failed).
  2. Trigger a transaction rollback if this callback fails (preventing user creation) and indicating a sign-up failure to the client side.

I'm not sure what the best way to proceed is here, but sending an email is always something that has the possibility of failure, and not having a graceful way to handle said failure feels sub-optimal. Open to suggestions on what we can do about this!

What version of Better Auth are you using?

1.4.3

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:32:56 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T8132",
    "release": "25.1.0",
    "cpuCount": 10,
    "cpuModel": "Apple M4",
    "totalMemory": "24.00 GB",
    "freeMemory": "0.09 GB"
  },
  "node": {
    "version": "v24.10.0",
    "env": "development"
  },
  "packageManager": {
    "name": "bun",
    "version": "1.3.0"
  },
  "frameworks": null,
  "databases": [
    {
      "name": "pg",
      "version": "^8.16.3"
    },
    {
      "name": "kysely",
      "version": "^0.28.8"
    }
  ],
  "betterAuth": {
    "version": "^1.4.3",
    "config": {
      "emailAndPassword": {
        "enabled": true,
        "requireEmailVerification": true
      },
      "emailVerification": {
        "sendOnSignUp": true,
        "sendOnSignIn": true
      },
      "requireEmailVerification": true,
      "database": {
        "_events": {},
        "_eventsCount": 0,
        "options": {
          "max": 10,
          "min": 0,
          "maxUses": null,
          "allowExitOnIdle": false,
          "maxLifetimeSeconds": 0,
          "idleTimeoutMillis": 10000
        },
        "_clients": [],
        "_idle": [],
        "_expired": {},
        "_pendingQueue": [],
        "ending": false,
        "ended": false
      },
      "advanced": {
        "database": {}
      }
    }
  }
}

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

Backend

Originally created by @intermittent-concussions on GitHub (Dec 1, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6436 ### Is this suited for github? - [x] Yes, this is suited for github ### The problem When using`emailVerification`, the `sendVerificationEmail` callback executes after the user is persisted to the database. If the email provider fails (e.g., Resend returns an error), there is no mechanism to handle this gracefully. ### To Reproduce **Code Reproduction** ```typescript emailVerification: { sendOnSignUp: true, sendVerificationEmail: async ({ user, url }) => { const { error } = await resend.emails.send({ ... }); if (error) { // Throwing here causes a generic 500, but user still exists. // Not throwing hides the failure from the client. throw new Error("Email provider failed"); } }, } ``` ### Current vs. Expected behavior **Current Behavior** 1. **Throwing an error in `sendVerificationEmail`:** Returns a 500 Internal Server Error to the client. The user is left created in the database, but the client assumes the sign-up failed entirely. It is also not possible to ascertain what really happened from just the 500 response. 2. **Catching/Swallowing (not throwing):** Returns a success response to the client. The user is created but receives no email, and the client UI behaves as if everything worked perfectly. **Expected Behavior** We should be able to either: 1. Return a specific error object from the `sendVerificationEmail` callback that is passed through to the client (indicating user created, but email failed). 2. Trigger a transaction rollback if this callback fails (preventing user creation) and indicating a sign-up failure to the client side. I'm not sure what the best way to proceed is here, but sending an email is always something that has the possibility of failure, and not having a graceful way to handle said failure feels sub-optimal. Open to suggestions on what we can do about this! ### What version of Better Auth are you using? 1.4.3 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:32:56 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T8132", "release": "25.1.0", "cpuCount": 10, "cpuModel": "Apple M4", "totalMemory": "24.00 GB", "freeMemory": "0.09 GB" }, "node": { "version": "v24.10.0", "env": "development" }, "packageManager": { "name": "bun", "version": "1.3.0" }, "frameworks": null, "databases": [ { "name": "pg", "version": "^8.16.3" }, { "name": "kysely", "version": "^0.28.8" } ], "betterAuth": { "version": "^1.4.3", "config": { "emailAndPassword": { "enabled": true, "requireEmailVerification": true }, "emailVerification": { "sendOnSignUp": true, "sendOnSignIn": true }, "requireEmailVerification": true, "database": { "_events": {}, "_eventsCount": 0, "options": { "max": 10, "min": 0, "maxUses": null, "allowExitOnIdle": false, "maxLifetimeSeconds": 0, "idleTimeoutMillis": 10000 }, "_clients": [], "_idle": [], "_expired": {}, "_pendingQueue": [], "ending": false, "ended": false }, "advanced": { "database": {} } } } } ``` ### Which area(s) are affected? (Select all that apply) Backend
GiteaMirror added the lockedenhancement labels 2026-04-17 19:03:52 -05:00
Author
Owner

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

Hi there, thanks for reporting this issue. One workaround is to modify your callback so that it handles email sending errors gracefully rather than simply throwing. For example, you could catch the error and return a custom response (or flag) indicating that, although the user was created successfully, the verification email failed to send. Another approach would be to wrap the entire sign-up process in a database transaction so that if the email provider fails, you can roll back the user creation entirely. Currently, the email verification callback runs after the user record is persisted, meaning an error thrown there will result in a generic 500 error without clear feedback to the client. Have you considered implementing a custom error response to differentiate between “user created but email failed” versus a total sign-up failure? This might give you more control over the client–server interaction during these failure states [1], [2].

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

If you need more help, tag @Skyvern in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3594199739 --> @better-auth-agent[bot] commented on GitHub (Dec 1, 2025): Hi there, thanks for reporting this issue. One workaround is to modify your callback so that it handles email sending errors gracefully rather than simply throwing. For example, you could catch the error and return a custom response (or flag) indicating that, although the user was created successfully, the verification email failed to send. Another approach would be to wrap the entire sign-up process in a database transaction so that if the email provider fails, you can roll back the user creation entirely. Currently, the email verification callback runs after the user record is persisted, meaning an error thrown there will result in a generic 500 error without clear feedback to the client. Have you considered implementing a custom error response to differentiate between “user created but email failed” versus a total sign-up failure? This might give you more control over the client–server interaction during these failure states [[1]](https://github.com/better-auth/better-auth/issues/4781), [[2]](https://github.com/better-auth/better-auth/issues/4125). tag @better-auth-agent if you would like an answer from the Agent _If you need more help, tag @Skyvern in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) • [Discord](https://discord.gg/fG2XXEuQX3) • [GitHub](https://github.com/Skyvern-AI/Skyvern) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/fG2XXEuQX3) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/Skyvern-AI/Skyvern)
Author
Owner

@intermittent-concussions commented on GitHub (Dec 1, 2025):

One workaround is to modify your callback so that it handles email sending errors gracefully rather than simply throwing. For example, you could catch the error and return a custom response (or flag) indicating that, although the user was created successfully, the verification email failed to send.

Not possible, since sendVerificationEmail expects Promise<void>.

Another approach would be to wrap the entire sign-up process in a database transaction so that if the email provider fails, you can roll back the user creation entirely.

Sounds like it involves modifying internals? In any case it's way too complex for something the library could easily do for us!

Have you considered implementing a custom error response to differentiate between “user created but email failed” versus a total sign-up failure?

Not sure how I would go around implementing this

<!-- gh-comment-id:3594216006 --> @intermittent-concussions commented on GitHub (Dec 1, 2025): > One workaround is to modify your callback so that it handles email sending errors gracefully rather than simply throwing. For example, you could catch the error and return a custom response (or flag) indicating that, although the user was created successfully, the verification email failed to send. Not possible, since `sendVerificationEmail` expects `Promise<void>`. > Another approach would be to wrap the entire sign-up process in a database transaction so that if the email provider fails, you can roll back the user creation entirely. Sounds like it involves modifying internals? In any case it's way too complex for something the library could easily do for us! > Have you considered implementing a custom error response to differentiate between “user created but email failed” versus a total sign-up failure? Not sure how I would go around implementing this
Author
Owner

@jonathansamines commented on GitHub (Dec 2, 2025):

Hey @intermittent-concussions, have you tried throwing an APIError instead? That should give you control over what the HTTP error looks like (e.g status code, properties, etc).

import { APIError } from "better-call";

throw new APIError("NOT_FOUND", { message: "some custom message" });
<!-- gh-comment-id:3599929311 --> @jonathansamines commented on GitHub (Dec 2, 2025): Hey @intermittent-concussions, have you tried throwing an `APIError` instead? That should give you control over what the HTTP error looks like (e.g status code, properties, etc). ```typescript import { APIError } from "better-call"; throw new APIError("NOT_FOUND", { message: "some custom message" }); ```
Author
Owner

@intermittent-concussions commented on GitHub (Dec 3, 2025):

Hi @jonathansamines! Thank you for the reply!
I've decided to move away from better-auth so I can't try this out, but that sounds like a great solution!
Hopefully it can be documented somewhere so it's easier for people to discover!
I also think it would be a great idea if there was some way to not have the user be created if the email could not be sent!

<!-- gh-comment-id:3605935502 --> @intermittent-concussions commented on GitHub (Dec 3, 2025): Hi @jonathansamines! Thank you for the reply! I've decided to move away from `better-auth` so I can't try this out, but that sounds like a great solution! Hopefully it can be documented somewhere so it's easier for people to discover! I also think it would be a great idea if there was some way to not have the user be created if the email could not be sent!
Author
Owner

@jonathansamines commented on GitHub (Dec 4, 2025):

Hi @jonathansamines! Thank you for the reply! I've decided to move away from better-auth so I can't try this out, but that sounds like a great solution! Hopefully it can be documented somewhere so it's easier for people to discover! I also think it would be a great idea if there was some way to not have the user be created if the email could not be sent!

Sorry to hear that, hope you decide to return back in the future. We have more throughly documented all supported response values here 😄

<!-- gh-comment-id:3609433195 --> @jonathansamines commented on GitHub (Dec 4, 2025): > Hi [@jonathansamines](https://github.com/jonathansamines)! Thank you for the reply! I've decided to move away from `better-auth` so I can't try this out, but that sounds like a great solution! Hopefully it can be documented somewhere so it's easier for people to discover! I also think it would be a great idea if there was some way to not have the user be created if the email could not be sent! Sorry to hear that, hope you decide to return back in the future. We have more throughly documented all supported response values [here](https://github.com/better-auth/better-call?tab=readme-ov-file#returning-a-response) 😄
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#27836