new structure

This commit is contained in:
Andreas Knuth 2026-03-07 15:16:14 -06:00
parent a70ae78a93
commit d1426afec5
18 changed files with 3233 additions and 33 deletions

3190
email-worker-nodejs/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -26,15 +26,15 @@ export class BlocklistChecker {
* Batch-check whether a sender is blocked for each recipient. * Batch-check whether a sender is blocked for each recipient.
* Uses a single batch DynamoDB call for efficiency. * Uses a single batch DynamoDB call for efficiency.
*/ */
async batchCheckBlockedSenders( async batchCheckBlockedSenders(
recipients: string[], recipients: string[],
sender: string, senders: string[], // <-- Geändert zu Array
workerName: string, workerName: string,
): Promise<Record<string, boolean>> { ): Promise<Record<string, boolean>> {
const patternsByRecipient = const patternsByRecipient = await this.dynamodb.batchGetBlockedPatterns(recipients);
await this.dynamodb.batchGetBlockedPatterns(recipients);
const senderClean = extractAddress(sender); // Alle übergebenen Adressen bereinigen
const sendersClean = senders.map(s => extractAddress(s)).filter(Boolean);
const result: Record<string, boolean> = {}; const result: Record<string, boolean> = {};
for (const recipient of recipients) { for (const recipient of recipients) {
@ -42,10 +42,10 @@ export class BlocklistChecker {
let isBlocked = false; let isBlocked = false;
for (const pattern of patterns) { for (const pattern of patterns) {
for (const senderClean of sendersClean) {
if (picomatch.isMatch(senderClean, pattern.toLowerCase())) { if (picomatch.isMatch(senderClean, pattern.toLowerCase())) {
log( log(
`⛔ BLOCKED: Sender ${senderClean} matches pattern '${pattern}' ` + `⛔ BLOCKED: Sender ${senderClean} matches pattern '${pattern}' for inbox ${recipient}`,
`for inbox ${recipient}`,
'WARNING', 'WARNING',
workerName, workerName,
); );
@ -53,10 +53,10 @@ export class BlocklistChecker {
break; break;
} }
} }
if (isBlocked) break;
}
result[recipient] = isBlocked; result[recipient] = isBlocked;
} }
return result; return result;
} }
} }

View File

@ -9,13 +9,13 @@
import { createTransport } from 'nodemailer'; import { createTransport } from 'nodemailer';
import type { ParsedMail } from 'mailparser'; import type { ParsedMail } from 'mailparser';
import type { DynamoDBHandler, EmailRule } from '../aws/dynamodb.js';
import type { SESHandler } from '../aws/ses.js'; import type { SESHandler } from '../aws/ses.js';
import { extractBodyParts } from './parser.js'; import { extractBodyParts } from './parser.js';
import { config, isInternalAddress } from '../config.js';
import { log } from '../logger.js'; import { log } from '../logger.js';
// Wir nutzen MailComposer direkt für das Erstellen der Raw Bytes // Wir nutzen MailComposer direkt für das Erstellen der Raw Bytes
import MailComposer from 'nodemailer/lib/mail-composer/index.js'; import MailComposer from 'nodemailer/lib/mail-composer/index.js';
import { DynamoDBHandler, EmailRule } from '../aws/dynamodb.js';
import { config, isInternalAddress } from '../config.js';
export type MetricsCallback = (action: 'autoreply' | 'forward', domain: string) => void; export type MetricsCallback = (action: 'autoreply' | 'forward', domain: string) => void;

View File

@ -13,7 +13,7 @@ import { config, loadDomains } from './config.js';
import { log } from './logger.js'; import { log } from './logger.js';
import { startMetricsServer, type MetricsCollector } from './metrics.js'; import { startMetricsServer, type MetricsCollector } from './metrics.js';
import { startHealthServer } from './health.js'; import { startHealthServer } from './health.js';
import { UnifiedWorker } from './worker/index.js'; import { UnifiedWorker } from './worker/unified-worker.js';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Banner // Banner

View File

@ -8,8 +8,9 @@
*/ */
import { createTransport, type Transporter } from 'nodemailer'; import { createTransport, type Transporter } from 'nodemailer';
import { config } from '../config.js';
import { log } from '../logger.js'; import { log } from '../logger.js';
import { config } from '../config.js';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Permanent error detection // Permanent error detection

View File

@ -9,9 +9,9 @@
*/ */
import type { SQSHandler } from '../aws/sqs.js'; import type { SQSHandler } from '../aws/sqs.js';
import type { MessageProcessor } from './message-processor.js';
import type { MetricsCollector } from '../metrics.js'; import type { MetricsCollector } from '../metrics.js';
import { log } from '../logger.js'; import { log } from '../logger.js';
import { MessageProcessor } from './message-processor.js';
export interface DomainPollerStats { export interface DomainPollerStats {
domain: string; domain: string;

View File

@ -19,15 +19,12 @@ import type { SESHandler } from '../aws/ses.js';
import type { DynamoDBHandler } from '../aws/dynamodb.js'; import type { DynamoDBHandler } from '../aws/dynamodb.js';
import type { EmailDelivery } from '../smtp/delivery.js'; import type { EmailDelivery } from '../smtp/delivery.js';
import type { MetricsCollector } from '../metrics.js'; import type { MetricsCollector } from '../metrics.js';
import {
parseEmail,
isProcessedByWorker,
BounceHandler,
RulesProcessor,
BlocklistChecker,
} from '../email/index.js';
import { domainToBucketName } from '../config.js';
import { log } from '../logger.js'; import { log } from '../logger.js';
import { BlocklistChecker } from '../email/blocklist.js';
import { BounceHandler } from '../email/bounce-handler.js';
import { parseEmail, isProcessedByWorker } from '../email/parser.js';
import { RulesProcessor } from '../email/rules-processor.js';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Processor // Processor
@ -182,9 +179,17 @@ export class MessageProcessor {
} }
// 6. BLOCKLIST CHECK // 6. BLOCKLIST CHECK
const sendersToCheck: string[] = [];
if (fromAddrFinal) sendersToCheck.push(fromAddrFinal);
const headerFrom = parsedFinal?.from?.text;
if (headerFrom && !sendersToCheck.includes(headerFrom)) {
sendersToCheck.push(headerFrom);
}
const blockedByRecipient = await this.blocklist.batchCheckBlockedSenders( const blockedByRecipient = await this.blocklist.batchCheckBlockedSenders(
recipients, recipients,
fromAddrFinal, sendersToCheck, // <-- Array übergeben
workerName, workerName,
); );

View File

@ -8,13 +8,17 @@
* - Graceful shutdown * - Graceful shutdown
*/ */
import { S3Handler, SQSHandler, SESHandler, DynamoDBHandler } from '../aws/index.js'; import { DynamoDBHandler } from '../aws/dynamodb';
import { EmailDelivery } from '../smtp/index.js'; import { S3Handler} from '../aws/s3.js';
import { SQSHandler} from '../aws/sqs.js'
import { SESHandler } from '../aws/ses';
import { EmailDelivery } from '../smtp/delivery.js';
import { MessageProcessor } from './message-processor.js'; import { MessageProcessor } from './message-processor.js';
import { DomainPoller, type DomainPollerStats } from './domain-poller.js'; import { DomainPoller, type DomainPollerStats } from './domain-poller.js';
import type { MetricsCollector } from '../metrics.js'; import type { MetricsCollector } from '../metrics.js';
import { log } from '../logger.js'; import { log } from '../logger.js';
export class UnifiedWorker { export class UnifiedWorker {
private pollers: DomainPoller[] = []; private pollers: DomainPoller[] = [];
private processor: MessageProcessor; private processor: MessageProcessor;