Gather subscribers for a launch, write issues in an HTML editor, and queue each campaign for every active subscriber.
Read the article · Open newsletter tool
The Subscribe panel tab has the same snippet as the waitlist, with the button labelled Subscribe.
html<div data-b59-list="<list id>"></div>
<script src="https://b59.link/sdk/b59-lists.js" async></script>Newsletters tab, then New newsletter. Fill in a title (only you see it), the subject line and the HTML body. The preview renders the HTML with scripts switched off. Starter template gives a simple layout. Save keeps the draft.
Scripts are rejected on save. The HTML is limited to 500 KB.
html<h1>We launched</h1>
<p>Thanks for waiting.</p>
<p><a href="https://example.com">Try it now</a></p>
Send as campaign… asks for confirmation, then queues one email per active subscriber. Unsubscribed people are skipped. Each email gets a footer with the company line (Biz59 LLC, New York, NY) and a unique unsubscribe link. The footer goes inside </body> when your HTML has one.
Queued emails show as pending in the Campaigns table, then as sent or failed once the email service has handled them.

For a React site, copy this component into your project. It posts to the same endpoint and needs no package.
tsx// B59Signup.tsx: copy into your React project. No packages needed.
import { useState, type FormEvent } from "react";
const LIST_ID = "<list id>";
export default function B59Signup({ buttonLabel = "Join" }: { buttonLabel?: string }) {
const [email, setEmail] = useState("");
const [name, setName] = useState("");
const [website, setWebsite] = useState(""); // honeypot: people never fill it
const [message, setMessage] = useState("");
const [status, setStatus] = useState<"idle" | "busy" | "done" | "error">("idle");
async function submit(e: FormEvent) {
e.preventDefault();
setStatus("busy");
try {
const res = await fetch(`https://b59.link/api/lists/public/${LIST_ID}/join`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, name: name || null, website }),
});
const body = await res.json();
if (!res.ok) throw new Error(body.detail ?? "Something went wrong. Try again.");
setMessage(body.position ? `${body.message} You're #${body.position} on the waitlist.` : body.message);
setStatus("done");
} catch (err) {
setMessage((err as Error).message);
setStatus("error");
}
}
if (status === "done") return <p>{message}</p>;
return (
<form onSubmit={submit} style={{ maxWidth: 420, display: "grid", gap: 8 }}>
<input type="email" required value={email} onChange={(e) => setEmail(e.target.value)} placeholder="you@example.com" aria-label="Email" />
<input value={name} onChange={(e) => setName(e.target.value)} placeholder="Name (optional)" aria-label="Name" />
<input tabIndex={-1} autoComplete="off" aria-hidden="true" value={website} onChange={(e) => setWebsite(e.target.value)} style={{ position: "absolute", left: -9999, opacity: 0 }} />
<button type="submit" disabled={status === "busy"}>{status === "busy" ? "Sending…" : buttonLabel}</button>
{status === "error" && <p style={{ color: "#ef4444" }}>{message}</p>}
</form>
);
}
// Use it: <B59Signup buttonLabel="Join the waitlist" />The sign-up panel calls this endpoint. You can call it from your own form too. No login is needed.
httpPOST https://b59.link/api/lists/public/<list id>/join
Content-Type: application/json
{"email": "ana@example.com", "name": "Ana"}
→ 201 {"message": "Thanks! You're on the list.", "position": 1}