Merge pull request #3837 from egeoztass/fix/email-error-propagation
fix: propagate SMTP transport errors from EmailService.sendEmail
This commit is contained in:
9 files changed
+119
-49
No files matched your search
@@ -93,24 +93,30 @@ class SettingsController implements ISettingsController {
|
||||
if (!html) {
|
||||
throw new AppError({ message: "Failed to build email template.", status: 500 });
|
||||
}
|
||||
const messageId = await this.emailService.sendEmail(to, subject, html, {
|
||||
systemEmailHost,
|
||||
systemEmailPort,
|
||||
systemEmailUser,
|
||||
systemEmailAddress,
|
||||
systemEmailDisplayName,
|
||||
systemEmailPassword,
|
||||
systemEmailConnectionHost,
|
||||
systemEmailSecure,
|
||||
systemEmailPool,
|
||||
systemEmailIgnoreTLS,
|
||||
systemEmailRequireTLS,
|
||||
systemEmailRejectUnauthorized,
|
||||
systemEmailTLSServername,
|
||||
});
|
||||
|
||||
if (!messageId) {
|
||||
throw new AppError({ message: "Failed to send test email.", status: 500 });
|
||||
let messageId: string;
|
||||
try {
|
||||
messageId = await this.emailService.sendEmail(to, subject, html, {
|
||||
systemEmailHost,
|
||||
systemEmailPort,
|
||||
systemEmailUser,
|
||||
systemEmailAddress,
|
||||
systemEmailDisplayName,
|
||||
systemEmailPassword,
|
||||
systemEmailConnectionHost,
|
||||
systemEmailSecure,
|
||||
systemEmailPool,
|
||||
systemEmailIgnoreTLS,
|
||||
systemEmailRequireTLS,
|
||||
systemEmailRejectUnauthorized,
|
||||
systemEmailTLSServername,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
// Surface the underlying SMTP failure: diagnosing the settings is the whole
|
||||
// point of the test email endpoint.
|
||||
throw new AppError({
|
||||
message: error instanceof Error ? `Failed to send test email. ${error.message}` : "Failed to send test email.",
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
|
||||
@@ -96,13 +96,15 @@ export class InviteService implements IInviteService {
|
||||
});
|
||||
}
|
||||
|
||||
const result = await this.emailService.sendEmail(invite.email, "Welcome to Uptime Monitor", html);
|
||||
if (!result) {
|
||||
try {
|
||||
await this.emailService.sendEmail(invite.email, "Welcome to Uptime Monitor", html);
|
||||
} catch (error: unknown) {
|
||||
throw new AppError({
|
||||
message: "Failed to send invite e-mail... Please verify your settings.",
|
||||
service: SERVICE_NAME,
|
||||
method: "sendInviteEmail",
|
||||
status: 500,
|
||||
details: { cause: error instanceof Error ? error.message : "Unknown error" },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -35,16 +35,18 @@ export class EmailProvider extends NotificationProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
const messageId = await this.emailService.sendEmail(notification.address, subject, html);
|
||||
if (!messageId) {
|
||||
try {
|
||||
await this.emailService.sendEmail(notification.address, subject, html);
|
||||
return true;
|
||||
} catch (error: unknown) {
|
||||
this.logger.warn({
|
||||
message: "Email test alert failed",
|
||||
service: SERVICE_NAME,
|
||||
method: "sendTestAlert",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async sendMessage(notification: Notification, message: NotificationMessage): Promise<boolean> {
|
||||
@@ -64,16 +66,20 @@ export class EmailProvider extends NotificationProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
const messageId = await this.emailService.sendEmail(notification.address, subject, html);
|
||||
if (!messageId) {
|
||||
// Providers must resolve to a boolean: NotificationsService fans these out through
|
||||
// Promise.all, so a rejection here would abort the other channels' alerts too.
|
||||
try {
|
||||
await this.emailService.sendEmail(notification.address, subject, html);
|
||||
return true;
|
||||
} catch (error: unknown) {
|
||||
this.logger.warn({
|
||||
message: "Email notification failed",
|
||||
service: SERVICE_NAME,
|
||||
method: "sendMessage",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private buildSubject(message: NotificationMessage): string {
|
||||
|
||||
@@ -30,7 +30,7 @@ export interface IUserService {
|
||||
currentUserEmail: string
|
||||
): Promise<User>;
|
||||
checkSuperadminExists(): Promise<boolean>;
|
||||
requestRecovery(email: string): Promise<string | false | undefined>;
|
||||
requestRecovery(email: string): Promise<string>;
|
||||
validateRecovery(recoveryToken: string): Promise<void>;
|
||||
resetPassword(password: string, recoveryToken: string): Promise<{ user: User; token: string }>;
|
||||
deleteUser(params: { userId: string; teamId: string; roles: UserRole[] }): Promise<void>;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { fileURLToPath } from "url";
|
||||
import { EmailTransportConfig } from "@/domain/app-settings/app-settings.type.js";
|
||||
import { ISettingsService } from "@/domain/app-settings/app-settings.service.js";
|
||||
import { ILogger } from "@/utils/logger.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import nodemailer from "nodemailer";
|
||||
@@ -20,7 +21,7 @@ type TemplateCompiler = (template: string) => (context: Record<string, unknown>)
|
||||
export interface IEmailService {
|
||||
init(): void;
|
||||
buildEmail(template: string, context: Record<string, unknown>): Promise<string | undefined>;
|
||||
sendEmail(to: string, subject: string, html: string, transportConfig?: EmailTransportConfig): Promise<string | false | undefined>;
|
||||
sendEmail(to: string, subject: string, html: string, transportConfig?: EmailTransportConfig): Promise<string>;
|
||||
}
|
||||
|
||||
export class EmailService implements IEmailService {
|
||||
@@ -151,7 +152,12 @@ export class EmailService implements IEmailService {
|
||||
method: "verifyTransporter",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
return false;
|
||||
throw new AppError({
|
||||
message: "Email transporter verification failed",
|
||||
service: SERVICE_NAME,
|
||||
method: "sendEmail",
|
||||
details: { cause: error instanceof Error ? error.message : "Unknown error" },
|
||||
});
|
||||
}
|
||||
|
||||
const trimmedDisplayName = systemEmailDisplayName?.trim();
|
||||
@@ -172,6 +178,12 @@ export class EmailService implements IEmailService {
|
||||
method: "sendEmail",
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
throw new AppError({
|
||||
message: "Failed to send email",
|
||||
service: SERVICE_NAME,
|
||||
method: "sendEmail",
|
||||
details: { cause: error instanceof Error ? error.message : "Unknown error" },
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user