diff options
author | Yuqian Yang <crupest@crupest.life> | 2025-04-30 00:20:23 +0800 |
---|---|---|
committer | Yuqian Yang <crupest@crupest.life> | 2025-04-30 13:32:43 +0800 |
commit | a58326c5cbe9a73a6b204dac4b9f077d8cf886f4 (patch) | |
tree | d01c21a1b621cc9fd738a816c4a4c4138eecae07 | |
parent | 84148346e29547cfba92dd697be9bbf31a23a926 (diff) | |
download | crupest-a58326c5cbe9a73a6b204dac4b9f077d8cf886f4.tar.gz crupest-a58326c5cbe9a73a6b204dac4b9f077d8cf886f4.tar.bz2 crupest-a58326c5cbe9a73a6b204dac4b9f077d8cf886f4.zip |
HALF WORK!: 2025-4-30 2
8 files changed, 135 insertions, 99 deletions
diff --git a/services/docker/mail-server/aws-sendmail/db.ts b/services/docker/mail-server/aws-sendmail/db.ts index b7e052f..ced5c19 100644 --- a/services/docker/mail-server/aws-sendmail/db.ts +++ b/services/docker/mail-server/aws-sendmail/db.ts @@ -92,8 +92,8 @@ export class DbService { } async addMail(mail: Mail): Promise<void> { - const { raw, message_id, aws_message_id } = mail; - if (message_id == null) { + const { raw, messageId, awsMessageId } = mail; + if (messageId == null) { // TODO: Better error. throw new Error( "Failed to add mail to database. Mail has no message id.", @@ -101,8 +101,8 @@ export class DbService { } await this._db.insertInto(tableNames.mail.table).values({ raw, - message_id, - aws_message_id, + message_id: messageId, + aws_message_id: awsMessageId, }) .executeTakeFirstOrThrow(); } diff --git a/services/docker/mail-server/aws-sendmail/deliver.ts b/services/docker/mail-server/aws-sendmail/deliver.ts deleted file mode 100644 index e0c6e1c..0000000 --- a/services/docker/mail-server/aws-sendmail/deliver.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Mail } from "./mail.ts"; - -class MailDeliverError extends Error { - constructor( - message: string, - options: ErrorOptions, - public readonly mail: Mail, - ) { - super(message, options); - } -} - -type MailDeliverHook = (mail: Mail) => Promise<void>; - -export abstract class MailDeliverer { - preHooks: MailDeliverHook[] = []; - postHooks: MailDeliverHook[] = []; - - constructor(public readonly destination: string) {} - - protected doPrepare(_mail: Mail): Promise<void> { - return Promise.resolve(); - } - protected abstract doDeliver(mail: Mail): Promise<void>; - protected doFinalize(_mail: Mail): Promise<void> { - return Promise.resolve(); - } - - async deliverRaw(raw: string): Promise<void> { - const mail = new Mail(raw); - await this.deliver(mail); - } - - async deliver(mail: Mail): Promise<void> { - this.doPrepare(mail); - - for (const hook of this.preHooks) { - await hook(mail); - } - - await this.doDeliver(mail); - - for (const hook of this.postHooks) { - await hook(mail); - } - - await this.doFinalize(mail); - } - - protected throwError( - reason: string, - mail: Mail, - cause?: unknown, - ): never { - throw new MailDeliverError( - `Failed to deliver mail to ${this.destination}: ${reason}`, - { cause }, - mail, - ); - } -} diff --git a/services/docker/mail-server/aws-sendmail/delivers/aws.ts b/services/docker/mail-server/aws-sendmail/delivers/aws.ts index 9fe7bec..ecc9956 100644 --- a/services/docker/mail-server/aws-sendmail/delivers/aws.ts +++ b/services/docker/mail-server/aws-sendmail/delivers/aws.ts @@ -1,8 +1,7 @@ import { SendEmailCommand, SESv2Client } from "@aws-sdk/client-sesv2"; import { AwsContext } from "../aws.ts"; -import { Mail } from "../mail.ts"; -import { MailDeliverer } from "../deliver.ts"; +import { Mail, MailDeliverer } from "../mail.ts"; export class AwsMailDeliverer extends MailDeliverer { private _ses; @@ -16,16 +15,28 @@ export class AwsMailDeliverer extends MailDeliverer { } protected override async doDeliver(mail: Mail): Promise<void> { - const sendCommand = new SendEmailCommand({ - Content: { - Raw: { Data: mail.encodeUtf8() }, - }, - }); - - const res = await this._ses.send(sendCommand); - if (res.MessageId == null) { - throw Error("No message id is returned from aws."); + let awsMessageId: string | undefined; + + try { + const sendCommand = new SendEmailCommand({ + Content: { + Raw: { Data: mail.encodeUtf8() }, + }, + }); + + const res = await this._ses.send(sendCommand); + awsMessageId = res.MessageId; + } catch (cause) { + mail.throwDeliverError( + this, + "failed to call send-email api of aws.", + cause, + ); + } + + if (awsMessageId == null) { + mail.setDelivered(this, new Error("No message id is returned from aws.")); } - mail.aws_message_id = res.MessageId; + mail.awsMessageId = awsMessageId ?? null; } } diff --git a/services/docker/mail-server/aws-sendmail/delivers/dovecot.ts b/services/docker/mail-server/aws-sendmail/delivers/dovecot.ts index 2b35872..1b8c911 100644 --- a/services/docker/mail-server/aws-sendmail/delivers/dovecot.ts +++ b/services/docker/mail-server/aws-sendmail/delivers/dovecot.ts @@ -1,6 +1,5 @@ import { getLogger } from "../logger.ts"; -import { MailDeliverer } from "../deliver.ts"; -import { Mail } from "../mail.ts"; +import { Mail, MailDeliverer } from "../mail.ts"; export class DovecotMailDeliverer extends MailDeliverer { constructor() { @@ -31,11 +30,11 @@ export class DovecotMailDeliverer extends MailDeliverer { status = await ldaProcess.status; } catch (cause) { - this.throwError("external error.", mail, cause); + mail.throwDeliverError(this, "external error.", cause); } if (!status.success) { - this.throwError(`${ldaBin} exited with non-zero.`, mail); + mail.throwDeliverError(this, `${ldaBin} exited with non-zero.`); } } } diff --git a/services/docker/mail-server/aws-sendmail/delivers/traffic.ts b/services/docker/mail-server/aws-sendmail/delivers/traffic.ts deleted file mode 100644 index 3d567f9..0000000 --- a/services/docker/mail-server/aws-sendmail/delivers/traffic.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { MailDeliverer } from "../deliver.ts"; -import { DovecotMailDeliverer } from "./dovecot.ts"; -import { AwsContext } from "../aws.ts"; -import { AwsMailDeliverer } from "./aws.ts"; - -export class MailTrafficDeliverer { - receiver: MailDeliverer; - sender: MailDeliverer; - - constructor(aws: AwsContext) { - this.receiver = new DovecotMailDeliverer(); - this.sender = new AwsMailDeliverer(aws); - } -} diff --git a/services/docker/mail-server/aws-sendmail/mail.ts b/services/docker/mail-server/aws-sendmail/mail.ts index e673593..de7942f 100644 --- a/services/docker/mail-server/aws-sendmail/mail.ts +++ b/services/docker/mail-server/aws-sendmail/mail.ts @@ -1,12 +1,57 @@ import { encodeBase64 } from "@std/encoding/base64"; import { getLogger } from "./logger.ts"; +export type MailDeliverStateNotSent = { kind: "not-sent" }; +export type MailDeliverStateDelivered = { + kind: "delivered"; + deliverer: MailDeliverer; + error?: Error; +}; +export type MailDeliverStateError = { + kind: "error"; + deliverer: MailDeliverer; + error: Error; +}; +export type MailDeliverState = + | MailDeliverStateNotSent + | MailDeliverStateDelivered + | MailDeliverStateError; + +class MailDeliverError extends Error { + constructor( + message: string, + options: ErrorOptions, + public readonly mail: Mail, + ) { + super(message, options); + } +} + export class Mail { - message_id: string | null = null; - aws_message_id: string | null = null; + messageId: string | null = null; + awsMessageId: string | null = null; + deliverState: MailDeliverState = { kind: "not-sent" }; constructor(public raw: string) {} + setDelivered(deliverer: MailDeliverer, error?: Error) { + this.deliverState = { "kind": "delivered", deliverer, error }; + } + + throwDeliverError( + deliverer: MailDeliverer, + reason: string, + cause?: unknown, + ): never { + const error = new MailDeliverError( + `Failed to deliver mail to ${deliverer.destination}: ${reason}`, + { cause }, + this, + ); + this.deliverState = { "kind": "error", deliverer, error }; + throw error; + } + encodeUtf8(): Uint8Array { const utf8Encoder = new TextEncoder(); // TODO: A problem here is if mail is VERY long, this will block for a long time. @@ -40,3 +85,45 @@ export class Mail { rawMail.slice(endOfHeadersIndex); } } + +type MailDeliverHook = (mail: Mail) => Promise<void>; + +export abstract class MailDeliverer { + preHooks: MailDeliverHook[] = []; + postHooks: MailDeliverHook[] = []; + + constructor(public readonly destination: string) {} + + protected doPrepare(_mail: Mail): Promise<void> { + return Promise.resolve(); + } + protected abstract doDeliver(mail: Mail): Promise<void>; + protected doFinalize(_mail: Mail): Promise<void> { + return Promise.resolve(); + } + + async deliverRaw(raw: string): Promise<void> { + const mail = new Mail(raw); + await this.deliver(mail); + } + + async deliver(mail: Mail): Promise<void> { + this.doPrepare(mail); + + for (const hook of this.preHooks) { + await hook(mail); + } + + await this.doDeliver(mail); + + if (mail.deliverState.kind === "not-sent") { + mail.setDelivered(this); + } + + for (const hook of this.postHooks) { + await hook(mail); + } + + await this.doFinalize(mail); + } +} diff --git a/services/docker/mail-server/aws-sendmail/main.ts b/services/docker/mail-server/aws-sendmail/main.ts index 98e364e..bb933b1 100644 --- a/services/docker/mail-server/aws-sendmail/main.ts +++ b/services/docker/mail-server/aws-sendmail/main.ts @@ -1,5 +1,5 @@ import { AwsContext } from "./aws.ts"; -import { MailTrafficDeliverer } from "./delivers/traffic.ts"; +import { MailTrafficHandler } from "./traffic.ts"; import { Logger, setLogger } from "./logger.ts"; class BugError extends Error { @@ -10,7 +10,7 @@ function warn(message: string) { class App { readonly aws = new AwsContext(); - readonly mailTrafficDeliverer = new MailTrafficDeliverer(this.aws); + readonly mailTrafficDeliverer = new MailTrafficHandler(this.aws); constructor() { setLogger(new Logger("log")); diff --git a/services/docker/mail-server/aws-sendmail/traffic.ts b/services/docker/mail-server/aws-sendmail/traffic.ts new file mode 100644 index 0000000..87e6dee --- /dev/null +++ b/services/docker/mail-server/aws-sendmail/traffic.ts @@ -0,0 +1,14 @@ +import { MailDeliverer } from "./deliver.ts"; +import { DovecotMailDeliverer } from "./delivers/dovecot.ts"; +import { AwsContext } from "./aws.ts"; +import { AwsMailDeliverer } from "./delivers/aws.ts"; + +export class MailTrafficHandler { + receiver: MailDeliverer; + sender: MailDeliverer; + + constructor(aws: AwsContext) { + this.receiver = new DovecotMailDeliverer(); + this.sender = new AwsMailDeliverer(aws); + } +} |