59 lines · 2.1 KB
import PostalMime, { type RawEmail } from "postal-mime";
import { getAddress, incrementReceivedCounter } from "./db.ts";
import type { Dispatcher } from "./dispatch.ts";
import type { SqlExecutor } from "./storage.ts";
import type { OwnerRef, ParsedMail } from "./types.ts";

// Deliberately not Cloudflare's ForwardableEmailMessage: postal-mime accepts
// every raw format either mode hands it (a ReadableStream from Email Routing,
// a string from mail.tm's API), so this is the only shape core needs.
export interface InboundMessage {
  to: string;
  from: string;
  raw: RawEmail;
}

export async function handleInboundEmail(
  message: InboundMessage,
  db: SqlExecutor,
  dispatcher: Dispatcher
): Promise<void> {
  const recipient = message.to.toLowerCase();
  const row = await getAddress(db, recipient);

  const now = Math.floor(Date.now() / 1000);
  if (!row || row.revoked !== 0 || (row.permanent !== 1 && row.expires_at <= now)) {
    return;
  }

  const owner: OwnerRef = { type: row.owner_type, id: row.owner_id };
  await incrementReceivedCounter(db);

  const parsed = await PostalMime.parse(message.raw, { attachmentEncoding: "arraybuffer" });
  const mail: ParsedMail = {
    from: parsed.from?.address ?? message.from,
    to: row.address,
    subject: parsed.subject ?? "(no subject)",
    text: parsed.text ?? "",
    html: parsed.html,
    attachments: parsed.attachments.map((a) => ({
      filename: a.filename ?? "attachment",