A MERN side project inspired by The Office's infamous all-channels-at-once communication startup.
Send a single message and blast it across multiple channels:
- Slack
- SMS
One message in. Multiple notifications out.
This repo contains three pieces that share one backend:
server/ the Express API — fans a message out across channels
client/ the public website — compose a message, pick channels, send
cli/ a personal terminal client — same backend, plus character
voices, Telegram, and a fake faxWUPHF is built around a simple idea: every communication channel behaves the same way from the application's perspective.
Each adapter implements a common interface:
async function send(message) {
// throws on failure
}
module.exports = { send };Because every channel follows the same contract, the route handler doesn't need to know whether it's talking to Slack, Twilio, Resend, or anything else.
The fan-out logic simply calls .send() on each selected adapter and uses Promise.allSettled() to ensure that one failure doesn't stop the others.
Example:
- Invalid phone number? SMS fails.
- Expired Slack webhook? Slack fails.
- Email still sends.
- Request still completes.
Adding a new channel requires only two steps:
- Create a new adapter in
server/channels/ - Register it in the
ADAPTERSmap insideserver/routes/wuphf.js
No route changes. No business logic changes.
server/channels/
├── slack.js
├── email.js
├── sms.js
└── your-new-channel.jsThis keeps channel-specific code isolated while the routing layer remains completely generic.
cli/ follows the same instinct one level up: it's a second, independent client of the exact same /api/wuphf endpoint the website uses, rather than a fork or a separate service. Nothing in server/ knows or cares whether a request came from the website's "WUPHF it" button or a terminal command — same rate limiter, same validation, same Slack/Email channels underneath.
cd server
npm install
cp .env.example .env
# Fill in the credentials for whichever channels you want to use
npm run devcd client
npx create-react-app .
# (or use Vite if you prefer)
# Drop WuphfPanel.jsx and WuphfPanel.css into src/
# Import <WuphfPanel /> inside App.js
npm startcd cli
npm install
cp .env.example .env
# Add a free Groq API key (console.groq.com/keys) for character
# voices, and a Telegram bot token (via @BotFather) if you want
# Telegram delivery
npm link
wuphf charactersThe CLI talks to the same deployed backend (WUPHF_API_BASE in its .env) — it doesn't need its own database, deployment, or hosting. See cli/README.md for full usage.
You do not need all three channels configured to run the application.
If a channel is missing required environment variables, WUPHF returns a channel-specific error while continuing to process the others.
Build the smallest possible end-to-end loop:
React Form
↓
Express Route
↓
Slack
↓
MongoDB Log
↓
UI ResponseOnce this works, you've validated the entire architecture.
The WuphfLog model is already wired into the route, making persistence the next logical step.
Email requires a bit more setup (such as verifying a sending domain), but the implementation is just as straightforward as Slack.
Save Twilio for last.
Common gotchas:
- Trial accounts can only message verified numbers.
- Numbers must be in E.164 format:
+15551234567Not:
(555) 123-4567
555-123-4567
555 123 4567Twilio will reject improperly formatted numbers immediately.
Once the backend and website are solid, cli/ is a low-risk way to extend WUPHF without touching production:
- Character voices — rewrites your message in the voice of an Office character (Michael, Dwight, Creed, and six others) using Groq's free tier, entirely client-side, before the message ever reaches the backend.
- Telegram — a fourth delivery channel, but handled entirely in the CLI rather than added to the deployed backend, so it needs no new public env vars and no new attack surface.
- Fax — a fake, theatrical "transmission," generating a styled HTML page that looks like a faxed document. Nothing is actually transmitted; it's pure bit.
All three CLI features deliberately avoid touching server/ — they either call the existing /api/wuphf endpoint exactly like the website does, or (Telegram, fax) skip the backend entirely and run client-side. The backend doesn't know personalities, Telegram, or fax exist.
server/
├── channels/
│ ├── channel.interface.js # Contract documentation
│ ├── slack.js
│ ├── email.js
│ └── sms.js
│
├── models/
│ └── WuphfLog.js
│
├── routes/
│ └── wuphf.js # POST: fan-out
│ # GET: message history
│
├── index.js
├── .env.example
└── package.json
client/
└── src/
├── WuphfPanel.jsx
└── WuphfPanel.css
cli/
├── characters/
│ └── roster.js # Voice descriptions per character
├── fax/
│ ├── buildFaxHTML.js # Generates the fake fax document
│ └── printFaxTransmission.js
├── cli.js # send / fax / characters commands
├── personality.js # Groq-based character rewriting
├── telegram.js # Telegram Bot API adapter
└── .env.example- Adapter Pattern for channel integrations
- Loose coupling between routing and delivery providers
- Failure isolation via
Promise.allSettled() - Easy extensibility for future channels
- Single responsibility for each adapter
- New surface area (CLI, personalities, Telegram, fax) extends the system from the outside — through the same public endpoint, or entirely client-side — rather than growing the deployed backend's responsibilities
The joke in The Office is that nobody actually wants every message delivered through every possible channel.
It's not productivity.
It's noise.
Building WUPHF for real makes that painfully obvious.
The first time a single:
"Running 5 minutes late"
causes:
- your phone to buzz,
- your inbox to ping,
- and Slack to light up,
you'll understand both Michael's excitement and Ryan's business model better than any pitch deck ever could.
"Why send a message one way when you can send it five ways?"