/** * Sender blocklist checking with wildcard / glob support * * Uses picomatch for pattern matching (equivalent to Python's fnmatch). * Patterns are stored per-recipient in DynamoDB. */ import picomatch from 'picomatch'; import type { DynamoDBHandler } from '../aws/dynamodb.js'; import { log } from '../logger.js'; /** * Extract the bare email address from a From header value. * "John Doe " → "john@example.com" */ function extractAddress(sender: string): string { const match = sender.match(/<([^>]+)>/); const addr = match ? match[1] : sender; return addr.trim().toLowerCase(); } export class BlocklistChecker { constructor(private dynamodb: DynamoDBHandler) {} /** * Batch-check whether a sender is blocked for each recipient. * Uses a single batch DynamoDB call for efficiency. */ async batchCheckBlockedSenders( recipients: string[], sender: string, workerName: string, ): Promise> { const patternsByRecipient = await this.dynamodb.batchGetBlockedPatterns(recipients); const senderClean = extractAddress(sender); const result: Record = {}; for (const recipient of recipients) { const patterns = patternsByRecipient[recipient] ?? []; let isBlocked = false; for (const pattern of patterns) { if (picomatch.isMatch(senderClean, pattern.toLowerCase())) { log( `⛔ BLOCKED: Sender ${senderClean} matches pattern '${pattern}' ` + `for inbox ${recipient}`, 'WARNING', workerName, ); isBlocked = true; break; } } result[recipient] = isBlocked; } return result; } }