Skip to content

Commit c4745eb

Browse files
authored
Mariano/notifs (#1724)
* chore(rules): add comprehensive and basic rules for Trigger.dev tasks * feat(jobs): add weekly task reminder job and email notification * chore: handle batches
1 parent 0ec6deb commit c4745eb

File tree

2 files changed

+93
-27
lines changed

2 files changed

+93
-27
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { logger, queue, task } from '@trigger.dev/sdk';
2+
import { sendWeeklyTaskDigestEmail } from '@trycompai/email/lib/weekly-task-digest';
3+
4+
// Queue with concurrency limit to prevent rate limiting
5+
const weeklyTaskDigestQueue = queue({
6+
name: 'weekly-task-digest-queue',
7+
concurrencyLimit: 2, // Max 2 emails at a time
8+
});
9+
10+
interface WeeklyTaskDigestPayload {
11+
email: string;
12+
userName: string;
13+
organizationName: string;
14+
organizationId: string;
15+
tasks: Array<{
16+
id: string;
17+
title: string;
18+
}>;
19+
}
20+
21+
export const sendWeeklyTaskDigestEmailTask = task({
22+
id: 'send-weekly-task-digest-email',
23+
queue: weeklyTaskDigestQueue,
24+
run: async (payload: WeeklyTaskDigestPayload) => {
25+
logger.info('Sending weekly task digest email', {
26+
email: payload.email,
27+
organizationName: payload.organizationName,
28+
taskCount: payload.tasks.length,
29+
});
30+
31+
try {
32+
await sendWeeklyTaskDigestEmail(payload);
33+
34+
logger.info('Successfully sent weekly task digest email', {
35+
email: payload.email,
36+
organizationName: payload.organizationName,
37+
});
38+
39+
return {
40+
success: true,
41+
email: payload.email,
42+
};
43+
} catch (error) {
44+
logger.error('Failed to send weekly task digest email', {
45+
email: payload.email,
46+
error: error instanceof Error ? error.message : String(error),
47+
});
48+
49+
throw error;
50+
}
51+
},
52+
});

apps/app/src/jobs/tasks/task/weekly-task-reminder.ts

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { db } from '@db';
22
import { logger, schedules } from '@trigger.dev/sdk';
3-
import { sendWeeklyTaskDigestEmail } from '@trycompai/email/lib/weekly-task-digest';
3+
import { sendWeeklyTaskDigestEmailTask } from '../email/weekly-task-digest-email';
44

55
export const weeklyTaskReminder = schedules.task({
66
id: 'weekly-task-reminder',
@@ -34,11 +34,9 @@ export const weeklyTaskReminder = schedules.task({
3434

3535
logger.info(`Found ${organizations.length} organizations to process`);
3636

37-
let totalEmailsSent = 0;
38-
let totalAdminsProcessed = 0;
39-
const errors: string[] = [];
37+
// Build email payloads for all admins/owners with TODO tasks
38+
const emailPayloads = [];
4039

41-
// Process each organization
4240
for (const org of organizations) {
4341
logger.info(`Processing organization: ${org.name} (${org.id})`);
4442

@@ -65,46 +63,62 @@ export const weeklyTaskReminder = schedules.task({
6563

6664
logger.info(`Found ${todoTasks.length} TODO tasks for organization ${org.name}`);
6765

68-
// Send one email per admin/owner
66+
// Build payload for each admin/owner
6967
for (const member of org.members) {
7068
if (!member.user.email || !member.user.name) {
7169
logger.warn(`Skipping member ${member.id} - missing email or name`);
7270
continue;
7371
}
7472

75-
try {
76-
const result = await sendWeeklyTaskDigestEmail({
73+
emailPayloads.push({
74+
payload: {
7775
email: member.user.email,
7876
userName: member.user.name,
7977
organizationName: org.name,
8078
organizationId: org.id,
8179
tasks: todoTasks,
82-
});
83-
84-
if (result.success) {
85-
totalEmailsSent++;
86-
logger.info(`Sent weekly task digest to ${member.user.email} (${org.name})`);
87-
} else {
88-
errors.push(`Failed to send email to ${member.user.email} (${org.name})`);
89-
logger.error(`Failed to send email to ${member.user.email}`);
90-
}
91-
92-
totalAdminsProcessed++;
93-
} catch (error) {
94-
const errorMsg = `Error sending email to ${member.user.email} (${org.name}): ${error instanceof Error ? error.message : String(error)}`;
95-
errors.push(errorMsg);
96-
logger.error(errorMsg);
80+
},
81+
});
82+
}
83+
}
84+
85+
// Batch trigger all emails with concurrency control
86+
// Trigger.dev has a limit of 500 items per batchTrigger
87+
if (emailPayloads.length > 0) {
88+
const BATCH_SIZE = 500;
89+
const batches = [];
90+
91+
for (let i = 0; i < emailPayloads.length; i += BATCH_SIZE) {
92+
batches.push(emailPayloads.slice(i, i + BATCH_SIZE));
93+
}
94+
95+
logger.info(`Triggering ${emailPayloads.length} emails in ${batches.length} batch(es)`);
96+
97+
try {
98+
for (const batch of batches) {
99+
await sendWeeklyTaskDigestEmailTask.batchTrigger(batch);
100+
logger.info(`Triggered batch of ${batch.length} emails`);
97101
}
102+
103+
logger.info(`Successfully triggered all ${emailPayloads.length} weekly task digest emails`);
104+
} catch (error) {
105+
logger.error(`Failed to trigger batch email sends: ${error}`);
106+
107+
return {
108+
success: false,
109+
timestamp: new Date().toISOString(),
110+
organizationsProcessed: organizations.length,
111+
totalAdminsProcessed: emailPayloads.length,
112+
emailsTriggered: 0,
113+
error: error instanceof Error ? error.message : String(error),
114+
};
98115
}
99116
}
100117

101118
const summary = {
102-
success: errors.length === 0,
103119
timestamp: new Date().toISOString(),
104120
organizationsProcessed: organizations.length,
105-
totalAdminsProcessed,
106-
emailsSent: totalEmailsSent,
107-
errors: errors.length > 0 ? errors : undefined,
121+
emailsTriggered: emailPayloads.length,
108122
};
109123

110124
logger.info('Weekly task reminder job completed', summary);

0 commit comments

Comments
 (0)