aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/docker/mail-server/aws-sendmail/db.ts8
-rw-r--r--services/docker/mail-server/aws-sendmail/deliver.ts61
-rw-r--r--services/docker/mail-server/aws-sendmail/delivers/aws.ts35
-rw-r--r--services/docker/mail-server/aws-sendmail/delivers/dovecot.ts7
-rw-r--r--services/docker/mail-server/aws-sendmail/delivers/traffic.ts14
-rw-r--r--services/docker/mail-server/aws-sendmail/mail.ts91
-rw-r--r--services/docker/mail-server/aws-sendmail/main.ts4
-rw-r--r--services/docker/mail-server/aws-sendmail/traffic.ts14
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);
+ }
+}