Relay: developer API platform
A paper-and-ink, terminal-flavoured site for a developer API: a live send console, the life of an event, a delivery map, language-synced docs, usage pricing and a status page.
// Ballmac UI: Relay template fonts. https://ui.ballmac.com/templates/template-relay
import { Hanken_Grotesk, IBM_Plex_Mono } from "next/font/google"
export const relaySans = Hanken_Grotesk({ variable: "--relay-sans", subsets: ["latin"], display: "swap", preload: false })
export const relayMono = IBM_Plex_Mono({ variable: "--relay-mono", subsets: ["latin"], weight: ["400", "500", "600"], display: "swap", preload: false })// Ballmac UI: Relay template shell. https://ui.ballmac.com/templates/template-relay
"use client"
import * as React from "react"
import { ArrowUpRight, Menu, X } from "lucide-react"
import { cn } from "@/lib/utils"
import { relayMono, relaySans } from "@/components/ballmac/templates/relay/relay-fonts"
type RelayPage = "home" | "docs" | "pricing" | "status"
type RelayHrefs = Record<RelayPage, string>
const defaultHrefs: RelayHrefs = { home: "/relay", docs: "/relay/docs", pricing: "/relay/pricing", status: "/relay/status" }
/** Relay's palette: warm paper and black ink with a single orange signal. Dark mode inverts to ink. */
const relayCss = `
.relay-theme{--background:oklch(0.975 0.008 85);--foreground:oklch(0.17 0.01 60);--card:oklch(0.99 0.006 85);--card-foreground:oklch(0.17 0.01 60);--popover:oklch(0.99 0.006 85);--popover-foreground:oklch(0.17 0.01 60);--primary:oklch(0.17 0.01 60);--primary-foreground:oklch(0.975 0.008 85);--secondary:oklch(0.94 0.012 85);--secondary-foreground:oklch(0.17 0.01 60);--muted:oklch(0.94 0.012 85);--muted-foreground:oklch(0.48 0.02 70);--accent:oklch(0.93 0.016 80);--accent-foreground:oklch(0.17 0.01 60);--border:oklch(0.17 0.01 60 / 14%);--input:oklch(0.17 0.01 60 / 20%);--ring:oklch(0.62 0.2 45);--surface:oklch(0.96 0.01 85);--destructive:oklch(0.55 0.22 27);--chart-1:oklch(0.52 0.19 42);--chart-2:oklch(0.46 0.12 160);--chart-3:oklch(0.7 0.15 85);--chart-4:oklch(0.5 0.17 265);--chart-5:oklch(0.55 0.2 330);--radius:0.375rem}
.dark .relay-theme{--background:oklch(0.15 0.008 60);--foreground:oklch(0.95 0.01 85);--card:oklch(0.185 0.01 60);--card-foreground:oklch(0.95 0.01 85);--popover:oklch(0.2 0.01 60);--popover-foreground:oklch(0.95 0.01 85);--primary:oklch(0.95 0.01 85);--primary-foreground:oklch(0.17 0.01 60);--secondary:oklch(0.24 0.012 60);--secondary-foreground:oklch(0.95 0.01 85);--muted:oklch(0.23 0.012 60);--muted-foreground:oklch(0.72 0.02 80);--accent:oklch(0.27 0.014 60);--accent-foreground:oklch(0.95 0.01 85);--border:oklch(0.95 0.01 85 / 14%);--input:oklch(0.95 0.01 85 / 20%);--ring:oklch(0.72 0.19 50);--surface:oklch(0.17 0.009 60);--destructive:oklch(0.7 0.19 25);--chart-1:oklch(0.74 0.18 50);--chart-2:oklch(0.78 0.14 165);--chart-3:oklch(0.83 0.14 90);--chart-4:oklch(0.72 0.14 265);--chart-5:oklch(0.74 0.17 335)}
`
function RelayMark({ className }: { className?: string }) {
return (
<svg viewBox="0 0 28 28" fill="none" aria-hidden="true" className={cn("size-6", className)}>
<rect x="1.5" y="1.5" width="25" height="25" rx="5" stroke="currentColor" strokeWidth="1.8" />
<path d="M7 14h6l3-5 3 10 3-5h2" stroke="var(--chart-1)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
}
const links: { key: RelayPage; label: string }[] = [
{ key: "docs", label: "Docs" },
{ key: "pricing", label: "Pricing" },
{ key: "status", label: "Status" },
]
type RelayShellProps = React.ComponentProps<"div"> & {
/** The page being shown, so its nav link is marked current. */
page: RelayPage
/** Override where pages live (used by previews). */
hrefs?: Partial<RelayHrefs>
}
/** Relay's frame: paper-and-ink theme, fonts, a ruled header and footer. Wrap every Relay page in it. */
function RelayShell({ page, hrefs: overrides, className, style, children, ...props }: RelayShellProps) {
const hrefs = { ...defaultHrefs, ...overrides }
const [open, setOpen] = React.useState(false)
return (
<div
data-slot="relay"
className={cn("relay-theme bg-background text-foreground relative min-h-dvh overflow-x-clip", relaySans.variable, relayMono.variable, className)}
style={{ fontFamily: "var(--relay-sans), ui-sans-serif, system-ui, sans-serif", ...style }}
{...props}
>
<style>{relayCss}</style>
<header className="bg-background/85 sticky top-0 z-40 border-b backdrop-blur-xl">
<div className="mx-auto flex h-14 max-w-6xl items-center justify-between px-4 sm:px-6">
<a href={hrefs.home} className="focus-visible:ring-ring/50 flex items-center gap-2.5 rounded-sm font-semibold tracking-tight outline-none focus-visible:ring-[3px]">
<RelayMark /> <span style={{ fontFamily: "var(--relay-mono)" }}>relay</span>
</a>
<nav aria-label="Main" className="hidden items-center gap-1 md:flex">
{links.map((l) => (
<a
key={l.key}
href={hrefs[l.key]}
aria-current={page === l.key ? "page" : undefined}
className="text-muted-foreground hover:text-foreground aria-[current=page]:text-foreground aria-[current=page]:bg-accent focus-visible:ring-ring/50 rounded-md px-3 py-1.5 text-sm outline-none transition-colors focus-visible:ring-[3px]"
>
{l.label}
</a>
))}
<a href="#" className="text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 inline-flex items-center gap-1 rounded-md px-3 py-1.5 text-sm outline-none transition-colors focus-visible:ring-[3px]">
GitHub <ArrowUpRight className="size-3.5 rtl:-scale-x-100" aria-hidden="true" />
</a>
</nav>
<div className="flex items-center gap-2">
<a href="#" className="text-muted-foreground hover:text-foreground hidden px-2 text-sm sm:block">Log in</a>
<a href="#" className="bg-foreground text-background focus-visible:ring-ring/50 inline-flex h-9 items-center rounded-md px-3.5 text-sm font-medium outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]" style={{ fontFamily: "var(--relay-mono)" }}>
Get API key
</a>
<button
type="button"
aria-label={open ? "Close menu" : "Open menu"}
aria-expanded={open}
aria-controls="relay-mobile-menu"
onClick={() => setOpen((v) => !v)}
className="text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 inline-flex size-9 items-center justify-center rounded-md outline-none focus-visible:ring-[3px] md:hidden"
>
{open ? <X className="size-5" aria-hidden="true" /> : <Menu className="size-5" aria-hidden="true" />}
</button>
</div>
</div>
{open && (
<nav id="relay-mobile-menu" aria-label="Mobile" className="border-t px-4 py-2 md:hidden">
{links.map((l) => (
<a key={l.key} href={hrefs[l.key]} className="hover:bg-accent block rounded-md px-3 py-2.5 text-sm">{l.label}</a>
))}
</nav>
)}
</header>
{children}
<footer className="border-t">
<div className="mx-auto grid max-w-6xl gap-10 px-4 py-14 sm:grid-cols-2 sm:px-6 lg:grid-cols-[1.4fr_repeat(3,1fr)]">
<div>
<a href={hrefs.home} className="flex items-center gap-2.5 font-semibold"><RelayMark /> <span style={{ fontFamily: "var(--relay-mono)" }}>relay</span></a>
<p className="text-muted-foreground mt-3 max-w-xs text-sm text-pretty">Reliable webhook delivery for teams that can’t afford to drop an event.</p>
</div>
{[
{ title: "Product", items: [["Delivery", "home"], ["Retries", "home"], ["Replay", "home"], ["Pricing", "pricing"]] },
{ title: "Developers", items: [["Documentation", "docs"], ["API reference", "docs"], ["SDKs", "docs"], ["Status", "status"]] },
{ title: "Company", items: [["About", "home"], ["Security", "home"], ["Careers", "home"], ["Contact", "home"]] },
].map((c) => (
<div key={c.title}>
<h2 className="text-xs font-semibold tracking-wider uppercase" style={{ fontFamily: "var(--relay-mono)" }}>{c.title}</h2>
<ul className="mt-4 space-y-2.5">
{c.items.map(([label, key]) => (
<li key={label}><a href={hrefs[key as RelayPage]} className="text-muted-foreground hover:text-foreground text-sm transition-colors">{label}</a></li>
))}
</ul>
</div>
))}
</div>
<div className="mx-auto max-w-6xl border-t px-4 py-5 sm:px-6">
<p className="text-muted-foreground text-xs" style={{ fontFamily: "var(--relay-mono)" }}>© 2026 Relay, Inc. · SOC 2 Type II · 99.99% delivery SLA</p>
</div>
</footer>
</div>
)
}
export { RelayMark, RelayShell, defaultHrefs as relayDefaultHrefs, type RelayHrefs, type RelayPage, type RelayShellProps }// Ballmac UI: Relay code samples. https://ui.ballmac.com/templates/template-relay
import type { Snippet } from "@/components/ballmac/snippet-tabs"
/** The package the samples install. Kept in a variable so it is documentation, not a dependency of this file. */
const pkg = "@relay/sdk"
/** Sending an event, in four languages. */
export const sendSnippets: Snippet[] = [
{
label: "curl",
language: "bash",
code: `curl https://api.relay.dev/v1/events \\
-H "Authorization: Bearer {{key}}" \\
-H "Content-Type: application/json" \\
-d '{
"type": "invoice.paid",
"endpoint": "ep_billing",
"data": { "id": "inv_1042", "amount": 4900 }
}'`,
},
{
label: "TypeScript",
language: "typescript",
code: `import { Relay } from ${JSON.stringify(pkg)}
const relay = new Relay("{{key}}")
await relay.events.send({
type: "invoice.paid",
endpoint: "ep_billing",
data: { id: "inv_1042", amount: 4900 },
})`,
},
{
label: "Python",
language: "python",
code: `from relay import Relay
relay = Relay("{{key}}")
relay.events.send(
type="invoice.paid",
endpoint="ep_billing",
data={"id": "inv_1042", "amount": 4900},
)`,
},
{
label: "Go",
language: "go",
code: `client := relay.New("{{key}}")
_, err := client.Events.Send(ctx, relay.Event{
Type: "invoice.paid",
Endpoint: "ep_billing",
Data: map[string]any{"id": "inv_1042", "amount": 4900},
})`,
},
]
/** Verifying a signature, in four languages. */
export const verifySnippets: Snippet[] = [
{
label: "curl",
language: "bash",
code: `# Recompute the signature yourself to check a delivery
SIGNED="$TIMESTAMP.$BODY"
printf '%s' "$SIGNED" | openssl dgst -sha256 -hmac "$RELAY_SECRET"
# The result must match the Relay-Signature header`,
},
{
label: "TypeScript",
language: "typescript",
code: `import { verify } from ${JSON.stringify(pkg)}
export async function POST(req: Request) {
const body = await req.text()
const event = verify(body, req.headers, process.env.RELAY_SECRET!)
// event.id is stable across retries: use it to dedupe
return new Response("ok")
}`,
},
{
label: "Python",
language: "python",
code: `from relay import verify
@app.post("/webhooks")
def webhook(request):
event = verify(request.body, request.headers, RELAY_SECRET)
# event.id is stable across retries: use it to dedupe
return "ok"`,
},
{
label: "Go",
language: "go",
code: `func webhook(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
event, err := relay.Verify(body, r.Header, secret)
// event.ID is stable across retries: use it to dedupe
w.WriteHeader(http.StatusOK)
}`,
},
]// Ballmac UI: Relay home page. https://ui.ballmac.com/templates/template-relay
"use client"
import * as React from "react"
import { ArrowRight, Check, Clock, History, KeyRound, ListRestart, Radar, ShieldCheck, Shuffle, X } from "lucide-react"
import { DottedMap } from "@/components/ballmac/dotted-map"
import { Marquee } from "@/components/ballmac/marquee"
import { NumberTicker } from "@/components/ballmac/number-ticker"
import { SnippetTabs } from "@/components/ballmac/snippet-tabs"
import { RelayShell, type RelayHrefs } from "@/components/ballmac/templates/relay/relay-theme"
import { sendSnippets, verifySnippets } from "@/components/ballmac/templates/relay/relay-samples"
import { cn } from "@/lib/utils"
const mono = { fontFamily: "var(--relay-mono)" } as const
const label = "text-muted-foreground text-xs font-semibold tracking-wider uppercase"
const attempts = [
{ n: 1, t: "0.0 s", status: "503", note: "billing.acme.co unavailable", ok: false },
{ n: 2, t: "+30 s", status: "timeout", note: "No response in 10 s", ok: false },
{ n: 3, t: "+5 min", status: "200", note: "Delivered in 142 ms", ok: true },
]
const features = [
{ icon: ListRestart, title: "Retries that make sense", text: "Exponential backoff with jitter for 3 days. Circuit breakers pause a sick endpoint instead of hammering it." },
{ icon: ShieldCheck, title: "Signed and verifiable", text: "Every delivery is signed with a rotating secret and a timestamp. Verifying takes five lines in any language." },
{ icon: History, title: "Replay anything", text: "Re-send one event or a whole day to any endpoint, including your laptop, with one click or one command." },
{ icon: Shuffle, title: "Transform in flight", text: "Reshape payloads, filter by field and fan out to many endpoints without changing your producer." },
{ icon: Radar, title: "See every attempt", text: "Request, response, headers and latency for each try, searchable for 90 days and exportable to your logs." },
{ icon: KeyRound, title: "Per-endpoint controls", text: "Rate limits, IP allow-lists and mTLS for each destination, so one slow consumer never slows the rest." },
]
const regions = [
{ name: "iad1", city: "Virginia", ms: 11, lat: 38.9, lng: -77.4 },
{ name: "sfo1", city: "San Francisco", ms: 14, lat: 37.7, lng: -122.4 },
{ name: "gru1", city: "São Paulo", ms: 23, lat: -23.5, lng: -46.6 },
{ name: "fra1", city: "Frankfurt", ms: 9, lat: 50.1, lng: 8.7 },
{ name: "bom1", city: "Mumbai", ms: 19, lat: 19.1, lng: 72.9 },
{ name: "sin1", city: "Singapore", ms: 12, lat: 1.35, lng: 103.8 },
{ name: "syd1", city: "Sydney", ms: 16, lat: -33.9, lng: 151.2 },
]
function Console() {
const [state, setState] = React.useState<"idle" | "sending" | "done">("idle")
const timer = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
React.useEffect(() => () => clearTimeout(timer.current), [])
function send() {
setState("sending")
clearTimeout(timer.current)
timer.current = setTimeout(() => setState("done"), 650)
}
return (
<div className="bg-card min-w-0 overflow-hidden rounded-lg border shadow-[6px_6px_0_0_var(--border)]">
<SnippetTabs snippets={sendSnippets} storageKey="relay-language" variables={{ key: "rl_live_8f3k…" }} title="Send an event" bodyClassName="max-h-64" />
<div className="flex items-center justify-between gap-3 border-t px-4 py-3">
<p className="text-muted-foreground text-xs" style={mono} aria-live="polite">
{state === "idle" ? "POST /v1/events" : state === "sending" ? "Sending…" : "202 Accepted · 38 ms · iad1"}
</p>
<button
type="button"
onClick={send}
disabled={state === "sending"}
className="bg-chart-1 text-background focus-visible:ring-ring/50 inline-flex h-9 items-center gap-1.5 rounded-md px-3.5 text-sm font-semibold outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px] disabled:opacity-60"
style={mono}
>
{state === "done" ? "Send again" : "Send"} <ArrowRight className="size-4 rtl:rotate-180" aria-hidden="true" />
</button>
</div>
{state === "done" && (
<div className="bg-surface border-t px-4 py-4 text-xs" style={mono}>
<p className="text-chart-2 font-semibold">HTTP/1.1 202 Accepted</p>
<pre className="text-muted-foreground mt-2 overflow-x-auto" tabIndex={0}>{`{
"id": "evt_9x2k",
"status": "queued",
"attempts": [],
"endpoint": "ep_billing"
}`}</pre>
</div>
)}
</div>
)
}
type RelayHomeProps = React.ComponentProps<"div"> & { hrefs?: Partial<RelayHrefs> }
/** The Relay home page: hero with a live console, the life of an event, features, a delivery map and code. */
function RelayHome({ hrefs, ...props }: RelayHomeProps) {
return (
<RelayShell page="home" hrefs={hrefs} {...props}>
<main>
<section className="relative border-b">
<div aria-hidden="true" className="absolute inset-0 [background-image:linear-gradient(var(--border)_1px,transparent_1px),linear-gradient(90deg,var(--border)_1px,transparent_1px)] [background-size:56px_56px] [mask-image:linear-gradient(to_bottom,black,transparent_85%)]" />
<div className="relative mx-auto grid max-w-6xl items-center gap-14 px-4 py-16 sm:px-6 sm:py-24 lg:grid-cols-[1.05fr_1fr]">
<div>
<p className={cn(label, "text-chart-1 flex items-center gap-2")} style={mono}><span className="bg-chart-1 size-1.5 rounded-full" aria-hidden="true" /> Webhook infrastructure</p>
<h1 className="mt-5 text-5xl leading-[0.98] font-semibold tracking-[-0.05em] text-balance sm:text-7xl">Never lose a webhook again.</h1>
<p className="text-muted-foreground mt-6 max-w-lg text-lg text-pretty">
Relay delivers your events to every customer endpoint with retries, signatures, replay and a complete audit trail. You send one request. We make sure it lands.
</p>
<div className="mt-9 flex flex-wrap items-center gap-3">
<a href="#" className="bg-foreground text-background focus-visible:ring-ring/50 inline-flex h-12 items-center gap-2 rounded-md px-5 font-medium outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]" style={mono}>
Get your API key <ArrowRight className="size-4 rtl:rotate-180" aria-hidden="true" />
</a>
<a href={hrefs?.docs ?? "/relay/docs"} className="hover:bg-accent focus-visible:ring-ring/50 inline-flex h-12 items-center rounded-md border px-5 font-medium outline-none transition-colors focus-visible:ring-[3px]" style={mono}>
Read the docs
</a>
</div>
<p className="text-muted-foreground mt-6 text-sm">Free up to 100,000 events a month. No card needed.</p>
</div>
<Console />
</div>
</section>
<section aria-label="Customers" className="border-b">
<div className="mx-auto max-w-6xl px-4 py-9 sm:px-6">
<Marquee speed={28} gap={64} fade pauseOnHover>
{["Stripeline", "Northbeam", "Cashew", "Lumen", "Paddock", "Vantage", "Ironclad Co", "Helix"].map((c) => (
<span key={c} className="text-muted-foreground text-lg font-semibold tracking-tight" style={mono}>{c}</span>
))}
</Marquee>
</div>
</section>
{/* Life of an event */}
<section className="border-b">
<div className="mx-auto grid max-w-6xl gap-12 px-4 py-20 sm:px-6 lg:grid-cols-[1fr_1.1fr]">
<div>
<p className={label} style={mono}>The life of an event</p>
<h2 className="mt-4 text-3xl font-semibold tracking-[-0.04em] text-balance sm:text-5xl">Your customer’s server was down. Your event still arrived.</h2>
<p className="text-muted-foreground mt-5 max-w-md text-pretty sm:text-lg">
Endpoints fail all the time. Relay keeps trying on a schedule that respects the receiver, then tells you exactly what happened.
</p>
</div>
<ol className="bg-card rounded-lg border" aria-label="Delivery attempts for evt_9x2k">
<li className="flex items-center justify-between gap-4 border-b px-5 py-4 text-sm" style={mono}>
<span>evt_9x2k · invoice.paid</span>
<span className="text-muted-foreground">received 09:41:02</span>
</li>
{attempts.map((a) => (
<li key={a.n} className="flex items-start gap-4 border-b px-5 py-4 last:border-b-0">
<span className={cn("mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-full border", a.ok ? "border-chart-2 text-chart-2" : "border-destructive/50 text-destructive")}>
{a.ok ? <Check className="size-3.5" aria-hidden="true" /> : <X className="size-3.5" aria-hidden="true" />}
<span className="sr-only">{a.ok ? "Delivered" : "Failed"}</span>
</span>
<div className="min-w-0 flex-1">
<p className="text-sm font-medium">Attempt {a.n} <span className="text-muted-foreground font-normal">· {a.status}</span></p>
<p className="text-muted-foreground mt-0.5 text-sm">{a.note}</p>
</div>
<span className="text-muted-foreground flex shrink-0 items-center gap-1.5 text-xs" style={mono}><Clock className="size-3" aria-hidden="true" />{a.t}</span>
</li>
))}
</ol>
</div>
</section>
{/* Features in a ruled grid */}
<section className="border-b">
<div className="mx-auto max-w-6xl px-4 py-20 sm:px-6">
<p className={label} style={mono}>What you get</p>
<h2 className="mt-4 max-w-2xl text-3xl font-semibold tracking-[-0.04em] text-balance sm:text-5xl">Everything between “send” and “received”.</h2>
<ul className="mt-12 grid overflow-hidden rounded-lg border sm:grid-cols-2 lg:grid-cols-3">
{features.map((f, i) => (
<li key={f.title} className={cn("bg-card p-7", "border-b sm:[&:nth-child(odd)]:border-e lg:[&:nth-child(odd)]:border-e-0 lg:[&:not(:nth-child(3n))]:border-e", i >= 4 && "sm:border-b-0", i >= 3 && "lg:border-b-0", i < 3 && "lg:border-b")}>
<f.icon className="text-chart-1 size-6" aria-hidden="true" />
<h3 className="mt-6 font-semibold">{f.title}</h3>
<p className="text-muted-foreground mt-2 text-sm text-pretty">{f.text}</p>
</li>
))}
</ul>
</div>
</section>
{/* Map */}
<section className="border-b">
<div className="mx-auto grid max-w-6xl items-center gap-12 px-4 py-20 sm:px-6 lg:grid-cols-[1.2fr_1fr]">
<div className="order-2 lg:order-1">
<DottedMap
tone="foreground"
dots={120}
latRange={[-50, 70]}
label="Relay delivery regions"
markers={regions.map((r) => ({ lat: r.lat, lng: r.lng, label: r.city, tone: "primary" as const }))}
arcs={[
{ from: [38.9, -77.4], to: [50.1, 8.7], tone: "primary" },
{ from: [37.7, -122.4], to: [38.9, -77.4], tone: "primary" },
{ from: [50.1, 8.7], to: [19.1, 72.9], tone: "primary" },
{ from: [1.35, 103.8], to: [-33.9, 151.2], tone: "primary" },
]}
className="text-muted-foreground/70"
/>
</div>
<div className="order-1 lg:order-2">
<p className={label} style={mono}>Global delivery</p>
<h2 className="mt-4 text-3xl font-semibold tracking-[-0.04em] text-balance sm:text-4xl">Seven regions. Closest one wins.</h2>
<dl className="mt-8 divide-y rounded-lg border" style={mono}>
{regions.map((r) => (
<div key={r.name} className="flex items-center justify-between px-4 py-2.5 text-sm">
<dt>{r.name} <span className="text-muted-foreground">· {r.city}</span></dt>
<dd className="text-chart-2 tabular-nums">{r.ms} ms</dd>
</div>
))}
</dl>
</div>
</div>
</section>
{/* Verify */}
<section className="border-b">
<div className="mx-auto grid max-w-6xl items-center gap-12 px-4 py-20 sm:px-6 lg:grid-cols-2">
<div>
<p className={label} style={mono}>Receiving</p>
<h2 className="mt-4 text-3xl font-semibold tracking-[-0.04em] text-balance sm:text-4xl">Verify in five lines. Dedupe for free.</h2>
<p className="text-muted-foreground mt-5 max-w-md text-pretty sm:text-lg">Choose a language once. Every sample in these docs follows you.</p>
</div>
<div className="min-w-0"><SnippetTabs snippets={verifySnippets} storageKey="relay-language" title="Receive an event" bodyClassName="max-h-64" /></div>
</div>
</section>
{/* Stats */}
<section aria-label="Relay in numbers" className="border-b">
<dl className="mx-auto grid max-w-6xl divide-y sm:grid-cols-2 sm:divide-y-0 lg:grid-cols-4 lg:divide-x">
{[
{ v: 12.8, s: "B", l: "Events delivered each month", d: 1 },
{ v: 99.99, s: "%", l: "Delivery success within 3 days", d: 2 },
{ v: 38, s: " ms", l: "Median time to first attempt", d: 0 },
{ v: 90, s: " days", l: "Searchable attempt history", d: 0 },
].map((x) => (
<div key={x.l} className="px-6 py-10">
<dd className="text-4xl font-semibold tracking-[-0.04em] tabular-nums" style={mono}><NumberTicker value={x.v} format={{ minimumFractionDigits: x.d, maximumFractionDigits: x.d }} />{x.s}</dd>
<dt className="text-muted-foreground mt-2 text-sm">{x.l}</dt>
</div>
))}
</dl>
</section>
<section className="px-4 py-20 sm:px-6">
<div className="bg-foreground text-background mx-auto max-w-6xl rounded-lg px-6 py-16 text-center sm:py-20">
<h2 className="mx-auto max-w-2xl text-3xl font-semibold tracking-[-0.045em] text-balance sm:text-5xl">Send your first event in two minutes.</h2>
<p className="mx-auto mt-4 max-w-md text-pretty opacity-70">One API key, one request. The free plan includes 100,000 events a month.</p>
<a href="#" className="bg-chart-1 text-background focus-visible:ring-ring mt-8 inline-flex h-12 items-center gap-2 rounded-md px-6 font-semibold outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]" style={mono}>
Get your API key <ArrowRight className="size-4 rtl:rotate-180" aria-hidden="true" />
</a>
</div>
</section>
</main>
</RelayShell>
)
}
export { RelayHome, type RelayHomeProps }// Ballmac UI: Relay docs page. https://ui.ballmac.com/templates/template-relay
"use client"
import * as React from "react"
import { ChevronRight } from "lucide-react"
import { ApiEndpoint } from "@/components/ballmac/api-endpoint"
import { SnippetTabs } from "@/components/ballmac/snippet-tabs"
import { TableOfContents } from "@/components/ballmac/table-of-contents"
import { RelayShell, type RelayHrefs } from "@/components/ballmac/templates/relay/relay-theme"
import { sendSnippets, verifySnippets } from "@/components/ballmac/templates/relay/relay-samples"
import { cn } from "@/lib/utils"
const mono = { fontFamily: "var(--relay-mono)" } as const
const nav = [
{ title: "Get started", items: ["Introduction", "Quickstart", "Authentication"] },
{ title: "Concepts", items: ["Events and endpoints", "Retries and backoff", "Signatures", "Replay"] },
{ title: "API reference", items: ["Send an event", "List attempts", "Replay an event"] },
{ title: "Guides", items: ["Verify in Next.js", "Test locally", "Go to production"] },
]
const h2 = "scroll-mt-24 text-2xl font-semibold tracking-[-0.03em]"
type RelayDocsProps = React.ComponentProps<"div"> & { hrefs?: Partial<RelayHrefs> }
/** Relay docs: sidebar, a quickstart with language-synced samples, API reference and an on-page contents list. */
function RelayDocs({ hrefs, ...props }: RelayDocsProps) {
return (
<RelayShell page="docs" hrefs={hrefs} {...props}>
<div className="mx-auto grid max-w-6xl gap-10 px-4 sm:px-6 lg:grid-cols-[14rem_minmax(0,1fr)] xl:grid-cols-[14rem_minmax(0,1fr)_13rem]">
<aside className="hidden lg:block">
<nav aria-label="Documentation" className="sticky top-14 max-h-[calc(100dvh-3.5rem)] space-y-6 overflow-y-auto py-10 pe-2">
{nav.map((g) => (
<div key={g.title}>
<h2 className="text-xs font-semibold tracking-wider uppercase" style={mono}>{g.title}</h2>
<ul className="mt-2.5 space-y-0.5">
{g.items.map((i) => (
<li key={i}>
<a
href="#"
aria-current={i === "Quickstart" ? "page" : undefined}
className="text-muted-foreground hover:text-foreground aria-[current=page]:bg-accent aria-[current=page]:text-foreground block rounded-md px-2.5 py-1.5 text-sm transition-colors"
>
{i}
</a>
</li>
))}
</ul>
</div>
))}
</nav>
</aside>
<main className="min-w-0 py-10 lg:py-12">
<nav aria-label="Breadcrumb" className="text-muted-foreground flex items-center gap-1 text-sm">
Get started <ChevronRight className="size-3.5 rtl:rotate-180" aria-hidden="true" /> <span className="text-foreground">Quickstart</span>
</nav>
<h1 className="mt-5 text-4xl font-semibold tracking-[-0.045em] text-balance sm:text-5xl">Quickstart</h1>
<p className="text-muted-foreground mt-4 max-w-2xl text-lg text-pretty">Send your first event and watch it arrive. This takes about two minutes.</p>
<div className="prose-none mt-12 space-y-14">
<section>
<h2 id="get-a-key" className={h2}>1. Get an API key</h2>
<p className="text-muted-foreground mt-3 max-w-2xl text-pretty">
Create a key in the dashboard under <span className="bg-muted rounded px-1.5 py-0.5 text-[0.85em]" style={mono}>Settings → API keys</span>. Keys starting with <span className="bg-muted rounded px-1.5 py-0.5 text-[0.85em]" style={mono}>rl_test_</span> never reach real endpoints.
</p>
</section>
<section>
<h2 id="send-an-event" className={h2}>2. Send an event</h2>
<p className="text-muted-foreground mt-3 mb-5 max-w-2xl text-pretty">Pick your language. Your choice is remembered across every sample in the docs.</p>
<SnippetTabs snippets={sendSnippets} storageKey="relay-language" variables={{ key: "rl_test_8f3k…" }} lineNumbers />
</section>
<section>
<h2 id="receive-it" className={h2}>3. Receive it</h2>
<p className="text-muted-foreground mt-3 mb-5 max-w-2xl text-pretty">Verify the signature, then return a 2xx. Anything else is retried.</p>
<SnippetTabs snippets={verifySnippets} storageKey="relay-language" lineNumbers />
<div className="bg-chart-1/10 border-chart-1/30 mt-6 rounded-lg border p-4 text-sm text-pretty">
<p className="font-semibold">Dedupe on event.id</p>
<p className="text-muted-foreground mt-1">Relay delivers at least once. The id is stable across retries and replays, so store it and skip repeats.</p>
</div>
</section>
<section>
<h2 id="api-reference" className={h2}>API reference</h2>
<div className="mt-6 space-y-4">
<ApiEndpoint
method="POST"
path="/v1/events"
summary="Send an event"
description="Queues an event for delivery to one endpoint, or every endpoint subscribed to its type."
baseUrl="https://api.relay.dev"
auth="Bearer token"
defaultOpen
parameters={[
{ name: "type", in: "body", type: "string", required: true, description: "Event type, such as invoice.paid." },
{ name: "endpoint", in: "body", type: "string", description: "Send to one endpoint. Omit to fan out by subscription." },
{ name: "data", in: "body", type: "object", required: true, description: "The payload your customer receives." },
{ name: "Idempotency-Key", in: "header", type: "string", description: "Makes retries of this request safe for 24 hours." },
]}
requestExample={`{
"type": "invoice.paid",
"endpoint": "ep_billing",
"data": { "id": "inv_1042", "amount": 4900 }
}`}
requestLanguage="json"
responses={[
{ status: 202, description: "Event queued", example: `{ "id": "evt_9x2k", "status": "queued" }`, language: "json" },
{ status: 422, description: "Invalid payload", example: `{ "error": "data is required" }`, language: "json" },
]}
/>
<ApiEndpoint
method="GET"
path="/v1/events/{id}/attempts"
summary="List attempts"
description="Every delivery attempt for an event, newest first, with status codes and latency."
baseUrl="https://api.relay.dev"
auth="Bearer token"
parameters={[{ name: "id", in: "path", type: "string", required: true, description: "The event id." }]}
responses={[{ status: 200, description: "Attempts", example: `{ "data": [{ "n": 3, "status": 200, "ms": 142 }] }`, language: "json" }]}
/>
</div>
</section>
</div>
</main>
<aside className="hidden xl:block">
<div className={cn("sticky top-14 py-12")}>
<TableOfContents
title="On this page"
offset={80}
items={[
{ id: "get-a-key", title: "1. Get an API key" },
{ id: "send-an-event", title: "2. Send an event" },
{ id: "receive-it", title: "3. Receive it" },
{ id: "api-reference", title: "API reference" },
]}
/>
</div>
</aside>
</div>
</RelayShell>
)
}
export { RelayDocs, type RelayDocsProps }// Ballmac UI: Relay pricing page. https://ui.ballmac.com/templates/template-relay
"use client"
import * as React from "react"
import { Check, Minus } from "lucide-react"
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ballmac/accordion"
import { Slider } from "@/components/ballmac/slider"
import { RelayShell, type RelayHrefs } from "@/components/ballmac/templates/relay/relay-theme"
import { cn } from "@/lib/utils"
const mono = { fontFamily: "var(--relay-mono)" } as const
const money = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 })
const moneyExact = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 2 })
const count = new Intl.NumberFormat("en-US")
const plans = [
{ name: "Free", price: "$0", per: "forever", events: "100,000 events / mo", cta: "Start free", features: ["3 endpoints", "Retries for 24 hours", "7-day attempt history", "Community support"] },
{ name: "Pro", price: "$49", per: "per month", events: "1M events / mo", cta: "Start Pro", featured: true, features: ["Unlimited endpoints", "Retries for 3 days", "90-day attempt history", "Replay and transformations", "Email support"] },
{ name: "Scale", price: "Custom", per: "annual", events: "Billions of events", cta: "Contact sales", features: ["Dedicated delivery pool", "mTLS and IP allow-lists", "1-year retention", "99.99% SLA", "Shared Slack channel"] },
]
const rows: { feature: string; free: boolean | string; pro: boolean | string; scale: boolean | string }[] = [
{ feature: "Endpoints", free: "3", pro: "Unlimited", scale: "Unlimited" },
{ feature: "Retry window", free: "24 hours", pro: "3 days", scale: "7 days" },
{ feature: "Attempt history", free: "7 days", pro: "90 days", scale: "1 year" },
{ feature: "Replay", free: false, pro: true, scale: true },
{ feature: "Transformations", free: false, pro: true, scale: true },
{ feature: "Per-endpoint rate limits", free: false, pro: true, scale: true },
{ feature: "mTLS and IP allow-lists", free: false, pro: false, scale: true },
{ feature: "Uptime SLA", free: false, pro: "99.9%", scale: "99.99%" },
]
const faqs = [
{ q: "What is an event?", a: "One message you send to Relay, no matter how many endpoints it fans out to or how many retries it needs. Replays count as new events." },
{ q: "What happens above my plan’s events?", a: "You pay the rate shown in the calculator. Nothing is throttled, and you can set a hard cap that pauses sending instead." },
{ q: "Can I try Pro before paying?", a: "Every account starts with a 14-day Pro trial, no card required. You fall back to Free afterwards." },
]
function Cell({ v }: { v: boolean | string }) {
if (typeof v === "string") return <span className="text-sm">{v}</span>
return v ? <Check className="text-chart-2 mx-auto size-4" aria-label="Included" role="img" /> : <Minus className="text-muted-foreground mx-auto size-4" aria-label="Not included" role="img" />
}
type RelayPricingProps = React.ComponentProps<"div"> & { hrefs?: Partial<RelayHrefs> }
/** Relay pricing: plans, a per-volume estimator with a stepped rate, a comparison table and questions. */
function RelayPricing({ hrefs, ...props }: RelayPricingProps) {
const [events, setEvents] = React.useState(3_000_000)
const included = 1_000_000
const extra = Math.max(0, events - included)
const t1 = Math.min(extra, 4_000_000)
const t2 = Math.max(0, extra - 4_000_000)
const overage = (t1 / 1_000_000) * 8 + (t2 / 1_000_000) * 5
const total = 49 + overage
return (
<RelayShell page="pricing" hrefs={hrefs} {...props}>
<main>
<section className="border-b">
<div className="mx-auto max-w-6xl px-4 py-16 sm:px-6 sm:py-24">
<p className="text-chart-1 text-xs font-semibold tracking-wider uppercase" style={mono}>Pricing</p>
<h1 className="mt-4 max-w-3xl text-4xl font-semibold tracking-[-0.05em] text-balance sm:text-6xl">Priced per event. Cheaper as you grow.</h1>
<p className="text-muted-foreground mt-5 max-w-xl text-lg text-pretty">No seats, no endpoint fees, no surprise tiers. Every plan gets the full delivery engine.</p>
<div className="mt-12 grid overflow-hidden rounded-lg border lg:grid-cols-3">
{plans.map((p) => (
<article key={p.name} className={cn("bg-card flex flex-col border-b p-7 last:border-b-0 lg:border-e lg:border-b-0 lg:last:border-e-0", p.featured && "bg-surface")}>
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold" style={mono}>{p.name}</h2>
{p.featured && <span className="bg-chart-1 text-background rounded-sm px-2 py-0.5 text-[11px] font-semibold" style={mono}>POPULAR</span>}
</div>
<p className="mt-6 flex items-baseline gap-2"><span className="text-5xl font-semibold tracking-[-0.05em]">{p.price}</span><span className="text-muted-foreground text-sm">{p.per}</span></p>
<p className="text-muted-foreground mt-2 text-sm" style={mono}>{p.events}</p>
<a href="#" className={cn("focus-visible:ring-ring/50 mt-6 inline-flex h-11 items-center justify-center rounded-md text-sm font-semibold outline-none transition-colors focus-visible:ring-[3px]", p.featured ? "bg-foreground text-background hover:opacity-90" : "hover:bg-accent border")} style={mono}>{p.cta}</a>
<ul className="mt-7 space-y-3 text-sm">
{p.features.map((f) => <li key={f} className="flex items-start gap-2.5"><Check className="text-chart-2 mt-0.5 size-4 shrink-0" aria-hidden="true" />{f}</li>)}
</ul>
</article>
))}
</div>
</div>
</section>
<section className="border-b" aria-labelledby="relay-estimator">
<div className="mx-auto grid max-w-6xl gap-10 px-4 py-16 sm:px-6 lg:grid-cols-[1.2fr_1fr] lg:gap-16">
<div>
<h2 id="relay-estimator" className="text-2xl font-semibold tracking-[-0.03em]">Estimate your Pro bill</h2>
<p className="text-muted-foreground mt-2 text-sm text-pretty">$49 includes 1M events. Then $8 per million up to 5M, and $5 per million beyond.</p>
<div className="mt-10">
<div className="flex items-baseline justify-between"><label id="relay-events" className="text-muted-foreground text-sm">Events per month</label><output className="text-xl font-medium tabular-nums" style={mono} aria-live="polite">{count.format(events)}</output></div>
<Slider className="mt-5" aria-labelledby="relay-events" min={500_000} max={50_000_000} step={500_000} value={[events]} onValueChange={(v) => setEvents(v[0] ?? events)} formatValue={(v) => `${count.format(v)} events`} />
<div className="text-muted-foreground mt-2 flex justify-between text-xs" style={mono}><span>500k</span><span>50M</span></div>
</div>
</div>
<div className="bg-card space-y-3 rounded-lg border p-5 text-sm" style={mono}>
<dl className="space-y-3">
<div className="flex justify-between"><dt className="text-muted-foreground">Pro base</dt><dd>$49.00</dd></div>
<div className="flex justify-between"><dt className="text-muted-foreground">{count.format(extra)} extra events</dt><dd className="tabular-nums">{moneyExact.format(overage)}</dd></div>
<div className="flex items-baseline justify-between border-t pt-4"><dt className="font-semibold">Per month</dt><dd className="text-3xl font-semibold tabular-nums">{money.format(total)}</dd></div>
</dl>
<p className="text-muted-foreground text-xs" style={{ fontFamily: "var(--relay-sans)" }}>Effective rate: {moneyExact.format(total / (events / 1_000_000))} per million events.</p>
</div>
</div>
</section>
<section className="border-b">
<div className="mx-auto max-w-6xl px-4 py-16 sm:px-6"><div tabIndex={0} role="region" aria-label="Plan comparison" className="focus-visible:ring-ring/50 overflow-x-auto rounded-md outline-none focus-visible:ring-[3px]">
<table className="w-full min-w-[34rem] text-start">
<caption className="sr-only">Plan comparison</caption>
<thead>
<tr className="border-b text-xs tracking-wider uppercase" style={mono}>
<th scope="col" className="py-3 pe-4 font-semibold">Feature</th>
{plans.map((p) => <th key={p.name} scope="col" className="px-4 py-3 text-center font-semibold">{p.name}</th>)}
</tr>
</thead>
<tbody>
{rows.map((r) => (
<tr key={r.feature} className="border-b">
<th scope="row" className="py-3.5 pe-4 text-sm font-normal">{r.feature}</th>
<td className="px-4 py-3.5 text-center"><Cell v={r.free} /></td>
<td className="bg-surface px-4 py-3.5 text-center"><Cell v={r.pro} /></td>
<td className="px-4 py-3.5 text-center"><Cell v={r.scale} /></td>
</tr>
))}
</tbody>
</table>
</div></div>
</section>
<section className="mx-auto max-w-3xl px-4 py-16 sm:px-6" aria-labelledby="relay-faq">
<h2 id="relay-faq" className="text-2xl font-semibold tracking-[-0.03em]">Questions</h2>
<Accordion type="single" collapsible className="mt-5">
{faqs.map((f) => (
<AccordionItem key={f.q} value={f.q}><AccordionTrigger>{f.q}</AccordionTrigger><AccordionContent className="text-muted-foreground text-pretty">{f.a}</AccordionContent></AccordionItem>
))}
</Accordion>
</section>
</main>
</RelayShell>
)
}
export { RelayPricing, type RelayPricingProps }// Ballmac UI: Relay status page. https://ui.ballmac.com/templates/template-relay
import * as React from "react"
import { CheckCircle2 } from "lucide-react"
import { RelayShell, type RelayHrefs } from "@/components/ballmac/templates/relay/relay-theme"
import { cn } from "@/lib/utils"
const mono = { fontFamily: "var(--relay-mono)" } as const
type Day = "up" | "degraded" | "down"
/** Ninety days of history. Fixed, so the server and browser draw the same bars. */
function history(seed: number, degraded: number[], down: number[]): Day[] {
return Array.from({ length: 90 }, (_, i) => (down.includes(i + seed) ? "down" : degraded.includes(i + seed) ? "degraded" : "up"))
}
const components: { name: string; uptime: string; days: Day[] }[] = [
{ name: "Event ingestion API", uptime: "99.998%", days: history(0, [41], []) },
{ name: "Delivery workers", uptime: "99.991%", days: history(0, [12, 57], [58]) },
{ name: "Dashboard", uptime: "99.987%", days: history(0, [23, 24, 71], []) },
{ name: "Replay and search", uptime: "99.996%", days: history(0, [66], []) },
{ name: "Webhooks to customers (global)", uptime: "99.994%", days: history(0, [57, 58], []) },
]
const incidents = [
{ date: "Sep 24, 2026", title: "Delayed deliveries in eu-central", status: "Resolved", body: "A partial network failure in Frankfurt slowed deliveries by up to 4 minutes. Events were queued and delivered in order; nothing was lost.", duration: "37 minutes" },
{ date: "Aug 15, 2026", title: "Dashboard search timeouts", status: "Resolved", body: "Searches over 30 days of history timed out for 11 minutes after an index rebuild. Ingestion and delivery were unaffected.", duration: "11 minutes" },
{ date: "Jul 29, 2026", title: "Elevated 5xx on the ingestion API", status: "Resolved", body: "A bad deploy returned errors for 0.4 percent of requests. We rolled back in 6 minutes. Clients using idempotency keys retried safely.", duration: "6 minutes" },
]
const tone: Record<Day, string> = { up: "bg-chart-2", degraded: "bg-chart-3", down: "bg-destructive" }
const word: Record<Day, string> = { up: "operational", degraded: "degraded", down: "outage" }
type RelayStatusProps = React.ComponentProps<"div"> & { hrefs?: Partial<RelayHrefs> }
/** Relay status: an overall banner, 90-day uptime bars per component and recent incidents. */
function RelayStatus({ hrefs, ...props }: RelayStatusProps) {
return (
<RelayShell page="status" hrefs={hrefs} {...props}>
<main className="mx-auto max-w-4xl px-4 py-14 sm:px-6 sm:py-20">
<p className="text-chart-1 text-xs font-semibold tracking-wider uppercase" style={mono}>System status</p>
<h1 className="mt-4 text-4xl font-semibold tracking-[-0.05em] text-balance sm:text-5xl">Status</h1>
<div role="status" className="border-chart-2/40 bg-chart-2/10 mt-8 flex items-center gap-3 rounded-lg border px-5 py-4">
<CheckCircle2 className="text-chart-2 size-6 shrink-0" aria-hidden="true" />
<div>
<p className="font-semibold">All systems operational</p>
<p className="text-muted-foreground text-sm">Updated 09:41 UTC · Median delivery latency 38 ms</p>
</div>
</div>
<section aria-labelledby="relay-uptime" className="mt-12">
<h2 id="relay-uptime" className="text-xl font-semibold tracking-[-0.02em]">Uptime, last 90 days</h2>
<ul className="mt-5 space-y-3">
{components.map((c) => (
<li key={c.name} className="bg-card rounded-lg border p-5">
<div className="flex items-baseline justify-between gap-3">
<h3 className="font-medium">{c.name}</h3>
<span className="text-chart-2 text-sm tabular-nums" style={mono}>{c.uptime}</span>
</div>
<div className="mt-4 flex h-8 gap-px" role="img" aria-label={`${c.name}: ${c.days.filter((d) => d === "up").length} of 90 days operational, ${c.days.filter((d) => d === "degraded").length} degraded, ${c.days.filter((d) => d === "down").length} outage`}>
{c.days.map((d, i) => <span key={i} className={cn("min-w-0 flex-1 rounded-[1px]", tone[d])} title={`${90 - i} days ago: ${word[d]}`} />)}
</div>
<div className="text-muted-foreground mt-2 flex justify-between text-xs" style={mono}><span>90 days ago</span><span>Today</span></div>
</li>
))}
</ul>
<ul className="text-muted-foreground mt-4 flex flex-wrap gap-x-5 gap-y-1 text-xs">
{(["up", "degraded", "down"] as Day[]).map((d) => <li key={d} className="flex items-center gap-1.5"><span className={cn("size-2.5 rounded-[2px]", tone[d])} aria-hidden="true" />{word[d][0].toUpperCase() + word[d].slice(1)}</li>)}
</ul>
</section>
<section aria-labelledby="relay-incidents" className="mt-14">
<h2 id="relay-incidents" className="text-xl font-semibold tracking-[-0.02em]">Past incidents</h2>
<ol className="mt-5 space-y-6">
{incidents.map((i) => (
<li key={i.title} className="border-s-2 ps-5">
<p className="text-muted-foreground text-xs" style={mono}>{i.date} · {i.duration}</p>
<h3 className="mt-1 font-semibold">{i.title} <span className="bg-chart-2/15 ms-1 rounded px-1.5 py-0.5 text-[11px] font-medium">{i.status}</span></h3>
<p className="text-muted-foreground mt-2 text-sm text-pretty">{i.body}</p>
</li>
))}
</ol>
</section>
</main>
</RelayShell>
)
}
export { RelayStatus, type RelayStatusProps }// Ballmac UI: Relay template route. https://ui.ballmac.com/templates/template-relay
import type { Metadata } from "next"
import { RelayHome } from "@/components/ballmac/templates/relay/relay-home"
export const metadata: Metadata = {
title: "Relay: Never lose a webhook again",
description: "Reliable webhook delivery with retries, signatures, replay and a full audit trail.",
}
export default function Page() {
return <RelayHome />
}// Ballmac UI: Relay template route. https://ui.ballmac.com/templates/template-relay
import type { Metadata } from "next"
import { RelayDocs } from "@/components/ballmac/templates/relay/relay-docs"
export const metadata: Metadata = {
title: "Relay docs: Quickstart",
description: "Send your first event and verify it in two minutes.",
}
export default function Page() {
return <RelayDocs />
}// Ballmac UI: Relay template route. https://ui.ballmac.com/templates/template-relay
import type { Metadata } from "next"
import { RelayPricing } from "@/components/ballmac/templates/relay/relay-pricing"
export const metadata: Metadata = {
title: "Relay pricing",
description: "Priced per event, and cheaper as you grow.",
}
export default function Page() {
return <RelayPricing />
}// Ballmac UI: Relay template route. https://ui.ballmac.com/templates/template-relay
import type { Metadata } from "next"
import { RelayStatus } from "@/components/ballmac/templates/relay/relay-status"
export const metadata: Metadata = {
title: "Relay status",
description: "Uptime and incidents for every Relay component.",
}
export default function Page() {
return <RelayStatus />
}Installation
$ pnpm dlx shadcn@latest add @ballmac/template-relayInstall the dependencies.
$ pnpm add lucide-reactAdd the Ballmac items it builds on.
$ pnpm dlx shadcn@latest add @ballmac/accordion @ballmac/api-endpoint @ballmac/dotted-map @ballmac/marquee @ballmac/number-ticker @ballmac/slider @ballmac/snippet-tabs @ballmac/table-of-contentsCopy the source into your project.
components/ballmac/templates/relay/relay-fonts.ts// Ballmac UI: Relay template fonts. https://ui.ballmac.com/templates/template-relay import { Hanken_Grotesk, IBM_Plex_Mono } from "next/font/google" export const relaySans = Hanken_Grotesk({ variable: "--relay-sans", subsets: ["latin"], display: "swap", preload: false }) export const relayMono = IBM_Plex_Mono({ variable: "--relay-mono", subsets: ["latin"], weight: ["400", "500", "600"], display: "swap", preload: false })components/ballmac/templates/relay/relay-theme.tsx// Ballmac UI: Relay template shell. https://ui.ballmac.com/templates/template-relay "use client" import * as React from "react" import { ArrowUpRight, Menu, X } from "lucide-react" import { cn } from "@/lib/utils" import { relayMono, relaySans } from "@/components/ballmac/templates/relay/relay-fonts" type RelayPage = "home" | "docs" | "pricing" | "status" type RelayHrefs = Record<RelayPage, string> const defaultHrefs: RelayHrefs = { home: "/relay", docs: "/relay/docs", pricing: "/relay/pricing", status: "/relay/status" } /** Relay's palette: warm paper and black ink with a single orange signal. Dark mode inverts to ink. */ const relayCss = ` .relay-theme{--background:oklch(0.975 0.008 85);--foreground:oklch(0.17 0.01 60);--card:oklch(0.99 0.006 85);--card-foreground:oklch(0.17 0.01 60);--popover:oklch(0.99 0.006 85);--popover-foreground:oklch(0.17 0.01 60);--primary:oklch(0.17 0.01 60);--primary-foreground:oklch(0.975 0.008 85);--secondary:oklch(0.94 0.012 85);--secondary-foreground:oklch(0.17 0.01 60);--muted:oklch(0.94 0.012 85);--muted-foreground:oklch(0.48 0.02 70);--accent:oklch(0.93 0.016 80);--accent-foreground:oklch(0.17 0.01 60);--border:oklch(0.17 0.01 60 / 14%);--input:oklch(0.17 0.01 60 / 20%);--ring:oklch(0.62 0.2 45);--surface:oklch(0.96 0.01 85);--destructive:oklch(0.55 0.22 27);--chart-1:oklch(0.52 0.19 42);--chart-2:oklch(0.46 0.12 160);--chart-3:oklch(0.7 0.15 85);--chart-4:oklch(0.5 0.17 265);--chart-5:oklch(0.55 0.2 330);--radius:0.375rem} .dark .relay-theme{--background:oklch(0.15 0.008 60);--foreground:oklch(0.95 0.01 85);--card:oklch(0.185 0.01 60);--card-foreground:oklch(0.95 0.01 85);--popover:oklch(0.2 0.01 60);--popover-foreground:oklch(0.95 0.01 85);--primary:oklch(0.95 0.01 85);--primary-foreground:oklch(0.17 0.01 60);--secondary:oklch(0.24 0.012 60);--secondary-foreground:oklch(0.95 0.01 85);--muted:oklch(0.23 0.012 60);--muted-foreground:oklch(0.72 0.02 80);--accent:oklch(0.27 0.014 60);--accent-foreground:oklch(0.95 0.01 85);--border:oklch(0.95 0.01 85 / 14%);--input:oklch(0.95 0.01 85 / 20%);--ring:oklch(0.72 0.19 50);--surface:oklch(0.17 0.009 60);--destructive:oklch(0.7 0.19 25);--chart-1:oklch(0.74 0.18 50);--chart-2:oklch(0.78 0.14 165);--chart-3:oklch(0.83 0.14 90);--chart-4:oklch(0.72 0.14 265);--chart-5:oklch(0.74 0.17 335)} ` function RelayMark({ className }: { className?: string }) { return ( <svg viewBox="0 0 28 28" fill="none" aria-hidden="true" className={cn("size-6", className)}> <rect x="1.5" y="1.5" width="25" height="25" rx="5" stroke="currentColor" strokeWidth="1.8" /> <path d="M7 14h6l3-5 3 10 3-5h2" stroke="var(--chart-1)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> </svg> ) } const links: { key: RelayPage; label: string }[] = [ { key: "docs", label: "Docs" }, { key: "pricing", label: "Pricing" }, { key: "status", label: "Status" }, ] type RelayShellProps = React.ComponentProps<"div"> & { /** The page being shown, so its nav link is marked current. */ page: RelayPage /** Override where pages live (used by previews). */ hrefs?: Partial<RelayHrefs> } /** Relay's frame: paper-and-ink theme, fonts, a ruled header and footer. Wrap every Relay page in it. */ function RelayShell({ page, hrefs: overrides, className, style, children, ...props }: RelayShellProps) { const hrefs = { ...defaultHrefs, ...overrides } const [open, setOpen] = React.useState(false) return ( <div data-slot="relay" className={cn("relay-theme bg-background text-foreground relative min-h-dvh overflow-x-clip", relaySans.variable, relayMono.variable, className)} style={{ fontFamily: "var(--relay-sans), ui-sans-serif, system-ui, sans-serif", ...style }} {...props} > <style>{relayCss}</style> <header className="bg-background/85 sticky top-0 z-40 border-b backdrop-blur-xl"> <div className="mx-auto flex h-14 max-w-6xl items-center justify-between px-4 sm:px-6"> <a href={hrefs.home} className="focus-visible:ring-ring/50 flex items-center gap-2.5 rounded-sm font-semibold tracking-tight outline-none focus-visible:ring-[3px]"> <RelayMark /> <span style={{ fontFamily: "var(--relay-mono)" }}>relay</span> </a> <nav aria-label="Main" className="hidden items-center gap-1 md:flex"> {links.map((l) => ( <a key={l.key} href={hrefs[l.key]} aria-current={page === l.key ? "page" : undefined} className="text-muted-foreground hover:text-foreground aria-[current=page]:text-foreground aria-[current=page]:bg-accent focus-visible:ring-ring/50 rounded-md px-3 py-1.5 text-sm outline-none transition-colors focus-visible:ring-[3px]" > {l.label} </a> ))} <a href="#" className="text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 inline-flex items-center gap-1 rounded-md px-3 py-1.5 text-sm outline-none transition-colors focus-visible:ring-[3px]"> GitHub <ArrowUpRight className="size-3.5 rtl:-scale-x-100" aria-hidden="true" /> </a> </nav> <div className="flex items-center gap-2"> <a href="#" className="text-muted-foreground hover:text-foreground hidden px-2 text-sm sm:block">Log in</a> <a href="#" className="bg-foreground text-background focus-visible:ring-ring/50 inline-flex h-9 items-center rounded-md px-3.5 text-sm font-medium outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]" style={{ fontFamily: "var(--relay-mono)" }}> Get API key </a> <button type="button" aria-label={open ? "Close menu" : "Open menu"} aria-expanded={open} aria-controls="relay-mobile-menu" onClick={() => setOpen((v) => !v)} className="text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 inline-flex size-9 items-center justify-center rounded-md outline-none focus-visible:ring-[3px] md:hidden" > {open ? <X className="size-5" aria-hidden="true" /> : <Menu className="size-5" aria-hidden="true" />} </button> </div> </div> {open && ( <nav id="relay-mobile-menu" aria-label="Mobile" className="border-t px-4 py-2 md:hidden"> {links.map((l) => ( <a key={l.key} href={hrefs[l.key]} className="hover:bg-accent block rounded-md px-3 py-2.5 text-sm">{l.label}</a> ))} </nav> )} </header> {children} <footer className="border-t"> <div className="mx-auto grid max-w-6xl gap-10 px-4 py-14 sm:grid-cols-2 sm:px-6 lg:grid-cols-[1.4fr_repeat(3,1fr)]"> <div> <a href={hrefs.home} className="flex items-center gap-2.5 font-semibold"><RelayMark /> <span style={{ fontFamily: "var(--relay-mono)" }}>relay</span></a> <p className="text-muted-foreground mt-3 max-w-xs text-sm text-pretty">Reliable webhook delivery for teams that can’t afford to drop an event.</p> </div> {[ { title: "Product", items: [["Delivery", "home"], ["Retries", "home"], ["Replay", "home"], ["Pricing", "pricing"]] }, { title: "Developers", items: [["Documentation", "docs"], ["API reference", "docs"], ["SDKs", "docs"], ["Status", "status"]] }, { title: "Company", items: [["About", "home"], ["Security", "home"], ["Careers", "home"], ["Contact", "home"]] }, ].map((c) => ( <div key={c.title}> <h2 className="text-xs font-semibold tracking-wider uppercase" style={{ fontFamily: "var(--relay-mono)" }}>{c.title}</h2> <ul className="mt-4 space-y-2.5"> {c.items.map(([label, key]) => ( <li key={label}><a href={hrefs[key as RelayPage]} className="text-muted-foreground hover:text-foreground text-sm transition-colors">{label}</a></li> ))} </ul> </div> ))} </div> <div className="mx-auto max-w-6xl border-t px-4 py-5 sm:px-6"> <p className="text-muted-foreground text-xs" style={{ fontFamily: "var(--relay-mono)" }}>© 2026 Relay, Inc. · SOC 2 Type II · 99.99% delivery SLA</p> </div> </footer> </div> ) } export { RelayMark, RelayShell, defaultHrefs as relayDefaultHrefs, type RelayHrefs, type RelayPage, type RelayShellProps }components/ballmac/templates/relay/relay-samples.ts// Ballmac UI: Relay code samples. https://ui.ballmac.com/templates/template-relay import type { Snippet } from "@/components/ballmac/snippet-tabs" /** The package the samples install. Kept in a variable so it is documentation, not a dependency of this file. */ const pkg = "@relay/sdk" /** Sending an event, in four languages. */ export const sendSnippets: Snippet[] = [ { label: "curl", language: "bash", code: `curl https://api.relay.dev/v1/events \\ -H "Authorization: Bearer {{key}}" \\ -H "Content-Type: application/json" \\ -d '{ "type": "invoice.paid", "endpoint": "ep_billing", "data": { "id": "inv_1042", "amount": 4900 } }'`, }, { label: "TypeScript", language: "typescript", code: `import { Relay } from ${JSON.stringify(pkg)} const relay = new Relay("{{key}}") await relay.events.send({ type: "invoice.paid", endpoint: "ep_billing", data: { id: "inv_1042", amount: 4900 }, })`, }, { label: "Python", language: "python", code: `from relay import Relay relay = Relay("{{key}}") relay.events.send( type="invoice.paid", endpoint="ep_billing", data={"id": "inv_1042", "amount": 4900}, )`, }, { label: "Go", language: "go", code: `client := relay.New("{{key}}") _, err := client.Events.Send(ctx, relay.Event{ Type: "invoice.paid", Endpoint: "ep_billing", Data: map[string]any{"id": "inv_1042", "amount": 4900}, })`, }, ] /** Verifying a signature, in four languages. */ export const verifySnippets: Snippet[] = [ { label: "curl", language: "bash", code: `# Recompute the signature yourself to check a delivery SIGNED="$TIMESTAMP.$BODY" printf '%s' "$SIGNED" | openssl dgst -sha256 -hmac "$RELAY_SECRET" # The result must match the Relay-Signature header`, }, { label: "TypeScript", language: "typescript", code: `import { verify } from ${JSON.stringify(pkg)} export async function POST(req: Request) { const body = await req.text() const event = verify(body, req.headers, process.env.RELAY_SECRET!) // event.id is stable across retries: use it to dedupe return new Response("ok") }`, }, { label: "Python", language: "python", code: `from relay import verify @app.post("/webhooks") def webhook(request): event = verify(request.body, request.headers, RELAY_SECRET) # event.id is stable across retries: use it to dedupe return "ok"`, }, { label: "Go", language: "go", code: `func webhook(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) event, err := relay.Verify(body, r.Header, secret) // event.ID is stable across retries: use it to dedupe w.WriteHeader(http.StatusOK) }`, }, ]components/ballmac/templates/relay/relay-home.tsx// Ballmac UI: Relay home page. https://ui.ballmac.com/templates/template-relay "use client" import * as React from "react" import { ArrowRight, Check, Clock, History, KeyRound, ListRestart, Radar, ShieldCheck, Shuffle, X } from "lucide-react" import { DottedMap } from "@/components/ballmac/dotted-map" import { Marquee } from "@/components/ballmac/marquee" import { NumberTicker } from "@/components/ballmac/number-ticker" import { SnippetTabs } from "@/components/ballmac/snippet-tabs" import { RelayShell, type RelayHrefs } from "@/components/ballmac/templates/relay/relay-theme" import { sendSnippets, verifySnippets } from "@/components/ballmac/templates/relay/relay-samples" import { cn } from "@/lib/utils" const mono = { fontFamily: "var(--relay-mono)" } as const const label = "text-muted-foreground text-xs font-semibold tracking-wider uppercase" const attempts = [ { n: 1, t: "0.0 s", status: "503", note: "billing.acme.co unavailable", ok: false }, { n: 2, t: "+30 s", status: "timeout", note: "No response in 10 s", ok: false }, { n: 3, t: "+5 min", status: "200", note: "Delivered in 142 ms", ok: true }, ] const features = [ { icon: ListRestart, title: "Retries that make sense", text: "Exponential backoff with jitter for 3 days. Circuit breakers pause a sick endpoint instead of hammering it." }, { icon: ShieldCheck, title: "Signed and verifiable", text: "Every delivery is signed with a rotating secret and a timestamp. Verifying takes five lines in any language." }, { icon: History, title: "Replay anything", text: "Re-send one event or a whole day to any endpoint, including your laptop, with one click or one command." }, { icon: Shuffle, title: "Transform in flight", text: "Reshape payloads, filter by field and fan out to many endpoints without changing your producer." }, { icon: Radar, title: "See every attempt", text: "Request, response, headers and latency for each try, searchable for 90 days and exportable to your logs." }, { icon: KeyRound, title: "Per-endpoint controls", text: "Rate limits, IP allow-lists and mTLS for each destination, so one slow consumer never slows the rest." }, ] const regions = [ { name: "iad1", city: "Virginia", ms: 11, lat: 38.9, lng: -77.4 }, { name: "sfo1", city: "San Francisco", ms: 14, lat: 37.7, lng: -122.4 }, { name: "gru1", city: "São Paulo", ms: 23, lat: -23.5, lng: -46.6 }, { name: "fra1", city: "Frankfurt", ms: 9, lat: 50.1, lng: 8.7 }, { name: "bom1", city: "Mumbai", ms: 19, lat: 19.1, lng: 72.9 }, { name: "sin1", city: "Singapore", ms: 12, lat: 1.35, lng: 103.8 }, { name: "syd1", city: "Sydney", ms: 16, lat: -33.9, lng: 151.2 }, ] function Console() { const [state, setState] = React.useState<"idle" | "sending" | "done">("idle") const timer = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined) React.useEffect(() => () => clearTimeout(timer.current), []) function send() { setState("sending") clearTimeout(timer.current) timer.current = setTimeout(() => setState("done"), 650) } return ( <div className="bg-card min-w-0 overflow-hidden rounded-lg border shadow-[6px_6px_0_0_var(--border)]"> <SnippetTabs snippets={sendSnippets} storageKey="relay-language" variables={{ key: "rl_live_8f3k…" }} title="Send an event" bodyClassName="max-h-64" /> <div className="flex items-center justify-between gap-3 border-t px-4 py-3"> <p className="text-muted-foreground text-xs" style={mono} aria-live="polite"> {state === "idle" ? "POST /v1/events" : state === "sending" ? "Sending…" : "202 Accepted · 38 ms · iad1"} </p> <button type="button" onClick={send} disabled={state === "sending"} className="bg-chart-1 text-background focus-visible:ring-ring/50 inline-flex h-9 items-center gap-1.5 rounded-md px-3.5 text-sm font-semibold outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px] disabled:opacity-60" style={mono} > {state === "done" ? "Send again" : "Send"} <ArrowRight className="size-4 rtl:rotate-180" aria-hidden="true" /> </button> </div> {state === "done" && ( <div className="bg-surface border-t px-4 py-4 text-xs" style={mono}> <p className="text-chart-2 font-semibold">HTTP/1.1 202 Accepted</p> <pre className="text-muted-foreground mt-2 overflow-x-auto" tabIndex={0}>{`{ "id": "evt_9x2k", "status": "queued", "attempts": [], "endpoint": "ep_billing" }`}</pre> </div> )} </div> ) } type RelayHomeProps = React.ComponentProps<"div"> & { hrefs?: Partial<RelayHrefs> } /** The Relay home page: hero with a live console, the life of an event, features, a delivery map and code. */ function RelayHome({ hrefs, ...props }: RelayHomeProps) { return ( <RelayShell page="home" hrefs={hrefs} {...props}> <main> <section className="relative border-b"> <div aria-hidden="true" className="absolute inset-0 [background-image:linear-gradient(var(--border)_1px,transparent_1px),linear-gradient(90deg,var(--border)_1px,transparent_1px)] [background-size:56px_56px] [mask-image:linear-gradient(to_bottom,black,transparent_85%)]" /> <div className="relative mx-auto grid max-w-6xl items-center gap-14 px-4 py-16 sm:px-6 sm:py-24 lg:grid-cols-[1.05fr_1fr]"> <div> <p className={cn(label, "text-chart-1 flex items-center gap-2")} style={mono}><span className="bg-chart-1 size-1.5 rounded-full" aria-hidden="true" /> Webhook infrastructure</p> <h1 className="mt-5 text-5xl leading-[0.98] font-semibold tracking-[-0.05em] text-balance sm:text-7xl">Never lose a webhook again.</h1> <p className="text-muted-foreground mt-6 max-w-lg text-lg text-pretty"> Relay delivers your events to every customer endpoint with retries, signatures, replay and a complete audit trail. You send one request. We make sure it lands. </p> <div className="mt-9 flex flex-wrap items-center gap-3"> <a href="#" className="bg-foreground text-background focus-visible:ring-ring/50 inline-flex h-12 items-center gap-2 rounded-md px-5 font-medium outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]" style={mono}> Get your API key <ArrowRight className="size-4 rtl:rotate-180" aria-hidden="true" /> </a> <a href={hrefs?.docs ?? "/relay/docs"} className="hover:bg-accent focus-visible:ring-ring/50 inline-flex h-12 items-center rounded-md border px-5 font-medium outline-none transition-colors focus-visible:ring-[3px]" style={mono}> Read the docs </a> </div> <p className="text-muted-foreground mt-6 text-sm">Free up to 100,000 events a month. No card needed.</p> </div> <Console /> </div> </section> <section aria-label="Customers" className="border-b"> <div className="mx-auto max-w-6xl px-4 py-9 sm:px-6"> <Marquee speed={28} gap={64} fade pauseOnHover> {["Stripeline", "Northbeam", "Cashew", "Lumen", "Paddock", "Vantage", "Ironclad Co", "Helix"].map((c) => ( <span key={c} className="text-muted-foreground text-lg font-semibold tracking-tight" style={mono}>{c}</span> ))} </Marquee> </div> </section> {/* Life of an event */} <section className="border-b"> <div className="mx-auto grid max-w-6xl gap-12 px-4 py-20 sm:px-6 lg:grid-cols-[1fr_1.1fr]"> <div> <p className={label} style={mono}>The life of an event</p> <h2 className="mt-4 text-3xl font-semibold tracking-[-0.04em] text-balance sm:text-5xl">Your customer’s server was down. Your event still arrived.</h2> <p className="text-muted-foreground mt-5 max-w-md text-pretty sm:text-lg"> Endpoints fail all the time. Relay keeps trying on a schedule that respects the receiver, then tells you exactly what happened. </p> </div> <ol className="bg-card rounded-lg border" aria-label="Delivery attempts for evt_9x2k"> <li className="flex items-center justify-between gap-4 border-b px-5 py-4 text-sm" style={mono}> <span>evt_9x2k · invoice.paid</span> <span className="text-muted-foreground">received 09:41:02</span> </li> {attempts.map((a) => ( <li key={a.n} className="flex items-start gap-4 border-b px-5 py-4 last:border-b-0"> <span className={cn("mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-full border", a.ok ? "border-chart-2 text-chart-2" : "border-destructive/50 text-destructive")}> {a.ok ? <Check className="size-3.5" aria-hidden="true" /> : <X className="size-3.5" aria-hidden="true" />} <span className="sr-only">{a.ok ? "Delivered" : "Failed"}</span> </span> <div className="min-w-0 flex-1"> <p className="text-sm font-medium">Attempt {a.n} <span className="text-muted-foreground font-normal">· {a.status}</span></p> <p className="text-muted-foreground mt-0.5 text-sm">{a.note}</p> </div> <span className="text-muted-foreground flex shrink-0 items-center gap-1.5 text-xs" style={mono}><Clock className="size-3" aria-hidden="true" />{a.t}</span> </li> ))} </ol> </div> </section> {/* Features in a ruled grid */} <section className="border-b"> <div className="mx-auto max-w-6xl px-4 py-20 sm:px-6"> <p className={label} style={mono}>What you get</p> <h2 className="mt-4 max-w-2xl text-3xl font-semibold tracking-[-0.04em] text-balance sm:text-5xl">Everything between “send” and “received”.</h2> <ul className="mt-12 grid overflow-hidden rounded-lg border sm:grid-cols-2 lg:grid-cols-3"> {features.map((f, i) => ( <li key={f.title} className={cn("bg-card p-7", "border-b sm:[&:nth-child(odd)]:border-e lg:[&:nth-child(odd)]:border-e-0 lg:[&:not(:nth-child(3n))]:border-e", i >= 4 && "sm:border-b-0", i >= 3 && "lg:border-b-0", i < 3 && "lg:border-b")}> <f.icon className="text-chart-1 size-6" aria-hidden="true" /> <h3 className="mt-6 font-semibold">{f.title}</h3> <p className="text-muted-foreground mt-2 text-sm text-pretty">{f.text}</p> </li> ))} </ul> </div> </section> {/* Map */} <section className="border-b"> <div className="mx-auto grid max-w-6xl items-center gap-12 px-4 py-20 sm:px-6 lg:grid-cols-[1.2fr_1fr]"> <div className="order-2 lg:order-1"> <DottedMap tone="foreground" dots={120} latRange={[-50, 70]} label="Relay delivery regions" markers={regions.map((r) => ({ lat: r.lat, lng: r.lng, label: r.city, tone: "primary" as const }))} arcs={[ { from: [38.9, -77.4], to: [50.1, 8.7], tone: "primary" }, { from: [37.7, -122.4], to: [38.9, -77.4], tone: "primary" }, { from: [50.1, 8.7], to: [19.1, 72.9], tone: "primary" }, { from: [1.35, 103.8], to: [-33.9, 151.2], tone: "primary" }, ]} className="text-muted-foreground/70" /> </div> <div className="order-1 lg:order-2"> <p className={label} style={mono}>Global delivery</p> <h2 className="mt-4 text-3xl font-semibold tracking-[-0.04em] text-balance sm:text-4xl">Seven regions. Closest one wins.</h2> <dl className="mt-8 divide-y rounded-lg border" style={mono}> {regions.map((r) => ( <div key={r.name} className="flex items-center justify-between px-4 py-2.5 text-sm"> <dt>{r.name} <span className="text-muted-foreground">· {r.city}</span></dt> <dd className="text-chart-2 tabular-nums">{r.ms} ms</dd> </div> ))} </dl> </div> </div> </section> {/* Verify */} <section className="border-b"> <div className="mx-auto grid max-w-6xl items-center gap-12 px-4 py-20 sm:px-6 lg:grid-cols-2"> <div> <p className={label} style={mono}>Receiving</p> <h2 className="mt-4 text-3xl font-semibold tracking-[-0.04em] text-balance sm:text-4xl">Verify in five lines. Dedupe for free.</h2> <p className="text-muted-foreground mt-5 max-w-md text-pretty sm:text-lg">Choose a language once. Every sample in these docs follows you.</p> </div> <div className="min-w-0"><SnippetTabs snippets={verifySnippets} storageKey="relay-language" title="Receive an event" bodyClassName="max-h-64" /></div> </div> </section> {/* Stats */} <section aria-label="Relay in numbers" className="border-b"> <dl className="mx-auto grid max-w-6xl divide-y sm:grid-cols-2 sm:divide-y-0 lg:grid-cols-4 lg:divide-x"> {[ { v: 12.8, s: "B", l: "Events delivered each month", d: 1 }, { v: 99.99, s: "%", l: "Delivery success within 3 days", d: 2 }, { v: 38, s: " ms", l: "Median time to first attempt", d: 0 }, { v: 90, s: " days", l: "Searchable attempt history", d: 0 }, ].map((x) => ( <div key={x.l} className="px-6 py-10"> <dd className="text-4xl font-semibold tracking-[-0.04em] tabular-nums" style={mono}><NumberTicker value={x.v} format={{ minimumFractionDigits: x.d, maximumFractionDigits: x.d }} />{x.s}</dd> <dt className="text-muted-foreground mt-2 text-sm">{x.l}</dt> </div> ))} </dl> </section> <section className="px-4 py-20 sm:px-6"> <div className="bg-foreground text-background mx-auto max-w-6xl rounded-lg px-6 py-16 text-center sm:py-20"> <h2 className="mx-auto max-w-2xl text-3xl font-semibold tracking-[-0.045em] text-balance sm:text-5xl">Send your first event in two minutes.</h2> <p className="mx-auto mt-4 max-w-md text-pretty opacity-70">One API key, one request. The free plan includes 100,000 events a month.</p> <a href="#" className="bg-chart-1 text-background focus-visible:ring-ring mt-8 inline-flex h-12 items-center gap-2 rounded-md px-6 font-semibold outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]" style={mono}> Get your API key <ArrowRight className="size-4 rtl:rotate-180" aria-hidden="true" /> </a> </div> </section> </main> </RelayShell> ) } export { RelayHome, type RelayHomeProps }components/ballmac/templates/relay/relay-docs.tsx// Ballmac UI: Relay docs page. https://ui.ballmac.com/templates/template-relay "use client" import * as React from "react" import { ChevronRight } from "lucide-react" import { ApiEndpoint } from "@/components/ballmac/api-endpoint" import { SnippetTabs } from "@/components/ballmac/snippet-tabs" import { TableOfContents } from "@/components/ballmac/table-of-contents" import { RelayShell, type RelayHrefs } from "@/components/ballmac/templates/relay/relay-theme" import { sendSnippets, verifySnippets } from "@/components/ballmac/templates/relay/relay-samples" import { cn } from "@/lib/utils" const mono = { fontFamily: "var(--relay-mono)" } as const const nav = [ { title: "Get started", items: ["Introduction", "Quickstart", "Authentication"] }, { title: "Concepts", items: ["Events and endpoints", "Retries and backoff", "Signatures", "Replay"] }, { title: "API reference", items: ["Send an event", "List attempts", "Replay an event"] }, { title: "Guides", items: ["Verify in Next.js", "Test locally", "Go to production"] }, ] const h2 = "scroll-mt-24 text-2xl font-semibold tracking-[-0.03em]" type RelayDocsProps = React.ComponentProps<"div"> & { hrefs?: Partial<RelayHrefs> } /** Relay docs: sidebar, a quickstart with language-synced samples, API reference and an on-page contents list. */ function RelayDocs({ hrefs, ...props }: RelayDocsProps) { return ( <RelayShell page="docs" hrefs={hrefs} {...props}> <div className="mx-auto grid max-w-6xl gap-10 px-4 sm:px-6 lg:grid-cols-[14rem_minmax(0,1fr)] xl:grid-cols-[14rem_minmax(0,1fr)_13rem]"> <aside className="hidden lg:block"> <nav aria-label="Documentation" className="sticky top-14 max-h-[calc(100dvh-3.5rem)] space-y-6 overflow-y-auto py-10 pe-2"> {nav.map((g) => ( <div key={g.title}> <h2 className="text-xs font-semibold tracking-wider uppercase" style={mono}>{g.title}</h2> <ul className="mt-2.5 space-y-0.5"> {g.items.map((i) => ( <li key={i}> <a href="#" aria-current={i === "Quickstart" ? "page" : undefined} className="text-muted-foreground hover:text-foreground aria-[current=page]:bg-accent aria-[current=page]:text-foreground block rounded-md px-2.5 py-1.5 text-sm transition-colors" > {i} </a> </li> ))} </ul> </div> ))} </nav> </aside> <main className="min-w-0 py-10 lg:py-12"> <nav aria-label="Breadcrumb" className="text-muted-foreground flex items-center gap-1 text-sm"> Get started <ChevronRight className="size-3.5 rtl:rotate-180" aria-hidden="true" /> <span className="text-foreground">Quickstart</span> </nav> <h1 className="mt-5 text-4xl font-semibold tracking-[-0.045em] text-balance sm:text-5xl">Quickstart</h1> <p className="text-muted-foreground mt-4 max-w-2xl text-lg text-pretty">Send your first event and watch it arrive. This takes about two minutes.</p> <div className="prose-none mt-12 space-y-14"> <section> <h2 id="get-a-key" className={h2}>1. Get an API key</h2> <p className="text-muted-foreground mt-3 max-w-2xl text-pretty"> Create a key in the dashboard under <span className="bg-muted rounded px-1.5 py-0.5 text-[0.85em]" style={mono}>Settings → API keys</span>. Keys starting with <span className="bg-muted rounded px-1.5 py-0.5 text-[0.85em]" style={mono}>rl_test_</span> never reach real endpoints. </p> </section> <section> <h2 id="send-an-event" className={h2}>2. Send an event</h2> <p className="text-muted-foreground mt-3 mb-5 max-w-2xl text-pretty">Pick your language. Your choice is remembered across every sample in the docs.</p> <SnippetTabs snippets={sendSnippets} storageKey="relay-language" variables={{ key: "rl_test_8f3k…" }} lineNumbers /> </section> <section> <h2 id="receive-it" className={h2}>3. Receive it</h2> <p className="text-muted-foreground mt-3 mb-5 max-w-2xl text-pretty">Verify the signature, then return a 2xx. Anything else is retried.</p> <SnippetTabs snippets={verifySnippets} storageKey="relay-language" lineNumbers /> <div className="bg-chart-1/10 border-chart-1/30 mt-6 rounded-lg border p-4 text-sm text-pretty"> <p className="font-semibold">Dedupe on event.id</p> <p className="text-muted-foreground mt-1">Relay delivers at least once. The id is stable across retries and replays, so store it and skip repeats.</p> </div> </section> <section> <h2 id="api-reference" className={h2}>API reference</h2> <div className="mt-6 space-y-4"> <ApiEndpoint method="POST" path="/v1/events" summary="Send an event" description="Queues an event for delivery to one endpoint, or every endpoint subscribed to its type." baseUrl="https://api.relay.dev" auth="Bearer token" defaultOpen parameters={[ { name: "type", in: "body", type: "string", required: true, description: "Event type, such as invoice.paid." }, { name: "endpoint", in: "body", type: "string", description: "Send to one endpoint. Omit to fan out by subscription." }, { name: "data", in: "body", type: "object", required: true, description: "The payload your customer receives." }, { name: "Idempotency-Key", in: "header", type: "string", description: "Makes retries of this request safe for 24 hours." }, ]} requestExample={`{ "type": "invoice.paid", "endpoint": "ep_billing", "data": { "id": "inv_1042", "amount": 4900 } }`} requestLanguage="json" responses={[ { status: 202, description: "Event queued", example: `{ "id": "evt_9x2k", "status": "queued" }`, language: "json" }, { status: 422, description: "Invalid payload", example: `{ "error": "data is required" }`, language: "json" }, ]} /> <ApiEndpoint method="GET" path="/v1/events/{id}/attempts" summary="List attempts" description="Every delivery attempt for an event, newest first, with status codes and latency." baseUrl="https://api.relay.dev" auth="Bearer token" parameters={[{ name: "id", in: "path", type: "string", required: true, description: "The event id." }]} responses={[{ status: 200, description: "Attempts", example: `{ "data": [{ "n": 3, "status": 200, "ms": 142 }] }`, language: "json" }]} /> </div> </section> </div> </main> <aside className="hidden xl:block"> <div className={cn("sticky top-14 py-12")}> <TableOfContents title="On this page" offset={80} items={[ { id: "get-a-key", title: "1. Get an API key" }, { id: "send-an-event", title: "2. Send an event" }, { id: "receive-it", title: "3. Receive it" }, { id: "api-reference", title: "API reference" }, ]} /> </div> </aside> </div> </RelayShell> ) } export { RelayDocs, type RelayDocsProps }components/ballmac/templates/relay/relay-pricing.tsx// Ballmac UI: Relay pricing page. https://ui.ballmac.com/templates/template-relay "use client" import * as React from "react" import { Check, Minus } from "lucide-react" import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ballmac/accordion" import { Slider } from "@/components/ballmac/slider" import { RelayShell, type RelayHrefs } from "@/components/ballmac/templates/relay/relay-theme" import { cn } from "@/lib/utils" const mono = { fontFamily: "var(--relay-mono)" } as const const money = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 }) const moneyExact = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 2 }) const count = new Intl.NumberFormat("en-US") const plans = [ { name: "Free", price: "$0", per: "forever", events: "100,000 events / mo", cta: "Start free", features: ["3 endpoints", "Retries for 24 hours", "7-day attempt history", "Community support"] }, { name: "Pro", price: "$49", per: "per month", events: "1M events / mo", cta: "Start Pro", featured: true, features: ["Unlimited endpoints", "Retries for 3 days", "90-day attempt history", "Replay and transformations", "Email support"] }, { name: "Scale", price: "Custom", per: "annual", events: "Billions of events", cta: "Contact sales", features: ["Dedicated delivery pool", "mTLS and IP allow-lists", "1-year retention", "99.99% SLA", "Shared Slack channel"] }, ] const rows: { feature: string; free: boolean | string; pro: boolean | string; scale: boolean | string }[] = [ { feature: "Endpoints", free: "3", pro: "Unlimited", scale: "Unlimited" }, { feature: "Retry window", free: "24 hours", pro: "3 days", scale: "7 days" }, { feature: "Attempt history", free: "7 days", pro: "90 days", scale: "1 year" }, { feature: "Replay", free: false, pro: true, scale: true }, { feature: "Transformations", free: false, pro: true, scale: true }, { feature: "Per-endpoint rate limits", free: false, pro: true, scale: true }, { feature: "mTLS and IP allow-lists", free: false, pro: false, scale: true }, { feature: "Uptime SLA", free: false, pro: "99.9%", scale: "99.99%" }, ] const faqs = [ { q: "What is an event?", a: "One message you send to Relay, no matter how many endpoints it fans out to or how many retries it needs. Replays count as new events." }, { q: "What happens above my plan’s events?", a: "You pay the rate shown in the calculator. Nothing is throttled, and you can set a hard cap that pauses sending instead." }, { q: "Can I try Pro before paying?", a: "Every account starts with a 14-day Pro trial, no card required. You fall back to Free afterwards." }, ] function Cell({ v }: { v: boolean | string }) { if (typeof v === "string") return <span className="text-sm">{v}</span> return v ? <Check className="text-chart-2 mx-auto size-4" aria-label="Included" role="img" /> : <Minus className="text-muted-foreground mx-auto size-4" aria-label="Not included" role="img" /> } type RelayPricingProps = React.ComponentProps<"div"> & { hrefs?: Partial<RelayHrefs> } /** Relay pricing: plans, a per-volume estimator with a stepped rate, a comparison table and questions. */ function RelayPricing({ hrefs, ...props }: RelayPricingProps) { const [events, setEvents] = React.useState(3_000_000) const included = 1_000_000 const extra = Math.max(0, events - included) const t1 = Math.min(extra, 4_000_000) const t2 = Math.max(0, extra - 4_000_000) const overage = (t1 / 1_000_000) * 8 + (t2 / 1_000_000) * 5 const total = 49 + overage return ( <RelayShell page="pricing" hrefs={hrefs} {...props}> <main> <section className="border-b"> <div className="mx-auto max-w-6xl px-4 py-16 sm:px-6 sm:py-24"> <p className="text-chart-1 text-xs font-semibold tracking-wider uppercase" style={mono}>Pricing</p> <h1 className="mt-4 max-w-3xl text-4xl font-semibold tracking-[-0.05em] text-balance sm:text-6xl">Priced per event. Cheaper as you grow.</h1> <p className="text-muted-foreground mt-5 max-w-xl text-lg text-pretty">No seats, no endpoint fees, no surprise tiers. Every plan gets the full delivery engine.</p> <div className="mt-12 grid overflow-hidden rounded-lg border lg:grid-cols-3"> {plans.map((p) => ( <article key={p.name} className={cn("bg-card flex flex-col border-b p-7 last:border-b-0 lg:border-e lg:border-b-0 lg:last:border-e-0", p.featured && "bg-surface")}> <div className="flex items-center justify-between"> <h2 className="text-lg font-semibold" style={mono}>{p.name}</h2> {p.featured && <span className="bg-chart-1 text-background rounded-sm px-2 py-0.5 text-[11px] font-semibold" style={mono}>POPULAR</span>} </div> <p className="mt-6 flex items-baseline gap-2"><span className="text-5xl font-semibold tracking-[-0.05em]">{p.price}</span><span className="text-muted-foreground text-sm">{p.per}</span></p> <p className="text-muted-foreground mt-2 text-sm" style={mono}>{p.events}</p> <a href="#" className={cn("focus-visible:ring-ring/50 mt-6 inline-flex h-11 items-center justify-center rounded-md text-sm font-semibold outline-none transition-colors focus-visible:ring-[3px]", p.featured ? "bg-foreground text-background hover:opacity-90" : "hover:bg-accent border")} style={mono}>{p.cta}</a> <ul className="mt-7 space-y-3 text-sm"> {p.features.map((f) => <li key={f} className="flex items-start gap-2.5"><Check className="text-chart-2 mt-0.5 size-4 shrink-0" aria-hidden="true" />{f}</li>)} </ul> </article> ))} </div> </div> </section> <section className="border-b" aria-labelledby="relay-estimator"> <div className="mx-auto grid max-w-6xl gap-10 px-4 py-16 sm:px-6 lg:grid-cols-[1.2fr_1fr] lg:gap-16"> <div> <h2 id="relay-estimator" className="text-2xl font-semibold tracking-[-0.03em]">Estimate your Pro bill</h2> <p className="text-muted-foreground mt-2 text-sm text-pretty">$49 includes 1M events. Then $8 per million up to 5M, and $5 per million beyond.</p> <div className="mt-10"> <div className="flex items-baseline justify-between"><label id="relay-events" className="text-muted-foreground text-sm">Events per month</label><output className="text-xl font-medium tabular-nums" style={mono} aria-live="polite">{count.format(events)}</output></div> <Slider className="mt-5" aria-labelledby="relay-events" min={500_000} max={50_000_000} step={500_000} value={[events]} onValueChange={(v) => setEvents(v[0] ?? events)} formatValue={(v) => `${count.format(v)} events`} /> <div className="text-muted-foreground mt-2 flex justify-between text-xs" style={mono}><span>500k</span><span>50M</span></div> </div> </div> <div className="bg-card space-y-3 rounded-lg border p-5 text-sm" style={mono}> <dl className="space-y-3"> <div className="flex justify-between"><dt className="text-muted-foreground">Pro base</dt><dd>$49.00</dd></div> <div className="flex justify-between"><dt className="text-muted-foreground">{count.format(extra)} extra events</dt><dd className="tabular-nums">{moneyExact.format(overage)}</dd></div> <div className="flex items-baseline justify-between border-t pt-4"><dt className="font-semibold">Per month</dt><dd className="text-3xl font-semibold tabular-nums">{money.format(total)}</dd></div> </dl> <p className="text-muted-foreground text-xs" style={{ fontFamily: "var(--relay-sans)" }}>Effective rate: {moneyExact.format(total / (events / 1_000_000))} per million events.</p> </div> </div> </section> <section className="border-b"> <div className="mx-auto max-w-6xl px-4 py-16 sm:px-6"><div tabIndex={0} role="region" aria-label="Plan comparison" className="focus-visible:ring-ring/50 overflow-x-auto rounded-md outline-none focus-visible:ring-[3px]"> <table className="w-full min-w-[34rem] text-start"> <caption className="sr-only">Plan comparison</caption> <thead> <tr className="border-b text-xs tracking-wider uppercase" style={mono}> <th scope="col" className="py-3 pe-4 font-semibold">Feature</th> {plans.map((p) => <th key={p.name} scope="col" className="px-4 py-3 text-center font-semibold">{p.name}</th>)} </tr> </thead> <tbody> {rows.map((r) => ( <tr key={r.feature} className="border-b"> <th scope="row" className="py-3.5 pe-4 text-sm font-normal">{r.feature}</th> <td className="px-4 py-3.5 text-center"><Cell v={r.free} /></td> <td className="bg-surface px-4 py-3.5 text-center"><Cell v={r.pro} /></td> <td className="px-4 py-3.5 text-center"><Cell v={r.scale} /></td> </tr> ))} </tbody> </table> </div></div> </section> <section className="mx-auto max-w-3xl px-4 py-16 sm:px-6" aria-labelledby="relay-faq"> <h2 id="relay-faq" className="text-2xl font-semibold tracking-[-0.03em]">Questions</h2> <Accordion type="single" collapsible className="mt-5"> {faqs.map((f) => ( <AccordionItem key={f.q} value={f.q}><AccordionTrigger>{f.q}</AccordionTrigger><AccordionContent className="text-muted-foreground text-pretty">{f.a}</AccordionContent></AccordionItem> ))} </Accordion> </section> </main> </RelayShell> ) } export { RelayPricing, type RelayPricingProps }components/ballmac/templates/relay/relay-status.tsx// Ballmac UI: Relay status page. https://ui.ballmac.com/templates/template-relay import * as React from "react" import { CheckCircle2 } from "lucide-react" import { RelayShell, type RelayHrefs } from "@/components/ballmac/templates/relay/relay-theme" import { cn } from "@/lib/utils" const mono = { fontFamily: "var(--relay-mono)" } as const type Day = "up" | "degraded" | "down" /** Ninety days of history. Fixed, so the server and browser draw the same bars. */ function history(seed: number, degraded: number[], down: number[]): Day[] { return Array.from({ length: 90 }, (_, i) => (down.includes(i + seed) ? "down" : degraded.includes(i + seed) ? "degraded" : "up")) } const components: { name: string; uptime: string; days: Day[] }[] = [ { name: "Event ingestion API", uptime: "99.998%", days: history(0, [41], []) }, { name: "Delivery workers", uptime: "99.991%", days: history(0, [12, 57], [58]) }, { name: "Dashboard", uptime: "99.987%", days: history(0, [23, 24, 71], []) }, { name: "Replay and search", uptime: "99.996%", days: history(0, [66], []) }, { name: "Webhooks to customers (global)", uptime: "99.994%", days: history(0, [57, 58], []) }, ] const incidents = [ { date: "Sep 24, 2026", title: "Delayed deliveries in eu-central", status: "Resolved", body: "A partial network failure in Frankfurt slowed deliveries by up to 4 minutes. Events were queued and delivered in order; nothing was lost.", duration: "37 minutes" }, { date: "Aug 15, 2026", title: "Dashboard search timeouts", status: "Resolved", body: "Searches over 30 days of history timed out for 11 minutes after an index rebuild. Ingestion and delivery were unaffected.", duration: "11 minutes" }, { date: "Jul 29, 2026", title: "Elevated 5xx on the ingestion API", status: "Resolved", body: "A bad deploy returned errors for 0.4 percent of requests. We rolled back in 6 minutes. Clients using idempotency keys retried safely.", duration: "6 minutes" }, ] const tone: Record<Day, string> = { up: "bg-chart-2", degraded: "bg-chart-3", down: "bg-destructive" } const word: Record<Day, string> = { up: "operational", degraded: "degraded", down: "outage" } type RelayStatusProps = React.ComponentProps<"div"> & { hrefs?: Partial<RelayHrefs> } /** Relay status: an overall banner, 90-day uptime bars per component and recent incidents. */ function RelayStatus({ hrefs, ...props }: RelayStatusProps) { return ( <RelayShell page="status" hrefs={hrefs} {...props}> <main className="mx-auto max-w-4xl px-4 py-14 sm:px-6 sm:py-20"> <p className="text-chart-1 text-xs font-semibold tracking-wider uppercase" style={mono}>System status</p> <h1 className="mt-4 text-4xl font-semibold tracking-[-0.05em] text-balance sm:text-5xl">Status</h1> <div role="status" className="border-chart-2/40 bg-chart-2/10 mt-8 flex items-center gap-3 rounded-lg border px-5 py-4"> <CheckCircle2 className="text-chart-2 size-6 shrink-0" aria-hidden="true" /> <div> <p className="font-semibold">All systems operational</p> <p className="text-muted-foreground text-sm">Updated 09:41 UTC · Median delivery latency 38 ms</p> </div> </div> <section aria-labelledby="relay-uptime" className="mt-12"> <h2 id="relay-uptime" className="text-xl font-semibold tracking-[-0.02em]">Uptime, last 90 days</h2> <ul className="mt-5 space-y-3"> {components.map((c) => ( <li key={c.name} className="bg-card rounded-lg border p-5"> <div className="flex items-baseline justify-between gap-3"> <h3 className="font-medium">{c.name}</h3> <span className="text-chart-2 text-sm tabular-nums" style={mono}>{c.uptime}</span> </div> <div className="mt-4 flex h-8 gap-px" role="img" aria-label={`${c.name}: ${c.days.filter((d) => d === "up").length} of 90 days operational, ${c.days.filter((d) => d === "degraded").length} degraded, ${c.days.filter((d) => d === "down").length} outage`}> {c.days.map((d, i) => <span key={i} className={cn("min-w-0 flex-1 rounded-[1px]", tone[d])} title={`${90 - i} days ago: ${word[d]}`} />)} </div> <div className="text-muted-foreground mt-2 flex justify-between text-xs" style={mono}><span>90 days ago</span><span>Today</span></div> </li> ))} </ul> <ul className="text-muted-foreground mt-4 flex flex-wrap gap-x-5 gap-y-1 text-xs"> {(["up", "degraded", "down"] as Day[]).map((d) => <li key={d} className="flex items-center gap-1.5"><span className={cn("size-2.5 rounded-[2px]", tone[d])} aria-hidden="true" />{word[d][0].toUpperCase() + word[d].slice(1)}</li>)} </ul> </section> <section aria-labelledby="relay-incidents" className="mt-14"> <h2 id="relay-incidents" className="text-xl font-semibold tracking-[-0.02em]">Past incidents</h2> <ol className="mt-5 space-y-6"> {incidents.map((i) => ( <li key={i.title} className="border-s-2 ps-5"> <p className="text-muted-foreground text-xs" style={mono}>{i.date} · {i.duration}</p> <h3 className="mt-1 font-semibold">{i.title} <span className="bg-chart-2/15 ms-1 rounded px-1.5 py-0.5 text-[11px] font-medium">{i.status}</span></h3> <p className="text-muted-foreground mt-2 text-sm text-pretty">{i.body}</p> </li> ))} </ol> </section> </main> </RelayShell> ) } export { RelayStatus, type RelayStatusProps }app/relay/page.tsx// Ballmac UI: Relay template route. https://ui.ballmac.com/templates/template-relay import type { Metadata } from "next" import { RelayHome } from "@/components/ballmac/templates/relay/relay-home" export const metadata: Metadata = { title: "Relay: Never lose a webhook again", description: "Reliable webhook delivery with retries, signatures, replay and a full audit trail.", } export default function Page() { return <RelayHome /> }app/relay/docs/page.tsx// Ballmac UI: Relay template route. https://ui.ballmac.com/templates/template-relay import type { Metadata } from "next" import { RelayDocs } from "@/components/ballmac/templates/relay/relay-docs" export const metadata: Metadata = { title: "Relay docs: Quickstart", description: "Send your first event and verify it in two minutes.", } export default function Page() { return <RelayDocs /> }app/relay/pricing/page.tsx// Ballmac UI: Relay template route. https://ui.ballmac.com/templates/template-relay import type { Metadata } from "next" import { RelayPricing } from "@/components/ballmac/templates/relay/relay-pricing" export const metadata: Metadata = { title: "Relay pricing", description: "Priced per event, and cheaper as you grow.", } export default function Page() { return <RelayPricing /> }app/relay/status/page.tsx// Ballmac UI: Relay template route. https://ui.ballmac.com/templates/template-relay import type { Metadata } from "next" import { RelayStatus } from "@/components/ballmac/templates/relay/relay-status" export const metadata: Metadata = { title: "Relay status", description: "Uptime and incidents for every Relay component.", } export default function Page() { return <RelayStatus /> }Update the import paths to match your project setup.
The page is added as a route in your app, with every block it uses.
What's inside
- Pages
- Home/relay
- Docs/relay/docs
- Pricing/relay/pricing
- Status/relay/status
- Fonts
- Hanken Grotesk, IBM Plex Mono (Google Fonts, loaded with next/font)
- Files
- 11 files · 8 Ballmac blocks and components
Built with
Dependencies
- npm
- lucide-react
Use with AI
Installs a four-page site for a developer API or infrastructure product. Change the palette in relayCss (relay-theme.tsx), the samples in relay-samples.ts, and the copy in each page file. With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI Relay: developer API platform (@ballmac/template-relay) to this project with the shadcn MCP, then use it where it fits.
Use it for
- Marketing and docs sites for APIs, SDKs and developer infrastructure
- Brands that want a precise, terminal-flavoured look in light and dark
Not for
- Consumer apps or soft, editorial brands (use a Northwind-style template)
Registry JSON: https://ui.ballmac.com/r/template-relay.json
Free to use in personal and commercial projects.