Skip to content

Commit 162259f

Browse files
committed
feat(app): send email via Novu when the policy has been published
1 parent db7043f commit 162259f

File tree

2 files changed

+78
-51
lines changed

2 files changed

+78
-51
lines changed

apps/app/src/actions/policies/accept-requested-policy-changes.ts

Lines changed: 28 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use server';
22

33
import { db, PolicyStatus } from '@db';
4-
import { sendPolicyNotificationEmail } from '@trycompai/email';
54
import { revalidatePath, revalidateTag } from 'next/cache';
65
import { z } from 'zod';
76
import { authActionClient } from '../safe-action';
@@ -92,56 +91,34 @@ export const acceptRequestedPolicyChangesAction = authActionClient
9291
return roles.includes('employee');
9392
});
9493

95-
// Send notification emails to all employees
96-
// Send emails in batches of 2 per second to respect rate limit
97-
const BATCH_SIZE = 2;
98-
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
99-
100-
const sendEmailsInBatches = async () => {
101-
for (let i = 0; i < employeeMembers.length; i += BATCH_SIZE) {
102-
const batch = employeeMembers.slice(i, i + BATCH_SIZE);
103-
104-
await Promise.all(
105-
batch.map(async (employee) => {
106-
if (!employee.user.email) return;
107-
108-
let notificationType: 'new' | 're-acceptance' | 'updated';
109-
const wasAlreadySigned = policy.signedBy.includes(employee.id);
110-
if (isNewPolicy) {
111-
notificationType = 'new';
112-
} else if (wasAlreadySigned) {
113-
notificationType = 're-acceptance';
114-
} else {
115-
notificationType = 'updated';
116-
}
117-
118-
try {
119-
await sendPolicyNotificationEmail({
120-
email: employee.user.email,
121-
userName: employee.user.name || employee.user.email || 'Employee',
122-
policyName: policy.name,
123-
organizationName: policy.organization.name,
124-
organizationId: session.activeOrganizationId,
125-
notificationType,
126-
});
127-
} catch (emailError) {
128-
console.error(`Failed to send email to ${employee.user.email}:`, emailError);
129-
// Don't fail the whole operation if email fails
130-
}
131-
}),
132-
);
133-
134-
// Only delay if there are more emails to send
135-
if (i + BATCH_SIZE < employeeMembers.length) {
136-
await delay(1000); // wait 1 second between batches
137-
}
138-
}
139-
};
140-
141-
// Fire and forget, but log errors if any
142-
sendEmailsInBatches().catch((error) => {
143-
console.error('Some emails failed to send:', error);
144-
});
94+
// Call /api/send-policy-email to send emails to employees
95+
96+
// Prepare the events array for the API
97+
const events = employeeMembers
98+
.filter((employee) => employee.user.email)
99+
.map((employee) => ({
100+
subscriberId: `${employee.user.id}-${session.activeOrganizationId}`,
101+
email: employee.user.email,
102+
userName: employee.user.name || employee.user.email || 'Employee',
103+
policyName: policy.name,
104+
organizationName: policy.organization.name,
105+
url: `${process.env.NEXT_PUBLIC_APP_URL ?? 'https://app.trycomp.ai'}/${session.activeOrganizationId}/policies/${policy.id}`,
106+
description: `The "${policy.name}" policy has been ${isNewPolicy ? 'created' : 'updated'}.`,
107+
}));
108+
109+
// Call the API route to send the emails
110+
try {
111+
await fetch(`${process.env.BETTER_AUTH_URL ?? ''}/api/send-policy-email`, {
112+
method: 'POST',
113+
headers: {
114+
'Content-Type': 'application/json',
115+
},
116+
body: JSON.stringify(events),
117+
});
118+
} catch (error) {
119+
console.error('Failed to call /api/send-policy-email:', error);
120+
// Don't throw, just log
121+
}
145122

146123
// If a comment was provided, create a comment
147124
if (comment && comment.trim() !== '') {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { NextResponse, type NextRequest } from 'next/server';
2+
import { Novu } from '@novu/api';
3+
4+
export async function POST(request: NextRequest) {
5+
let events;
6+
try {
7+
events = await request.json();
8+
} catch (error) {
9+
return NextResponse.json(
10+
{ success: false, error: 'Invalid JSON in request body' },
11+
{ status: 400 }
12+
);
13+
}
14+
15+
// You may want to validate required fields in the body here
16+
// For now, we just pass the whole body to Novu
17+
18+
const novuApiKey = process.env.NOVU_API_KEY;
19+
if (!novuApiKey) {
20+
return NextResponse.json(
21+
{ success: false, error: 'Novu API key not configured' },
22+
{ status: 500 }
23+
);
24+
}
25+
26+
const novu = new Novu({ secretKey: novuApiKey });
27+
28+
try {
29+
const result = await novu.triggerBulk({
30+
events: events.map((event: any) => ({
31+
workflowId: "new-policy-email",
32+
to: {
33+
subscriberId: event.subscriberId,
34+
email: event.email,
35+
},
36+
payload: event,
37+
})),
38+
});
39+
40+
return NextResponse.json({ success: true, result });
41+
} catch (error) {
42+
return NextResponse.json(
43+
{
44+
success: false,
45+
error: error instanceof Error ? error.message : 'Failed to trigger notification',
46+
},
47+
{ status: 500 }
48+
);
49+
}
50+
}

0 commit comments

Comments
 (0)