Email
mailbox.js 151 lines
Files · 9
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
import ddl from "./schema.js";
export function createMailbox(L) {
const setup = () =>
L.db.exec(ddl.replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS"));
async function row(id) {
return (await L.db.query("SELECT * FROM emails WHERE id=?", [id]))[0];
}
async function publish(message) {
if (message.event_emitted) return;
const sent = message.folder === "sent";
await L.events.emit(sent ? "email-sent" : "email-received", {
id: message.id,
...(sent
? { to: message.to_addr, sent_at: message.created_at }
: { from: message.from_addr, received_at: message.created_at }),
subject: message.subject,
body: message.body,
});
await L.db.exec("UPDATE emails SET event_emitted=1 WHERE id=?", [
message.id,
]);
}
async function deliver(message) {
if (message.folder === "sent") {
await publish(message);
return { status: "sent" };
}
let result;
await L.db.exec("UPDATE emails SET auto_send=0,last_error=? WHERE id=?", [
"Delivery in progress; if interrupted, check Sent before retrying.",
message.id,
]);
try {
result = await L.email.send({
to: message.to_addr,
...(message.cc ? { cc: message.cc } : {}),
subject: message.subject,
body: message.body,
});
} catch (error) {
await L.db.exec("UPDATE emails SET last_error=? WHERE id=?", [
String(error),
message.id,
]);
throw error;
}
if (result.status === "unconfigured") {
await L.db.exec("UPDATE emails SET auto_send=1,last_error=? WHERE id=?", [
"Draft waiting for an account in host Settings. It will send automatically after setup.",
message.id,
]);
return result;
}
if (result.status !== "sent")
throw Error("The host did not confirm SMTP delivery.");
await L.db.exec(
"UPDATE emails SET folder='sent',from_addr=?,to_addr=?,created_at=?,last_error=NULL WHERE id=?",
[result.from, result.to, result.sent_at, message.id],
);
await publish(await row(message.id));
return result;
}
async function send(data, envelope) {
if (!data?.to?.trim() || !data?.subject?.trim())
throw Error("Enter a recipient and subject.");
const key = envelope?.hash || null;
let message = key
? (await L.db.query("SELECT * FROM emails WHERE source_key=?", [key]))[0]
: null;
if (!message) {
await L.db.exec(
"INSERT INTO emails (folder,to_addr,cc,subject,body,is_read,created_at,source_key) VALUES ('outbox',?,?,?,?,1,?,?)",
[
data.to.trim(),
data.cc || "",
data.subject,
data.body || "",
new Date().toISOString(),
key,
],
);
message = (
await L.db.query("SELECT * FROM emails WHERE rowid=last_insert_rowid()")
)[0];
} else if (message.folder === "outbox") {
await L.db.exec(
"UPDATE emails SET to_addr=?,cc=?,subject=?,body=? WHERE id=?",
[
data.to.trim(),
data.cc || "",
data.subject,
data.body || "",
message.id,
],
);
message = await row(message.id);
}
return deliver(message);
}
async function sync() {
const unpublished = await L.db.query(
"SELECT * FROM emails WHERE folder IN ('sent','inbox') AND event_emitted=0 ORDER BY id",
);
for (const message of unpublished) await publish(message);
const waiting = await L.db.query(
"SELECT * FROM emails WHERE folder='outbox' AND auto_send=1 ORDER BY id",
);
for (const message of waiting) await deliver(message);
const since = await L.kv.get("imap_cursor");
const result = await L.email.fetch(since ? { since } : {});
if (!result.configured) return result;
for (const incoming of result.messages) {
await L.db.exec(
"INSERT OR IGNORE INTO emails (folder,from_addr,to_addr,subject,body,created_at,remote_key) VALUES ('inbox',?,?,?,?,?,?)",
[
incoming.from,
result.address,
incoming.subject,
incoming.body,
incoming.received_at,
incoming.key,
],
);
const message = (
await L.db.query("SELECT * FROM emails WHERE remote_key=?", [
incoming.key,
])
)[0];
await publish(message);
}
await L.kv.set("imap_cursor", result.since);
return result;
}
async function setStarred(id, starred) {
const [message] = await L.db.query(
"UPDATE emails SET starred=? WHERE id=? AND COALESCE(starred,0)<>? RETURNING *",
[starred ? 1 : 0, id, starred ? 1 : 0]);
if (message && starred) await L.events.emit("email-starred", {
id: message.id, from: message.from_addr || "", subject: message.subject,
body: message.body, received_at: message.created_at, folder: message.folder,
});
}
return { setup, send, sync, deliver, setStarred };
}