diff options
Diffstat (limited to 'services/docker/mail-server/aws-sendmail')
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); +  } +} | 
