aboutsummaryrefslogtreecommitdiff
path: root/deno/mail
diff options
context:
space:
mode:
authorYuqian Yang <crupest@crupest.life>2025-07-01 17:29:07 +0800
committerYuqian Yang <crupest@crupest.life>2025-07-01 17:29:07 +0800
commitf4d60fb87f9c6d86a584eba11e6a3d17479af201 (patch)
tree37f1a7577e67acc06ddcfc918affb1ecd0df6038 /deno/mail
parent5e250ddcdbd6307989071e4ea794b1ad39e928e5 (diff)
downloadcrupest-f4d60fb87f9c6d86a584eba11e6a3d17479af201.tar.gz
crupest-f4d60fb87f9c6d86a584eba11e6a3d17479af201.tar.bz2
crupest-f4d60fb87f9c6d86a584eba11e6a3d17479af201.zip
mail: remove 'aws' of aws message id map.
Diffstat (limited to 'deno/mail')
-rw-r--r--deno/mail/app.ts2
-rw-r--r--deno/mail/aws/app.ts17
-rw-r--r--deno/mail/aws/deliver.ts12
-rw-r--r--deno/mail/aws/mail.ts59
-rw-r--r--deno/mail/db.test.ts8
-rw-r--r--deno/mail/db.ts36
-rw-r--r--deno/mail/dumb-smtp-server.ts4
-rw-r--r--deno/mail/mail.ts76
8 files changed, 108 insertions, 106 deletions
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);
+ }
+ }
+}