diff options
| author | Yuqian Yang <crupest@crupest.life> | 2025-07-01 17:29:07 +0800 | 
|---|---|---|
| committer | Yuqian Yang <crupest@crupest.life> | 2025-07-01 17:29:07 +0800 | 
| commit | f4d60fb87f9c6d86a584eba11e6a3d17479af201 (patch) | |
| tree | 37f1a7577e67acc06ddcfc918affb1ecd0df6038 /deno | |
| parent | 5e250ddcdbd6307989071e4ea794b1ad39e928e5 (diff) | |
| download | crupest-f4d60fb87f9c6d86a584eba11e6a3d17479af201.tar.gz crupest-f4d60fb87f9c6d86a584eba11e6a3d17479af201.tar.bz2 crupest-f4d60fb87f9c6d86a584eba11e6a3d17479af201.zip | |
mail: remove 'aws' of aws message id map.
Diffstat (limited to 'deno')
| -rw-r--r-- | deno/base/lib.ts | 2 | ||||
| -rw-r--r-- | deno/mail/app.ts | 2 | ||||
| -rw-r--r-- | deno/mail/aws/app.ts | 17 | ||||
| -rw-r--r-- | deno/mail/aws/deliver.ts | 12 | ||||
| -rw-r--r-- | deno/mail/aws/mail.ts | 59 | ||||
| -rw-r--r-- | deno/mail/db.test.ts | 8 | ||||
| -rw-r--r-- | deno/mail/db.ts | 36 | ||||
| -rw-r--r-- | deno/mail/dumb-smtp-server.ts | 4 | ||||
| -rw-r--r-- | deno/mail/mail.ts | 76 | 
9 files changed, 109 insertions, 107 deletions
| diff --git a/deno/base/lib.ts b/deno/base/lib.ts index 3c69e0a..af75115 100644 --- a/deno/base/lib.ts +++ b/deno/base/lib.ts @@ -3,7 +3,7 @@ function camelCaseToKebabCase(str: string): string {  }  function prependNonEmpty<T>( -  object: T, +  object: T | null | undefined,    prefix: string = " ",  ): string {    if (object == null) return ""; diff --git a/deno/mail/app.ts b/deno/mail/app.ts index 332c430..2a8c78a 100644 --- a/deno/mail/app.ts +++ b/deno/mail/app.ts @@ -49,7 +49,7 @@ export function createHono(outbound: MailDeliverer, inbound: MailDeliverer) {      } else {        const result = await outbound.deliverRaw(body);        return context.json({ -        awsMessageId: result.awsMessageId, +        newMessageId: result.newMessageId,        });      }    }); diff --git a/deno/mail/aws/app.ts b/deno/mail/aws/app.ts index 6c7ede7..7e16488 100644 --- a/deno/mail/aws/app.ts +++ b/deno/mail/aws/app.ts @@ -13,14 +13,10 @@ import { DbService } from "../db.ts";  import { createHono, createInbound, createSmtp, sendMail } from "../app.ts";  import { DovecotMailDeliverer } from "../dovecot.ts";  import { MailDeliverer } from "../mail.ts"; -import { -  AwsMailMessageIdRewriteHook, -  AwsMailMessageIdSaveHook, -} from "./mail.ts"; +import { MessageIdRewriteHook, MessageIdSaveHook } from "../mail.ts";  import { AwsMailDeliverer } from "./deliver.ts";  import { AwsMailFetcher, LiveMailNotFoundError } from "./fetch.ts"; -  const PREFIX = "crupest-mail-server";  const CONFIG_DEFINITIONS = {    dataPath: { @@ -105,12 +101,12 @@ function createOutbound(  ) {    const deliverer = new AwsMailDeliverer(awsOptions);    deliverer.preHooks.push( -    new AwsMailMessageIdRewriteHook(db.messageIdToAws.bind(db)), +    new MessageIdRewriteHook(db.messageIdToNew.bind(db)),    );    deliverer.postHooks.push( -    new AwsMailMessageIdSaveHook( -      async (original, aws, context) => { -        await db.addMessageIdMap({ message_id: original, aws_message_id: aws }); +    new MessageIdSaveHook( +      async (original, new_message_id, context) => { +        await db.addMessageIdMap({ message_id: original, new_message_id });          void local?.saveNewSent(context.logTag, context.mail, original);        },      ), @@ -240,7 +236,8 @@ function createServerServices() {  }  async function serve(cron: boolean = false) { -  const { config, fetcher, inbound, smtp, dbService, hono } = createServerServices(); +  const { config, fetcher, inbound, smtp, dbService, hono } = +    createServerServices();    await dbService.migrate(); diff --git a/deno/mail/aws/deliver.ts b/deno/mail/aws/deliver.ts index 0195369..37a871d 100644 --- a/deno/mail/aws/deliver.ts +++ b/deno/mail/aws/deliver.ts @@ -6,12 +6,6 @@ import {  import { Mail, MailDeliverContext, MailDeliverer } from "../mail.ts"; -declare module "../mail.ts" { -  interface MailDeliverResult { -    awsMessageId?: string; -  } -} -  export class AwsMailDeliverer extends MailDeliverer {    readonly name = "aws";    readonly #aws; @@ -42,12 +36,12 @@ export class AwsMailDeliverer extends MailDeliverer {            "AWS send-email returned null message id.",          );        } else { -        context.result.awsMessageId = +        context.result.newMessageId =            `${res.MessageId}@${this.#aws.region}.amazonses.com`;        } -      context.result.smtpMessage = -        `AWS Message ID: ${context.result.awsMessageId}`; +      context.result.messageForSmtp = +        `AWS Message ID: ${context.result.newMessageId}`;        context.result.recipients.set("*", {          kind: "success",          message: `Succeeded to call aws send-email api.`, diff --git a/deno/mail/aws/mail.ts b/deno/mail/aws/mail.ts deleted file mode 100644 index 26f3ea0..0000000 --- a/deno/mail/aws/mail.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { MailDeliverContext, MailDeliverHook } from "../mail.ts"; - -export class AwsMailMessageIdRewriteHook implements MailDeliverHook { -  readonly #lookup; - -  constructor(lookup: (origin: string) => Promise<string | null>) { -    this.#lookup = lookup; -  } - -  async callback(context: MailDeliverContext): Promise<void> { -    const addresses = context.mail.simpleFindAllAddresses(); -    for (const address of addresses) { -      const awsMessageId = await this.#lookup(address); -      if (awsMessageId != null && awsMessageId.length !== 0) { -        console.info( -          context.logTag, -          `Rewrite address-line string in mail: ${address} => ${awsMessageId}.`, -        ); -        context.mail.raw = context.mail.raw.replaceAll(address, awsMessageId); -      } -    } -  } -} - -export class AwsMailMessageIdSaveHook implements MailDeliverHook { -  readonly #record; - -  constructor( -    record: ( -      original: string, -      aws: string, -      context: MailDeliverContext, -    ) => Promise<void>, -  ) { -    this.#record = record; -  } - -  async callback(context: MailDeliverContext): Promise<void> { -    const { messageId } = context.mail.parsed; -    if (messageId == null) { -      console.warn( -        context.logTag, -        "Original mail doesn't have message id, skip saving message id map.", -      ); -      return; -    } -    if (context.result.awsMessageId != null) { -      console.info( -        context.logTag, -        `Save message id map: ${messageId} => ${context.result.awsMessageId}.`, -      ); -      context.mail.raw = context.mail.raw.replaceAll( -        messageId, -        context.result.awsMessageId, -      ); -      await this.#record(messageId, context.result.awsMessageId, context); -    } -  } -} diff --git a/deno/mail/db.test.ts b/deno/mail/db.test.ts index 60035c4..8a9ad27 100644 --- a/deno/mail/db.test.ts +++ b/deno/mail/db.test.ts @@ -6,17 +6,17 @@ import { DbService } from "./db.ts";  describe("DbService", () => {    const mockRow = {      message_id: "mock-message-id@mock.mock", -    aws_message_id: "mock-aws-message-id@mock.mock", +    new_message_id: "mock-new-message-id@mock.mock",    };    it("works", async () => {      const db = new DbService(":memory:");      await db.migrate();      await db.addMessageIdMap(mockRow); -    expect(await db.messageIdToAws(mockRow.message_id)).toBe( -      mockRow.aws_message_id, +    expect(await db.messageIdToNew(mockRow.message_id)).toBe( +      mockRow.new_message_id,      ); -    expect(await db.messageIdFromAws(mockRow.aws_message_id)).toBe( +    expect(await db.messageIdFromNew(mockRow.new_message_id)).toBe(        mockRow.message_id,      );    }); diff --git a/deno/mail/db.ts b/deno/mail/db.ts index 062700b..e41f762 100644 --- a/deno/mail/db.ts +++ b/deno/mail/db.ts @@ -53,14 +53,14 @@ class SqliteDatabaseAdapter implements SqliteDatabase {  export class DbError extends Error {} -interface AwsMessageIdMapTable { +interface MessageIdMapTable {    id: Generated<number>;    message_id: string; -  aws_message_id: string; +  new_message_id: string;  }  interface Database { -  aws_message_id_map: AwsMessageIdMapTable; +  message_id_map: MessageIdMapTable;  }  const migrations: Record<string, Migration> = { @@ -68,16 +68,16 @@ const migrations: Record<string, Migration> = {      // deno-lint-ignore no-explicit-any      async up(db: Kysely<any>): Promise<void> {        await db.schema -        .createTable("aws_message_id_map") +        .createTable("message_id_map")          .addColumn("id", "integer", (col) => col.primaryKey().autoIncrement())          .addColumn("message_id", "text", (col) => col.notNull().unique()) -        .addColumn("aws_message_id", "text", (col) => col.notNull().unique()) +        .addColumn("new_message_id", "text", (col) => col.notNull().unique())          .execute(); -      for (const column of ["message_id", "aws_message_id"]) { +      for (const column of ["message_id", "new_message_id"]) {          await db.schema -          .createIndex(`aws_message_id_map_${column}`) -          .on("aws_message_id_map") +          .createIndex(`message_id_map_${column}`) +          .on("message_id_map")            .column(column)            .execute();        } @@ -85,7 +85,7 @@ const migrations: Record<string, Migration> = {      // deno-lint-ignore no-explicit-any      async down(db: Kysely<any>): Promise<void> { -      await db.schema.dropTable("aws_message_id_map").execute(); +      await db.schema.dropTable("message_id_map").execute();      },    },  }; @@ -117,28 +117,28 @@ export class DbService {    }    async addMessageIdMap( -    mail: Insertable<AwsMessageIdMapTable>, +    mail: Insertable<MessageIdMapTable>,    ): Promise<number> {      const inserted = await this.#kysely -      .insertInto("aws_message_id_map") +      .insertInto("message_id_map")        .values(mail)        .executeTakeFirstOrThrow();      return Number(inserted.insertId!);    } -  async messageIdToAws(messageId: string): Promise<string | null> { +  async messageIdToNew(messageId: string): Promise<string | null> {      const row = await this.#kysely -      .selectFrom("aws_message_id_map") +      .selectFrom("message_id_map")        .where("message_id", "=", messageId) -      .select("aws_message_id") +      .select("new_message_id")        .executeTakeFirst(); -    return row?.aws_message_id ?? null; +    return row?.new_message_id ?? null;    } -  async messageIdFromAws(awsMessageId: string): Promise<string | null> { +  async messageIdFromNew(newMessageId: string): Promise<string | null> {      const row = await this.#kysely -      .selectFrom("aws_message_id_map") -      .where("aws_message_id", "=", awsMessageId) +      .selectFrom("message_id_map") +      .where("new_message_id", "=", newMessageId)        .select("message_id")        .executeTakeFirst();      return row?.message_id ?? null; diff --git a/deno/mail/dumb-smtp-server.ts b/deno/mail/dumb-smtp-server.ts index 70d5ec0..c3ebf5d 100644 --- a/deno/mail/dumb-smtp-server.ts +++ b/deno/mail/dumb-smtp-server.ts @@ -91,8 +91,8 @@ export class DumbSmtpServer {            if (line === ".") {              try {                console.info(logTag, "Mail data received, begin to relay..."); -              const { smtpMessage } = await this.#deliverer.deliverRaw(rawMail); -              await send(`250 2.6.0 ${smtpMessage}`); +              const result = await this.#deliverer.deliverRaw(rawMail); +              await send(`250 2.6.0 ${result.generateMessageForSmtp()}`);                rawMail = null;              } catch (err) {                console.error(logTag, "Relay failed.", err); diff --git a/deno/mail/mail.ts b/deno/mail/mail.ts index 9cc591c..b88ce2b 100644 --- a/deno/mail/mail.ts +++ b/deno/mail/mail.ts @@ -1,5 +1,8 @@  import { encodeBase64 } from "@std/encoding/base64";  import { parse } from "@std/csv/parse"; + +import { StringUtils } from "@crupest/base"; +  import { simpleParseMail } from "./mail-parsing.ts";  export class Mail { @@ -47,7 +50,9 @@ export interface MailDeliverRecipientResult {  export class MailDeliverResult {    message?: string; -  smtpMessage?: string; +  messageForSmtp?: string; +  newMessageId?: string; +    recipients = new Map<string, MailDeliverRecipientResult>();    constructor(public mail: Mail) {} @@ -58,8 +63,8 @@ export class MailDeliverResult {    generateLogMessage(prefix: string) {      const lines = [];      if (this.message != null) lines.push(`${prefix} message: ${this.message}`); -    if (this.smtpMessage != null) { -      lines.push(`${prefix} smtpMessage: ${this.smtpMessage}`); +    if (this.messageForSmtp != null) { +      lines.push(`${prefix} smtpMessage: ${this.messageForSmtp}`);      }      for (const [name, result] of this.recipients.entries()) {        const { kind, message } = result; @@ -67,6 +72,13 @@ export class MailDeliverResult {      }      return lines.join("\n");    } + +  generateMessageForSmtp(): string { +    if (this.messageForSmtp != null) return this.messageForSmtp; +    return `2.0.0 OK${ +      StringUtils.prependNonEmpty(this.newMessageId) +    } Message accepted for delivery`; +  }  }  export class MailDeliverContext { @@ -232,3 +244,61 @@ export class AliasRecipientMailHook implements MailDeliverHook {      }    }  } + +export class MessageIdRewriteHook implements MailDeliverHook { +  readonly #lookup; + +  constructor(lookup: (origin: string) => Promise<string | null>) { +    this.#lookup = lookup; +  } + +  async callback(context: MailDeliverContext): Promise<void> { +    const addresses = context.mail.simpleFindAllAddresses(); +    for (const address of addresses) { +      const newMessageId = await this.#lookup(address); +      if (newMessageId != null && newMessageId.length !== 0) { +        console.info( +          context.logTag, +          `Rewrite address-line string in mail: ${address} => ${newMessageId}.`, +        ); +        context.mail.raw = context.mail.raw.replaceAll(address, newMessageId); +      } +    } +  } +} + +export class MessageIdSaveHook implements MailDeliverHook { +  readonly #record; + +  constructor( +    record: ( +      original: string, +      newMessageId: string, +      context: MailDeliverContext, +    ) => Promise<void>, +  ) { +    this.#record = record; +  } + +  async callback(context: MailDeliverContext): Promise<void> { +    const { messageId } = context.mail.parsed; +    if (messageId == null) { +      console.warn( +        context.logTag, +        "Original mail doesn't have message id, skip saving message id map.", +      ); +      return; +    } +    if (context.result.newMessageId != null) { +      console.info( +        context.logTag, +        `Save message id map: ${messageId} => ${context.result.newMessageId}.`, +      ); +      context.mail.raw = context.mail.raw.replaceAll( +        messageId, +        context.result.newMessageId, +      ); +      await this.#record(messageId, context.result.newMessageId, context); +    } +  } +} | 
