Something went wrong. Try again.
a tool for shared writing and social publishing
Something went wrong. Try again.
3.1 kB · 101 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102import type { Database } from "supabase/database.types";
export type EmailPostSendMode = "scheduled" | "on_subscribe";export type EmailPostAudience = "all" | "free" | "paid";// scheduled → sending → sent | failed for a one-off send; an on-subscribe// email sits in active. Either pauses when the publication stops being// eligible (email mode off, or its owner loses Pro) until the author saves it// again.export type EmailPostStatus = | "scheduled" | "sending" | "sent" | "failed" | "paused" | "active";
export type EmailPostRow = Database["public"]["Tables"]["publication_email_posts"]["Row"];
export const EMAIL_POST_SUMMARY_COLUMNS = "id, leaflet, title, description, send_mode, send_at, audience, status, subscriber_count, sent_at, error, updated_at";
export type EmailPostSummary = Pick< EmailPostRow, | "id" | "leaflet" | "title" | "description" | "send_at" | "subscriber_count" | "sent_at" | "error" | "updated_at"> & { send_mode: EmailPostSendMode; audience: EmailPostAudience; status: EmailPostStatus;};
// Bounded by how long an Inngest function can sleep before sending.export function latestSendAt() { const d = new Date(); d.setFullYear(d.getFullYear() + 1); return d;}
// A schedule a few seconds in the past is a slow form submit, not a mistake.const SEND_AT_GRACE_MS = 60_000;
export function isValidSendAt(at: Date) { return ( !isNaN(at.getTime()) && at.getTime() >= Date.now() - SEND_AT_GRACE_MS && at <= latestSendAt() );}
// Statuses whose send options can still change; anything else (sending, sent,// failed) is history.export const UNSENT_EMAIL_POST_STATUSES: EmailPostStatus[] = [ "scheduled", "active", "paused",];
export function isEmailPostFinal(status: EmailPostStatus) { return !UNSENT_EMAIL_POST_STATUSES.includes(status);}
// A one-off email whose send time has come is on its way out, even in the// moments before the send job claims it and its status still reads scheduled.export function isEmailPostSending( email: Pick<EmailPostSummary, "status" | "send_at">, now: number,) { if (email.status === "sending") return true; return ( email.status === "scheduled" && !!email.send_at && new Date(email.send_at).getTime() <= now );}
// When an on-subscribe email goes out, for the audience it's aimed at.export function onSubscribeTrigger(audience: EmailPostAudience) { if (audience === "paid") return "when someone becomes a paid member"; if (audience === "free") return "when someone subscribes for free"; return "when someone subscribes";}
export function subscriberCountLabel(n: number | null) { if (n === null) return "subscribers"; return `${n.toLocaleString()} ${n === 1 ? "subscriber" : "subscribers"}`;}
// Why a paused email is paused; `error` holds the ineligible reason.export function pausedReason(error: string | null) { if (error === "not_pro") return "The publication's owner no longer has Leaflet Pro."; return "Email mode is turned off for this publication.";}