Skip to content

Quickstart

This sends one email through an in-memory node:sqlite store and a fake transport. Swap the transport for a real provider and the store for your Durable Object storage when you deploy.

import { DatabaseSync, type SQLOutputValue } from "node:sqlite";
import { createCarrier } from "@fungi.computer/carrier";
import type {
WatchdogSqliteOwner,
WatchdogSqliteValue,
} from "@fungi.computer/watchdog";
const database = new DatabaseSync(":memory:");
const toSqlite = (value: WatchdogSqliteValue) =>
value instanceof ArrayBuffer ? new Uint8Array(value) : value;
const fromSqlite = (value: SQLOutputValue): WatchdogSqliteValue => {
if (typeof value === "bigint") return Number(value);
if (value instanceof Uint8Array) return value.slice().buffer;
return value;
};
const storage: WatchdogSqliteOwner = {
sql: {
exec: (statement, ...bindings) => {
const prepared = database.prepare(statement);
const values = bindings.map(toSqlite);
if (!/^\s*(?:SELECT|PRAGMA|WITH)\b/iu.test(statement)) {
prepared.run(...values);
return { toArray: () => [] };
}
const rows = prepared
.all(...values)
.map((row) =>
Object.fromEntries(
Object.entries(row).map(([key, value]) => [key, fromSqlite(value)]),
),
);
return { toArray: () => rows };
},
},
transactionSync: (operation) => {
database.exec("BEGIN");
try {
const result = operation();
database.exec("COMMIT");
return result;
} catch (error) {
database.exec("ROLLBACK");
throw error;
}
},
};
const carrier = await createCarrier({
storage,
now: Date.now,
host: {
// Commit the mutation together with a durable wake. A demo needs no wake.
commit: async (mutation) => mutation(),
requestWake: async () => {},
},
transport: async (email) => {
console.log(`provider got "${email.subject}"`);
return { kind: "provider_accepted", providerMessageId: "demo-1" };
},
});
const receipt = await carrier.send({
id: "welcome:user-123:v1",
from: { email: "hello@example.com", name: "Example" },
to: ["ada@example.net"],
subject: "Welcome",
text: "Your account is ready.",
});
console.log(receipt.status); // "queued"
await carrier.tick(); // your wake calls this
console.log((await carrier.get(receipt.id))?.status); // "provider_accepted"
database.close();

send() only accepts the email. The provider call happens on tick(), which a real host runs from its durable wake. The Cloudflare adapter does this for you from the Durable Object alarm.