import * as plugins from '../../plugins.js'; import { CachedDocument, TTL } from '../classes.cached.document.js'; import { CacheDb } from '../classes.cachedb.js'; /** * Email status in the cache */ export type TCachedEmailStatus = 'pending' | 'processing' | 'delivered' | 'failed' | 'deferred'; /** * Helper to get the smartdata database instance */ const getDb = () => CacheDb.getInstance().getDb(); /** * CachedEmail - Stores email queue items in the cache * * Used for persistent email queue storage, tracking delivery status, * and maintaining email history for the configured TTL period. */ @plugins.smartdata.Collection(() => getDb()) export class CachedEmail extends CachedDocument { /** * Unique identifier for this email */ @plugins.smartdata.unI() @plugins.smartdata.svDb() public id: string; /** * Email message ID (RFC 822 Message-ID header) */ @plugins.smartdata.svDb() public messageId: string; /** * Sender email address (envelope from) */ @plugins.smartdata.svDb() public from: string; /** * Recipient email addresses */ @plugins.smartdata.svDb() public to: string[]; /** * CC recipients */ @plugins.smartdata.svDb() public cc: string[]; /** * BCC recipients */ @plugins.smartdata.svDb() public bcc: string[]; /** * Email subject */ @plugins.smartdata.svDb() public subject: string; /** * Raw RFC822 email content */ @plugins.smartdata.svDb() public rawContent: string; /** * Current status of the email */ @plugins.smartdata.svDb() public status: TCachedEmailStatus; /** * Number of delivery attempts */ @plugins.smartdata.svDb() public attempts: number = 0; /** * Maximum number of delivery attempts */ @plugins.smartdata.svDb() public maxAttempts: number = 3; /** * Timestamp for next delivery attempt */ @plugins.smartdata.svDb() public nextAttempt: Date; /** * Last error message if delivery failed */ @plugins.smartdata.svDb() public lastError: string; /** * Timestamp when the email was successfully delivered */ @plugins.smartdata.svDb() public deliveredAt: Date; /** * Sender domain (for querying/filtering) */ @plugins.smartdata.svDb() public senderDomain: string; /** * Priority level (higher = more important) */ @plugins.smartdata.svDb() public priority: number = 0; /** * JSON-serialized route data */ @plugins.smartdata.svDb() public routeData: string; /** * DKIM signature status */ @plugins.smartdata.svDb() public dkimSigned: boolean = false; constructor() { super(); this.setTTL(TTL.DAYS_30); // Default 30-day TTL this.status = 'pending'; this.to = []; this.cc = []; this.bcc = []; } /** * Create a new CachedEmail with a unique ID */ public static createNew(): CachedEmail { const email = new CachedEmail(); email.id = plugins.uuid.v4(); return email; } /** * Find an email by ID */ public static async findById(id: string): Promise { return await CachedEmail.getInstance({ id, }); } /** * Find all emails with a specific status */ public static async findByStatus(status: TCachedEmailStatus): Promise { return await CachedEmail.getInstances({ status, }); } /** * Find all emails pending delivery (status = pending and nextAttempt <= now) */ public static async findPendingForDelivery(): Promise { const now = new Date(); return await CachedEmail.getInstances({ status: 'pending', nextAttempt: { $lte: now }, }); } /** * Find emails by sender domain */ public static async findBySenderDomain(domain: string): Promise { return await CachedEmail.getInstances({ senderDomain: domain, }); } /** * Mark as delivered */ public markDelivered(): void { this.status = 'delivered'; this.deliveredAt = new Date(); } /** * Mark as failed with error */ public markFailed(error: string): void { this.status = 'failed'; this.lastError = error; } /** * Increment attempt counter and schedule next attempt */ public scheduleRetry(delayMs: number = 5 * 60 * 1000): void { this.attempts++; this.status = 'deferred'; this.nextAttempt = new Date(Date.now() + delayMs); // If max attempts reached, mark as failed if (this.attempts >= this.maxAttempts) { this.status = 'failed'; this.lastError = `Max attempts (${this.maxAttempts}) reached`; } } /** * Extract sender domain from email address */ public updateSenderDomain(): void { if (this.from) { const match = this.from.match(/@([^>]+)>?$/); if (match) { this.senderDomain = match[1].toLowerCase(); } } } }