Skip to content

Commit abdfd9f

Browse files
fix(web): write approval audits before email side effect
Move user.join_request_approved and org.member_added audit writes to occur immediately after addUserToOrganization() and before the email send. This ensures the audit trail is complete even if render() or sendMail() throws. Wrapped the email block in try/catch so email failures are logged without propagating as errors. Co-authored-by: Brendan Kellam <brendan-kellam@users.noreply.github.com>
1 parent 073421a commit abdfd9f

1 file changed

Lines changed: 34 additions & 30 deletions

File tree

packages/web/src/actions.ts

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,36 +1005,6 @@ export const approveAccountRequest = async (requestId: string) => sew(async () =
10051005
return addUserToOrgRes;
10061006
}
10071007

1008-
// Send approval email to the user
1009-
const smtpConnectionUrl = getSMTPConnectionURL();
1010-
if (smtpConnectionUrl && env.EMAIL_FROM_ADDRESS) {
1011-
const html = await render(JoinRequestApprovedEmail({
1012-
baseUrl: env.AUTH_URL,
1013-
user: {
1014-
name: request.requestedBy.name ?? undefined,
1015-
email: request.requestedBy.email!,
1016-
avatarUrl: request.requestedBy.image ?? undefined,
1017-
},
1018-
orgName: org.name,
1019-
}));
1020-
1021-
const transport = createTransport(smtpConnectionUrl);
1022-
const result = await transport.sendMail({
1023-
to: request.requestedBy.email!,
1024-
from: env.EMAIL_FROM_ADDRESS,
1025-
subject: `Your request to join ${org.name} has been approved`,
1026-
html,
1027-
text: `Your request to join ${org.name} on Sourcebot has been approved. You can now access the organization at ${env.AUTH_URL}`,
1028-
});
1029-
1030-
const failed = result.rejected.concat(result.pending).filter(Boolean);
1031-
if (failed.length > 0) {
1032-
logger.error(`Failed to send approval email to ${request.requestedBy.email}: ${failed}`);
1033-
}
1034-
} else {
1035-
logger.warn(`SMTP_CONNECTION_URL or EMAIL_FROM_ADDRESS not set. Skipping approval email to ${request.requestedBy.email}`);
1036-
}
1037-
10381008
await auditService.createAudit({
10391009
action: "user.join_request_approved",
10401010
actor: {
@@ -1057,6 +1027,40 @@ export const approveAccountRequest = async (requestId: string) => sew(async () =
10571027
message: `${user.id} approved join request ${requestId} for ${request.requestedById}`,
10581028
},
10591029
});
1030+
1031+
// Send approval email to the user
1032+
const smtpConnectionUrl = getSMTPConnectionURL();
1033+
if (smtpConnectionUrl && env.EMAIL_FROM_ADDRESS) {
1034+
try {
1035+
const html = await render(JoinRequestApprovedEmail({
1036+
baseUrl: env.AUTH_URL,
1037+
user: {
1038+
name: request.requestedBy.name ?? undefined,
1039+
email: request.requestedBy.email!,
1040+
avatarUrl: request.requestedBy.image ?? undefined,
1041+
},
1042+
orgName: org.name,
1043+
}));
1044+
1045+
const transport = createTransport(smtpConnectionUrl);
1046+
const result = await transport.sendMail({
1047+
to: request.requestedBy.email!,
1048+
from: env.EMAIL_FROM_ADDRESS,
1049+
subject: `Your request to join ${org.name} has been approved`,
1050+
html,
1051+
text: `Your request to join ${org.name} on Sourcebot has been approved. You can now access the organization at ${env.AUTH_URL}`,
1052+
});
1053+
1054+
const failed = result.rejected.concat(result.pending).filter(Boolean);
1055+
if (failed.length > 0) {
1056+
logger.error(`Failed to send approval email to ${request.requestedBy.email}: ${failed}`);
1057+
}
1058+
} catch (e) {
1059+
logger.error(`Failed to send approval email to ${request.requestedBy.email}: ${e}`);
1060+
}
1061+
} else {
1062+
logger.warn(`SMTP_CONNECTION_URL or EMAIL_FROM_ADDRESS not set. Skipping approval email to ${request.requestedBy.email}`);
1063+
}
10601064
return {
10611065
success: true,
10621066
}

0 commit comments

Comments
 (0)