class MailDeliverError extends Error { constructor( message: string, options: ErrorOptions, public readonly rawMail: string, ) { super(message, options); } } export class MailDeliverContext { constructor(public rawMail: string) {} } type MailDeliverHook = (context: Context) => Promise; export abstract class MailDeliverer { preHooks: MailDeliverHook[] = []; postHooks: MailDeliverHook[] = []; constructor(public readonly destination: string) {} protected abstract doPrepare(rawMail: string): Promise; protected abstract doDeliver(context: TContext): Promise; async deliver(rawMail: string): Promise { const context = await this.doPrepare(rawMail); for (const hook of this.preHooks) { await hook(context); } await this.doDeliver(context); for (const hook of this.postHooks) { await hook(context); } } protected throwError( reason: string, rawMail: string, cause?: unknown, ): never { throw new MailDeliverError( `Failed to deliver mail to ${this.destination}: ${reason}`, { cause }, rawMail, ); } }