From c7d260188038632ec07b14e2230350c3e396f0f7 Mon Sep 17 00:00:00 2001 From: Zai Shi Date: Wed, 16 Jul 2025 22:57:55 +0200 Subject: [PATCH] Improve email sending error handling (#732) > [!IMPORTANT] > Improves email sending error handling in `emails.tsx` by introducing `StatusError` and a centralized error handling function for consistent and user-friendly error reporting. > > - **Error Handling**: > - Introduced `StatusError` for better error reporting in `emails.tsx`. > - Added `handleError` function in `sendEmail()` to log errors and throw `StatusError` with a user-friendly message. > - **Email Sending Logic**: > - Updated `sendEmail()` to use `handleError` for consistent error handling. > - Ensures retries for transient errors and logs specific errors for shared email configurations. > - **Misc**: > - Minor refactoring in `sendEmail()` to improve code clarity and maintainability. > > This description was created by [Ellipsis](https://www.ellipsis.dev?ref=stack-auth%2Fstack-auth&utm_source=github&utm_medium=referral) for b6dad5dac16efcb68a2809241935a1b60f3ea84e. You can [customize](https://app.ellipsis.dev/stack-auth/settings/summaries) this summary. It will automatically update as commits are pushed. Co-authored-by: Konsti Wohlwend --- apps/backend/src/lib/emails.tsx | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/apps/backend/src/lib/emails.tsx b/apps/backend/src/lib/emails.tsx index 54b6e6b54..2a40ba24e 100644 --- a/apps/backend/src/lib/emails.tsx +++ b/apps/backend/src/lib/emails.tsx @@ -5,7 +5,7 @@ import { TEditorConfiguration } from '@stackframe/stack-emails/dist/editor/docum import { EMAIL_TEMPLATES_METADATA, renderEmailTemplate } from '@stackframe/stack-emails/dist/utils'; import { UsersCrud } from '@stackframe/stack-shared/dist/interface/crud/users'; import { getEnvVariable } from '@stackframe/stack-shared/dist/utils/env'; -import { StackAssertionError, captureError } from '@stackframe/stack-shared/dist/utils/errors'; +import { StackAssertionError, StatusError, captureError } from '@stackframe/stack-shared/dist/utils/errors'; import { filterUndefined, omit, pick } from '@stackframe/stack-shared/dist/utils/objects'; import { runAsynchronously, wait } from '@stackframe/stack-shared/dist/utils/promises'; import { Result } from '@stackframe/stack-shared/dist/utils/results'; @@ -285,7 +285,17 @@ export async function sendEmail(options: SendEmailOptions) { throw new StackAssertionError("No recipient email address provided to sendEmail", omit(options, ['emailConfig'])); } - return Result.orThrow(await Result.retry(async (attempt) => { + const errorMessage = "Failed to send email. If you are the admin of this project, please check the email configuration and try again."; + + const handleError = (error: any) => { + console.warn("Failed to send email", error); + if (options.emailConfig.type === 'shared') { + captureError("failed-to-send-email-to-shared-email-config", error); + } + throw new StatusError(400, errorMessage); + }; + + const result = await Result.retry(async (attempt) => { const result = await sendEmailWithoutRetries(options); if (result.status === 'error') { @@ -302,12 +312,15 @@ export async function sendEmail(options: SendEmailOptions) { return Result.error(result.error); } - // TODO if using custom email config, we should notify the developer instead of throwing an error - throw new StackAssertionError('Failed to send email: ' + result.error.rawError?.message, extraData); + handleError(extraData); } return result; - }, 3, { exponentialDelayBase: 2000 })); + }, 3, { exponentialDelayBase: 2000 }); + + if (result.status === 'error') { + handleError(result.error); + } } export async function sendEmailFromTemplate(options: {