aboutsummaryrefslogtreecommitdiff
path: root/deno/mail-relay/mail.ts
diff options
context:
space:
mode:
authorYuqian Yang <crupest@crupest.life>2025-06-30 14:25:02 +0800
committerYuqian Yang <crupest@crupest.life>2025-06-30 14:25:02 +0800
commite18d101cae1dfcef29abd102d2908d429f4688d5 (patch)
tree49c0b1d1c237c674fe603db23d2e174acdea6979 /deno/mail-relay/mail.ts
parent66e2d76b75ed04ae8a43baefdb970f4cb89c5925 (diff)
downloadcrupest-e18d101cae1dfcef29abd102d2908d429f4688d5.tar.gz
crupest-e18d101cae1dfcef29abd102d2908d429f4688d5.tar.bz2
crupest-e18d101cae1dfcef29abd102d2908d429f4688d5.zip
mail: revert removing.
Diffstat (limited to 'deno/mail-relay/mail.ts')
-rw-r--r--deno/mail-relay/mail.ts234
1 files changed, 0 insertions, 234 deletions
diff --git a/deno/mail-relay/mail.ts b/deno/mail-relay/mail.ts
deleted file mode 100644
index 9cc591c..0000000
--- a/deno/mail-relay/mail.ts
+++ /dev/null
@@ -1,234 +0,0 @@
-import { encodeBase64 } from "@std/encoding/base64";
-import { parse } from "@std/csv/parse";
-import { simpleParseMail } from "./mail-parsing.ts";
-
-export class Mail {
- #raw;
- #parsed;
-
- constructor(raw: string) {
- this.#raw = raw;
- this.#parsed = simpleParseMail(raw);
- }
-
- get raw() {
- return this.#raw;
- }
-
- set raw(value) {
- this.#raw = value;
- this.#parsed = simpleParseMail(value);
- }
-
- get parsed() {
- return this.#parsed;
- }
-
- toUtf8Bytes(): Uint8Array {
- const utf8Encoder = new TextEncoder();
- return utf8Encoder.encode(this.raw);
- }
-
- toBase64(): string {
- return encodeBase64(this.raw);
- }
-
- simpleFindAllAddresses(): string[] {
- const re = /,?\<?([a-z0-9_'+\-\.]+\@[a-z0-9_'+\-\.]+)\>?,?/gi;
- return [...this.raw.matchAll(re)].map((m) => m[1]);
- }
-}
-
-export interface MailDeliverRecipientResult {
- kind: "success" | "failure";
- message?: string;
- cause?: unknown;
-}
-
-export class MailDeliverResult {
- message?: string;
- smtpMessage?: string;
- recipients = new Map<string, MailDeliverRecipientResult>();
- constructor(public mail: Mail) {}
-
- get hasFailure() {
- return this.recipients.values().some((v) => v.kind !== "success");
- }
-
- 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}`);
- }
- for (const [name, result] of this.recipients.entries()) {
- const { kind, message } = result;
- lines.push(`${prefix} (${name}): ${kind} ${message}`);
- }
- return lines.join("\n");
- }
-}
-
-export class MailDeliverContext {
- readonly recipients: Set<string> = new Set();
- readonly result;
-
- constructor(public logTag: string, public mail: Mail) {
- this.result = new MailDeliverResult(this.mail);
- }
-}
-
-export interface MailDeliverHook {
- callback(context: MailDeliverContext): Promise<void>;
-}
-
-export abstract class MailDeliverer {
- #counter = 1;
- #last?: Promise<void>;
-
- abstract name: string;
- preHooks: MailDeliverHook[] = [];
- postHooks: MailDeliverHook[] = [];
-
- constructor(public sync: boolean) {}
-
- protected abstract doDeliver(
- mail: Mail,
- context: MailDeliverContext,
- ): Promise<void>;
-
- async deliverRaw(rawMail: string) {
- return await this.deliver({ mail: new Mail(rawMail) });
- }
-
- async #deliverCore(context: MailDeliverContext) {
- for (const hook of this.preHooks) {
- await hook.callback(context);
- }
-
- await this.doDeliver(context.mail, context);
-
- for (const hook of this.postHooks) {
- await hook.callback(context);
- }
- }
-
- async deliver(options: {
- mail: Mail;
- recipients?: string[];
- logTag?: string;
- }): Promise<MailDeliverResult> {
- const logTag = options.logTag ?? `[${this.name} ${this.#counter}]`;
- this.#counter++;
-
- if (this.#last != null) {
- console.info(logTag, "Wait for last delivering done...");
- await this.#last;
- }
-
- const context = new MailDeliverContext(
- logTag,
- options.mail,
- );
- options.recipients?.forEach((r) => context.recipients.add(r));
-
- console.info(context.logTag, "Begin to deliver mail...");
-
- const deliverPromise = this.#deliverCore(context);
-
- if (this.sync) {
- this.#last = deliverPromise.then(() => {}, () => {});
- }
-
- await deliverPromise;
- this.#last = undefined;
-
- console.info(context.logTag, "Deliver result:");
- console.info(context.result.generateLogMessage(context.logTag));
-
- if (context.result.hasFailure) {
- throw new Error("Failed to deliver to some recipients.");
- }
-
- return context.result;
- }
-}
-
-export class RecipientFromHeadersHook implements MailDeliverHook {
- constructor(public mailDomain: string) {}
-
- callback(context: MailDeliverContext) {
- if (context.recipients.size !== 0) {
- console.warn(
- context.logTag,
- "Recipients are already filled, skip inferring from headers.",
- );
- } else {
- [...context.mail.parsed.recipients].filter((r) =>
- r.endsWith("@" + this.mailDomain)
- ).forEach((r) => context.recipients.add(r));
-
- console.info(
- context.logTag,
- "Use recipients inferred from mail headers:",
- [...context.recipients].join(", "),
- );
- }
- return Promise.resolve();
- }
-}
-
-export class FallbackRecipientHook implements MailDeliverHook {
- constructor(public fallback: Set<string> = new Set()) {}
-
- callback(context: MailDeliverContext) {
- if (context.recipients.size === 0) {
- console.info(
- context.logTag,
- "Use fallback recipients:" + [...this.fallback].join(", "),
- );
- this.fallback.forEach((a) => context.recipients.add(a));
- }
- return Promise.resolve();
- }
-}
-
-export class AliasRecipientMailHook implements MailDeliverHook {
- #aliasFile;
-
- constructor(aliasFile: string) {
- this.#aliasFile = aliasFile;
- }
-
- async #parseAliasFile(logTag: string): Promise<Map<string, string>> {
- const result = new Map();
- if ((await Deno.stat(this.#aliasFile)).isFile) {
- const text = await Deno.readTextFile(this.#aliasFile);
- const csv = parse(text);
- for (const [real, ...aliases] of csv) {
- aliases.forEach((a) => result.set(a, real));
- }
- } else {
- console.warn(
- logTag,
- `Recipient alias file ${this.#aliasFile} is not found.`,
- );
- }
- return result;
- }
-
- async callback(context: MailDeliverContext) {
- const aliases = await this.#parseAliasFile(context.logTag);
- for (const recipient of [...context.recipients]) {
- const realRecipients = aliases.get(recipient);
- if (realRecipients != null) {
- console.info(
- context.logTag,
- `Recipient alias resolved: ${recipient} => ${realRecipients}.`,
- );
- context.recipients.delete(recipient);
- context.recipients.add(realRecipients);
- }
- }
- }
-}