diff --git a/apps/web/src/server/service/campaign-service.ts b/apps/web/src/server/service/campaign-service.ts index e095daf..37a773a 100644 --- a/apps/web/src/server/service/campaign-service.ts +++ b/apps/web/src/server/service/campaign-service.ts @@ -112,18 +112,22 @@ export async function unsubscribeContactFromLink(id: string, hash: string) { throw new Error("Invalid unsubscribe link"); } - return await unsubscribeContact( + return await unsubscribeContact({ contactId, campaignId, - UnsubscribeReason.UNSUBSCRIBED - ); + reason: UnsubscribeReason.UNSUBSCRIBED, + }); } -export async function unsubscribeContact( - contactId: string, - campaignId: string, - reason: UnsubscribeReason -) { +export async function unsubscribeContact({ + contactId, + campaignId, + reason, +}: { + contactId: string; + campaignId?: string; + reason: UnsubscribeReason; +}) { // Update the contact's subscription status try { const contact = await db.contact.findUnique({ @@ -140,14 +144,16 @@ export async function unsubscribeContact( data: { subscribed: false, unsubscribeReason: reason }, }); - await db.campaign.update({ - where: { id: campaignId }, - data: { - unsubscribed: { - increment: 1, + if (campaignId) { + await db.campaign.update({ + where: { id: campaignId }, + data: { + unsubscribed: { + increment: 1, + }, }, - }, - }); + }); + } } return contact; diff --git a/apps/web/src/server/service/ses-hook-parser.ts b/apps/web/src/server/service/ses-hook-parser.ts index d194382..5fd3921 100644 --- a/apps/web/src/server/service/ses-hook-parser.ts +++ b/apps/web/src/server/service/ses-hook-parser.ts @@ -103,7 +103,12 @@ export async function parseSesHook(data: SesEvent) { mailStatus !== "CLICKED" || !(mailData as SesClick).link.startsWith(`${env.NEXTAUTH_URL}/unsubscribe`) ) { - await checkUnsubscribe(email.campaignId, email.contactId!, mailStatus); + await checkUnsubscribe({ + contactId: email.contactId!, + campaignId: email.campaignId, + teamId: email.teamId, + event: mailStatus, + }); const mailEvent = await db.emailEvent.findFirst({ where: { @@ -129,19 +134,60 @@ export async function parseSesHook(data: SesEvent) { return true; } -function checkUnsubscribe( - campaignId: string, - contactId: string, - event: EmailStatus -) { +async function checkUnsubscribe({ + contactId, + campaignId, + teamId, + event, +}: { + contactId: string; + campaignId: string; + teamId: number; + event: EmailStatus; +}) { if (event === EmailStatus.BOUNCED || event === EmailStatus.COMPLAINED) { - return unsubscribeContact( - contactId, - campaignId, - event === EmailStatus.BOUNCED - ? UnsubscribeReason.BOUNCED - : UnsubscribeReason.COMPLAINED - ); + const contact = await db.contact.findUnique({ + where: { + id: contactId, + }, + }); + + if (!contact) { + return; + } + + const allContacts = await db.contact.findMany({ + where: { + email: contact.email, + contactBook: { + teamId, + }, + }, + }); + + const allContactIds = allContacts + .map((c) => c.id) + .filter((c) => c !== contactId); + + await Promise.all([ + unsubscribeContact({ + contactId, + campaignId, + reason: + event === EmailStatus.BOUNCED + ? UnsubscribeReason.BOUNCED + : UnsubscribeReason.COMPLAINED, + }), + ...allContactIds.map((c) => + unsubscribeContact({ + contactId: c, + reason: + event === EmailStatus.BOUNCED + ? UnsubscribeReason.BOUNCED + : UnsubscribeReason.COMPLAINED, + }) + ), + ]); } }