231 lines
4.8 KiB
TypeScript
231 lines
4.8 KiB
TypeScript
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<CachedEmail> {
|
|
/**
|
|
* 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<CachedEmail | null> {
|
|
return await CachedEmail.getInstance({
|
|
id,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Find all emails with a specific status
|
|
*/
|
|
public static async findByStatus(status: TCachedEmailStatus): Promise<CachedEmail[]> {
|
|
return await CachedEmail.getInstances({
|
|
status,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Find all emails pending delivery (status = pending and nextAttempt <= now)
|
|
*/
|
|
public static async findPendingForDelivery(): Promise<CachedEmail[]> {
|
|
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<CachedEmail[]> {
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|