# Reroute: Email Relays

> An email API's relays with a switch each. Pause relays and the mail moves on to whichever is still up.

A shadcn/ui block from the [@keyframeui](https://keyframeui.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/keyframeui/reroute-email-demo).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/reroute-email-demo
- Screenshot: https://shadcn.exchange/shot/keyframeui/reroute-email-demo/light.png
- Registry item: https://keyframeui.com/r/reroute-email-demo.json
- Categories: interaction, ai, diagram
- Listed: 2026-11-02, source last changed 2026-11-02

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/keyframeui/reroute-email-demo.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `https://keyframeui.com/r/reroute.json`

## Notes from the registry

Live preview, examples and props: https://keyframeui.com/components/reroute#reroute-email-demo

## Files

### `registry/demos/reroute-email-demo.tsx`

```tsx
"use client";

import * as React from "react";

import { Reroute, type RerouteItem } from "@/components/ui/reroute";

function Mark({ children }: { children: React.ReactNode }) {
  return (
    <svg viewBox="0 0 24 24" className="size-full" aria-hidden>
      {children}
    </svg>
  );
}

// Postwick, a transactional email API, sends through four relays. Flip one off and mail reroutes.
const RELAYS: (RerouteItem & { short: string })[] = [
  {
    id: "pelican",
    short: "Pelican",
    name: "Pelican Mail",
    detail: "99.2% inboxed · 1.1s",
    icon: (
      <Mark>
        <rect x="3" y="5" width="18" height="14" rx="3.5" fill="#0ea5e9" />
        <path d="M6.5 9l5.5 4 5.5-4" fill="none" stroke="#fff" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
      </Mark>
    ),
  },
  {
    id: "courier",
    short: "Courier",
    name: "Courier One",
    detail: "98.7% inboxed · 0.9s",
    icon: (
      <Mark>
        <path d="M3.5 11.5 20.5 4l-5 16-3.6-6.2z" fill="#f97316" />
        <path d="M11.9 13.8 20.5 4" stroke="#fff" strokeWidth="1.5" strokeLinecap="round" />
      </Mark>
    ),
  },
  {
    id: "harbor",
    short: "Harbor",
    name: "Harbor SMTP",
    detail: "99.0% inboxed · 1.4s",
    icon: (
      <Mark>
        <circle cx="12" cy="12" r="9" fill="#4f46e5" />
        <path d="M12 6.5v10M8 12.5a4 4 0 0 0 8 0M9.5 8.5h5" fill="none" stroke="#fff" strokeWidth="1.8" strokeLinecap="round" />
      </Mark>
    ),
  },
  {
    id: "beacon",
    short: "Beacon",
    name: "Beacon Relay",
    detail: "98.9% inboxed · 1.2s",
    icon: (
      <Mark>
        <rect x="4" y="4" width="16" height="16" rx="5" fill="#16a34a" />
        <circle cx="12" cy="12" r="2.4" fill="#fff" />
        <path d="M7.8 7.8a6 6 0 0 0 0 8.4M16.2 7.8a6 6 0 0 1 0 8.4" fill="none" stroke="#bbf7d0" strokeWidth="1.6" strokeLinecap="round" />
      </Mark>
    ),
  },
];

export function RerouteEmailDemo() {
  const [down, setDown] = React.useState<string[]>(["courier"]);
  const [live, setLive] = React.useState<string | null>("pelican");
  const [sent, setSent] = React.useState(18204);

  // Mail keeps going out while any relay is up.
  React.useEffect(() => {
    if (!live) return;
    const id = window.setInterval(() => setSent((n) => n + 1 + Math.floor(Math.random() * 3)), 420);
    return () => window.clearInterval(id);
  }, [live]);

  const toggle = (id: string) => setDown((d) => (d.includes(id) ? d.filter((x) => x !== id) : [...d, id]));

  return (
    <div className="w-full max-w-xl overflow-hidden rounded-3xl border border-zinc-950/[0.08] bg-white shadow-[0_1px_2px_rgba(0,0,0,0.04)] dark:border-white/10 dark:bg-zinc-950">
      <div className="flex items-end justify-between gap-4 px-6 pt-6">
        <div className="min-w-0">
          <h3 className="text-[15px] font-semibold tracking-tight text-zinc-950 dark:text-white">Delivery relays</h3>
          <p className="mt-0.5 text-xs text-zinc-500 dark:text-zinc-400">Pause a relay and mail moves to the next one.</p>
        </div>
        <div className="shrink-0 text-right">
          <p className="font-mono text-lg font-medium leading-6 tabular-nums tracking-tight text-zinc-950 dark:text-white">
            {sent.toLocaleString("en-US")}
          </p>
          <p className="text-[11px] text-zinc-500 dark:text-zinc-400">sent today</p>
        </div>
      </div>

      <Reroute
        className="[--reroute-accent:#00a86b] [--reroute-down:#ef4444] dark:[--reroute-accent:#1fd18f] dark:[--reroute-down:#f87171]"
        label="Email relay"
        items={RELAYS}
        defaultValue="pelican"
        down={down}
        onRoute={(id) => setLive(id)}
        hub={
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
            <rect x="3.5" y="5.5" width="17" height="13" rx="2.5" />
            <path d="M4 7l8 6 8-6" />
          </svg>
        }
      />

      <ul className="grid grid-cols-1 gap-px border-t border-zinc-950/[0.06] bg-zinc-950/[0.06] sm:grid-cols-2 dark:border-white/[0.08] dark:bg-white/[0.08]">
        {RELAYS.map((r) => {
          const off = down.includes(r.id);
          return (
            <li key={r.id} className="flex items-center gap-3 bg-white px-5 py-3 dark:bg-zinc-950">
              <span className="size-5 shrink-0">{r.icon}</span>
              <span className="min-w-0 flex-1">
                <span className="block truncate text-[13px] font-medium tracking-tight text-zinc-950 dark:text-white">{r.short}</span>
                <span className="block text-[11px] text-zinc-500 dark:text-zinc-400">
                  {off ? "Paused" : r.id === live ? "Sending" : "Standing by"}
                </span>
              </span>
              <button
                type="button"
                role="switch"
                aria-checked={!off}
                aria-label={`${r.short} relay`}
                onClick={() => toggle(r.id)}
                className="relative h-6 w-10 shrink-0 rounded-full outline-offset-2 outline-[#00a86b] transition-colors duration-200 focus-visible:outline-2 aria-checked:bg-[#00a86b] aria-[checked=false]:bg-zinc-200 dark:aria-checked:bg-[#1fd18f] dark:aria-[checked=false]:bg-zinc-700"
              >
                <span
                  className="absolute left-0.5 top-0.5 size-5 rounded-full bg-white shadow-sm transition-transform duration-200 ease-[cubic-bezier(0.23,1,0.32,1)]"
                  style={{ transform: off ? "translateX(0)" : "translateX(16px)" }}
                />
              </button>
            </li>
          );
        })}
      </ul>
    </div>
  );
}
```
