Skip to content

Commit 707802c

Browse files
committed
WIP
1 parent 3444641 commit 707802c

File tree

5 files changed

+56
-20
lines changed

5 files changed

+56
-20
lines changed

packages/core/src/events/handlers/sendIssueEscalatingHandler.test.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -188,17 +188,21 @@ describe('sendIssueEscalatingHandler', () => {
188188
// Verify first batch has 2 users
189189
const firstBatch = mockSend.mock.calls[0][0]
190190
expect(firstBatch.to).toHaveLength(2)
191+
expect(firstBatch.recipientVariables).toBeDefined()
192+
expect(Object.keys(firstBatch.recipientVariables)).toHaveLength(2)
191193

192194
// Verify second batch has 1 user
193195
const secondBatch = mockSend.mock.calls[1][0]
194196
expect(secondBatch.to).toHaveLength(1)
197+
expect(secondBatch.recipientVariables).toBeDefined()
198+
expect(Object.keys(secondBatch.recipientVariables)).toHaveLength(1)
195199

196200
// Verify all 3 users were sent emails
197201
const allRecipients = [...firstBatch.to, ...secondBatch.to]
198202
expect(allRecipients).toHaveLength(3)
199-
expect(
200-
allRecipients.map((r: { address: string; name: string }) => r.address),
201-
).toEqual(expect.arrayContaining([user.email, user2.email, user3.email]))
203+
expect(allRecipients).toEqual(
204+
expect.arrayContaining([user.email, user2.email, user3.email]),
205+
)
202206
})
203207
})
204208

@@ -397,14 +401,15 @@ describe('sendIssueEscalatingHandler', () => {
397401
const mailerInstance = vi.mocked(IssueEscalatingMailer).mock.results[0]
398402
?.value
399403

400-
// Verify send was called with Address objects
404+
// Verify send was called with proper format
401405
expect(mailerInstance?.send).toHaveBeenCalledWith({
402-
to: expect.arrayContaining([
403-
expect.objectContaining({
404-
address: expect.any(String),
406+
to: expect.arrayContaining([expect.any(String)]),
407+
recipientVariables: expect.objectContaining({
408+
[user.email]: expect.objectContaining({
405409
name: expect.any(String),
410+
id: expect.any(String),
406411
}),
407-
]),
412+
}),
408413
})
409414
})
410415
})

packages/core/src/events/handlers/sendIssueEscalatingHandler.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@ import { env } from '@latitude-data/env'
33
import { findAllUsersInWorkspace } from '../../data-access/users'
44
import { unsafelyFindWorkspace } from '../../data-access/workspaces'
55
import { NotFoundError } from '../../lib/errors'
6-
import { IssueEscalatingMailer } from '../../mailers'
6+
import {
7+
IssueEscalatingMailer,
8+
SendIssueEscalatingMailOptions,
9+
} from '../../mailers'
710
import { IssuesRepository } from '../../repositories'
811
import { updateEscalatingIssue } from '../../services/issues/updateEscalating'
912
import { captureException } from '../../utils/datadogCapture'
1013
import { IssueIncrementedEvent } from '../events'
1114
import { Workspace } from '../../schema/models/types/Workspace'
1215

13-
const BATCH_SIZE = 100
16+
const BATCH_SIZE = 100 // Batch size can be up to 1000 in mailgun
1417

