From 3cc07f5e9f7e4442e43e45d0b08fbadeb7960134 Mon Sep 17 00:00:00 2001 From: Hampus Kraft Date: Thu, 19 Feb 2026 19:15:01 +0000 Subject: [PATCH] fix(email): handle sweego complaints as hard bounces --- .../api/src/webhook/SweegoWebhookService.tsx | 10 ++--- .../tests/SweegoWebhookService.test.tsx | 37 +++++++++++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/packages/api/src/webhook/SweegoWebhookService.tsx b/packages/api/src/webhook/SweegoWebhookService.tsx index bcc302ba..283e8c8e 100644 --- a/packages/api/src/webhook/SweegoWebhookService.tsx +++ b/packages/api/src/webhook/SweegoWebhookService.tsx @@ -126,13 +126,13 @@ export class SweegoWebhookService { } async processEvent(event: SweegoEvent): Promise { - if (event.event_type !== 'soft-bounce' && event.event_type !== 'hard_bounce') { + if (event.event_type !== 'soft-bounce' && event.event_type !== 'hard_bounce' && event.event_type !== 'complaint') { Logger.debug({eventType: event.event_type, recipient: event.recipient}, 'Sweego event received (ignored)'); return; } - if (event.event_type === 'hard_bounce') { - await this.handleHardBounce(event); + if (event.event_type === 'hard_bounce' || event.event_type === 'complaint') { + await this.handleHardBounceOrComplaint(event); return; } @@ -142,7 +142,7 @@ export class SweegoWebhookService { ); } - private async handleHardBounce(event: SweegoEvent): Promise { + private async handleHardBounceOrComplaint(event: SweegoEvent): Promise { Logger.warn( { recipient: event.recipient, @@ -150,7 +150,7 @@ export class SweegoWebhookService { details: event.details, eventId: event.event_id, }, - 'Processing hard bounce - marking email as invalid', + 'Processing hard bounce or complaint - marking email as invalid', ); const user = await this.userRepository.findByEmail(event.recipient); diff --git a/packages/api/src/webhook/tests/SweegoWebhookService.test.tsx b/packages/api/src/webhook/tests/SweegoWebhookService.test.tsx index 95f4bf5d..c247d4b7 100644 --- a/packages/api/src/webhook/tests/SweegoWebhookService.test.tsx +++ b/packages/api/src/webhook/tests/SweegoWebhookService.test.tsx @@ -170,6 +170,43 @@ describe('SweegoWebhookService', () => { ); }); + it('marks complaints as unverified', async () => { + const userRepo = createMockUserRepository(); + const gateway = createMockGatewayService(); + const service = new SweegoWebhookService(userRepo, gateway); + + const mockUser = { + id: BigInt(124), + email: 'complaint@example.com', + emailBounced: false, + emailVerified: true, + suspiciousActivityFlags: 0, + toRow: () => ({}), + }; + (userRepo.findByEmail as ReturnType).mockResolvedValue(mockUser); + (userRepo.patchUpsert as ReturnType).mockResolvedValue(null); + + const event: SweegoEvent = { + event_type: 'complaint', + timestamp: '2026-01-29T14:21:46.729251+00:00', + recipient: 'complaint@example.com', + event_id: 'event-2', + }; + + await service.processEvent(event); + + expect(userRepo.findByEmail).toHaveBeenCalledWith('complaint@example.com'); + expect(userRepo.patchUpsert).toHaveBeenCalledWith( + mockUser.id, + { + email_bounced: true, + email_verified: false, + suspicious_activity_flags: SuspiciousActivityFlags.REQUIRE_REVERIFIED_EMAIL, + }, + mockUser.toRow(), + ); + }); + it('skips already bounced users', async () => { const userRepo = createMockUserRepository(); const gateway = createMockGatewayService();