fix(email): handle sweego complaints as hard bounces

This commit is contained in:
Hampus Kraft 2026-02-19 19:15:01 +00:00
parent 61984cd1a6
commit 3cc07f5e9f
No known key found for this signature in database
GPG Key ID: 6090864C465A454D
2 changed files with 42 additions and 5 deletions

View File

@ -126,13 +126,13 @@ export class SweegoWebhookService {
}
async processEvent(event: SweegoEvent): Promise<void> {
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<void> {
private async handleHardBounceOrComplaint(event: SweegoEvent): Promise<void> {
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);

View File

@ -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<typeof vi.fn>).mockResolvedValue(mockUser);
(userRepo.patchUpsert as ReturnType<typeof vi.fn>).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();