18+
// TODO: Refactor common batching logic with other mailers
1519
async function sendEmail({
1620
workspace,
1721
issue,
@@ -36,28 +40,43 @@ async function sendEmail({
3640
)
3741

3842
const addresses = users.map((u) => ({
43+
id: u.id,
3944
address: u.email,
4045
name: u.name || u.email,
4146
}))
4247

43-
const batches: (typeof addresses)[] = []
48+
const batches: SendIssueEscalatingMailOptions[] = []
4449

4550
for (let i = 0; i < addresses.length; i += batchSize) {
46-
batches.push(addresses.slice(i, i + batchSize))
51+
const batchAddresses = addresses.slice(i, i + batchSize)
52+
const recipientVariables: Record<string, Record<string, unknown>> = {}
53+
54+
batchAddresses.forEach((user) => {
55+
recipientVariables[user.address] = {
56+
name: user.name,
57+
id: user.id,
58+
}
59+
})
60+
61+
batches.push({
62+
to: batchAddresses.map((u) => u.address),
63+
recipientVariables,
64+
})
4765
}
4866

4967
// Send all batches in parallel and capture any errors
5068
await Promise.all(
5169
batches.map(async (batch, index) => {
52-
const result = await mailer.send({ to: batch })
70+
const result = await mailer.send(batch)
5371

5472
if (result.error) {
73+
const batchSize = Array.isArray(batch.to) ? batch.to.length : 1
5574
captureException(result.error, {
5675
issueId: issue.id,
5776
issueTitle: issue.title,
5877
workspaceId: workspace.id,
5978
batchIndex: index,
60-
batchSize: batch.length,
79+
batchSize: batchSize,
6180
context: 'issue_escalation_email',
6281
})
6382
}
@@ -115,9 +134,7 @@ export async function sendIssueEscalatingHandler({
115134
const wasEscalatingButExpired =
116135
previousEscalatingAt && previousEscalatingAt <= expirationDate
117136

118-
// Early return if still in same escalation period
119137
if (!wasNotEscalating && !wasEscalatingButExpired) return
120138

121-
// Send email for new escalation
122139
await sendEmail({ workspace, issue: updatedIssue, batchSize })
123140
}

packages/core/src/mailers/emails/issues/IssueEscalatingMail.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export default function IssueEscalatingMail({
1515
title='Issue Escalating'
1616
previewText={`Issue "${issueTitle}" is escalating.`}
1717
>
18-
<Text>Hello</Text>
18+
<Text>Hello team</Text>
1919
<Text>
2020
We have detected that the issue <strong>"{issueTitle}"</strong> is
2121
escalating. This means the error frequency has increased significantly

packages/core/src/mailers/mailers/Mailer.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ import SMTPTransport from 'nodemailer/lib/smtp-transport'
66
import { Result, TypedResult } from '../../lib/Result'
77
import { createAdapter } from './adapters'
88

9+
/**
10+
* Batch sending options for Mailgun.
11+
* https://documentation.mailgun.com/docs/mailgun/user-manual/sending-messages/batch-sending
12+
*/
13+
export type ExtendedMailOptions = Mail.Options & {
14+
'recipient-variables'?: Record<string, Record<string, unknown>>
15+
}
16+
917
export default abstract class Mailer {
1018
protected options: Mail.Options
1119

@@ -21,12 +29,12 @@ export default abstract class Mailer {
2129
}
2230

2331
abstract send(
24-
_options: Mail.Options,
32+
_options: ExtendedMailOptions,
2533
_attrs: unknown,
2634
): Promise<TypedResult<SMTPTransport.SentMessageInfo, Error>>
2735

2836
protected async sendMail(
29-
options: Mail.Options,
37+
options: ExtendedMailOptions,
3038
): Promise<TypedResult<SMTPTransport.SentMessageInfo, Error>> {
3139
let result
3240
try {

packages/core/src/mailers/mailers/mailers/issues/IssueEscalatingMailer.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ function convertTo(to: Mail.Options['to']): Address[] {
2323
})
2424
}
2525

26+
export type SendIssueEscalatingMailOptions = {
27+
to: Mail.Options['to']
28+
recipientVariables?: Record<string, Record<string, unknown>>
29+
}
2630
export class IssueEscalatingMailer extends Mailer {
2731
issueTitle: string
2832
link: string
@@ -39,14 +43,16 @@ export class IssueEscalatingMailer extends Mailer {
3943

4044
async send({
4145
to,
42-
}: Pick<Mail.Options, 'to'>): Promise<
46+
recipientVariables,
47+
}: SendIssueEscalatingMailOptions): Promise<
4348
TypedResult<SMTPTransport.SentMessageInfo, Error>
4449
> {
4550
const emails = convertTo(to ?? this.options.to)
4651
return this.sendMail({
4752
to: emails,
4853
from: this.options.from,
4954
subject: `📈 Latitude issue Escalating: ${this.issueTitle}`,
55+
'recipient-variables': recipientVariables,
5056
html: await render(
5157
IssueEscalatingMail({
5258
issueTitle: this.issueTitle,

0 commit comments

Comments
 (0)