fix(server): avoid job fail if mail is not configured (#12306)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Improved email notification handling to prevent errors from interrupting other processes when sending emails is not possible.
- **Refactor**
  - Updated internal email sending logic across notifications and workspace features for more robust operation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
forehalo
2025-05-20 02:49:49 +00:00
parent acce1fbd99
commit 42d527251a
6 changed files with 33 additions and 19 deletions

View File

@@ -6,6 +6,9 @@ import { MailName } from '../../mails';
export class MockMailer { export class MockMailer {
send = Sinon.createStubInstance(Mailer).send.resolves(true); send = Sinon.createStubInstance(Mailer).send.resolves(true);
trySend(command: Jobs['notification.sendMail']) {
return this.send(command, true);
}
last<Mail extends MailName>( last<Mail extends MailName>(
name: Mail name: Mail

View File

@@ -10,13 +10,20 @@ export class Mailer {
private readonly sender: MailSender private readonly sender: MailSender
) {} ) {}
get enabled() { /**
// @ts-expect-error internal api * try to send mail
return this.sender.smtp !== null; *
* @note never throw
*/
async trySend(command: Jobs['notification.sendMail']) {
return this.send(command, true);
} }
async send(command: Jobs['notification.sendMail']) { async send(command: Jobs['notification.sendMail'], suppressError = false) {
if (!this.enabled) { if (!this.sender.configured) {
if (suppressError) {
return false;
}
throw new EmailServiceNotConfigured(); throw new EmailServiceNotConfigured();
} }

View File

@@ -43,6 +43,10 @@ export class MailSender {
return createTransport(configToSMTPOptions(config)); return createTransport(configToSMTPOptions(config));
} }
get configured() {
return this.smtp !== null;
}
@OnEvent('config.init') @OnEvent('config.init')
onConfigInit() { onConfigInit() {
this.setup(); this.setup();

View File

@@ -65,7 +65,7 @@ export class NotificationService {
elementId: input.body.doc.elementId, elementId: input.body.doc.elementId,
}) })
); );
await this.mailer.send({ await this.mailer.trySend({
name: 'Mention', name: 'Mention',
to: receiver.email, to: receiver.email,
props: { props: {
@@ -110,7 +110,7 @@ export class NotificationService {
if (!receiver) { if (!receiver) {
return; return;
} }
await this.mailer.send({ await this.mailer.trySend({
name: 'MemberInvitation', name: 'MemberInvitation',
to: receiver.email, to: receiver.email,
props: { props: {
@@ -161,7 +161,7 @@ export class NotificationService {
if (!inviter) { if (!inviter) {
return; return;
} }
await this.mailer.send({ await this.mailer.trySend({
name: 'MemberAccepted', name: 'MemberAccepted',
to: inviter.email, to: inviter.email,
props: { props: {
@@ -226,7 +226,7 @@ export class NotificationService {
if (!reviewer) { if (!reviewer) {
return; return;
} }
await this.mailer.send({ await this.mailer.trySend({
name: 'LinkInvitationReviewRequest', name: 'LinkInvitationReviewRequest',
to: reviewer.email, to: reviewer.email,
props: { props: {
@@ -273,7 +273,7 @@ export class NotificationService {
if (!receiver) { if (!receiver) {
return; return;
} }
await this.mailer.send({ await this.mailer.trySend({
name: 'LinkInvitationApprove', name: 'LinkInvitationApprove',
to: receiver.email, to: receiver.email,
props: { props: {
@@ -312,7 +312,7 @@ export class NotificationService {
if (!receiver) { if (!receiver) {
return; return;
} }
await this.mailer.send({ await this.mailer.trySend({
name: 'LinkInvitationDecline', name: 'LinkInvitationDecline',
to: receiver.email, to: receiver.email,
props: { props: {

View File

@@ -65,7 +65,7 @@ export class WorkspaceEvents {
return; return;
} }
await this.mailer.send({ await this.mailer.trySend({
name: 'MemberRemoved', name: 'MemberRemoved',
to: user.email, to: user.email,
props: { props: {

View File

@@ -107,7 +107,7 @@ export class WorkspaceService {
const admins = await this.models.workspaceUser.getAdmins(workspaceId); const admins = await this.models.workspaceUser.getAdmins(workspaceId);
const link = this.url.link(`/workspace/${workspaceId}`); const link = this.url.link(`/workspace/${workspaceId}`);
await this.mailer.send({ await this.mailer.trySend({
name: 'TeamWorkspaceUpgraded', name: 'TeamWorkspaceUpgraded',
to: owner.email, to: owner.email,
props: { props: {
@@ -121,7 +121,7 @@ export class WorkspaceService {
await Promise.allSettled( await Promise.allSettled(
admins.map(async user => { admins.map(async user => {
await this.mailer.send({ await this.mailer.trySend({
name: 'TeamWorkspaceUpgraded', name: 'TeamWorkspaceUpgraded',
to: user.email, to: user.email,
props: { props: {
@@ -188,7 +188,7 @@ export class WorkspaceService {
} }
if (ws.role === WorkspaceRole.Admin) { if (ws.role === WorkspaceRole.Admin) {
await this.mailer.send({ await this.mailer.trySend({
name: 'TeamBecomeAdmin', name: 'TeamBecomeAdmin',
to: user.email, to: user.email,
props: { props: {
@@ -199,7 +199,7 @@ export class WorkspaceService {
}, },
}); });
} else { } else {
await this.mailer.send({ await this.mailer.trySend({
name: 'TeamBecomeCollaborator', name: 'TeamBecomeCollaborator',
to: user.email, to: user.email,
props: { props: {
@@ -213,7 +213,7 @@ export class WorkspaceService {
} }
async sendOwnershipTransferredEmail(email: string, ws: { id: string }) { async sendOwnershipTransferredEmail(email: string, ws: { id: string }) {
await this.mailer.send({ await this.mailer.trySend({
name: 'OwnershipTransferred', name: 'OwnershipTransferred',
to: email, to: email,
props: { props: {
@@ -225,7 +225,7 @@ export class WorkspaceService {
} }
async sendOwnershipReceivedEmail(email: string, ws: { id: string }) { async sendOwnershipReceivedEmail(email: string, ws: { id: string }) {
await this.mailer.send({ await this.mailer.trySend({
name: 'OwnershipReceived', name: 'OwnershipReceived',
to: email, to: email,
props: { props: {
@@ -238,7 +238,7 @@ export class WorkspaceService {
async sendLeaveEmail(workspaceId: string, userId: string) { async sendLeaveEmail(workspaceId: string, userId: string) {
const owner = await this.models.workspaceUser.getOwner(workspaceId); const owner = await this.models.workspaceUser.getOwner(workspaceId);
await this.mailer.send({ await this.mailer.trySend({
name: 'MemberLeave', name: 'MemberLeave',
to: owner.email, to: owner.email,
props: { props: {