We started this project with a simple frustration. Surveys tell you how people felt last quarter. Exit interviews tell you what went wrong after someone has already decided to leave. Neither of those helps you on a Tuesday afternoon when tension is building in a channel and nobody has said anything out loud yet.
That is where Ember came from. The idea was not to spy on employees or dump chat logs on a manager's screen. We wanted something quieter: signals from how teams already communicate in Slack, turned into risk scores and alerts that HR and People Ops can actually use.
Protect your culture with signals, not surveys. That line stuck because it described what we were trying to build.
What we actually built
Ember listens when the bot is in a Slack channel. Messages come in through the Events API, get stored, scored, and eventually show up on a dashboard with an org risk map, alerts, and per-person breakdowns. Raw message text only appears on individual profile pages when something is flagged. The main view stays at the signal level.
Scoring works in layers. Each message gets a sentiment score from an LLM via OpenRouter, with a keyword fallback if the API is down or slow. Those scores feed four behavioral signals: sentiment drift, after-hours activity, channel exclusion, and a drop in how much someone is posting compared to their own baseline. Those roll up into a 0 to 10 composite score with levels like Normal, Watch, Warning, and Critical.
For a hackathon project, that is a lot of moving parts. We are proud it works end to end.
Why we split the stack between Vercel and AWS
Early on we had to decide where things live. We did not want one database doing everything.
Vercel became the obvious home for the app itself. Next.js route handlers, deploy on push, environment variables in one place, and a URL we could hand to Slack without fighting infrastructure. For a small team racing a deadline, that mattered. We were writing product logic, not babysitting servers.
AWS took the data that needed to scale differently. Raw Slack events land in DynamoDB. They are append-heavy, high volume, and we only need to mark them processed after scoring. Structured people data, risk scores, and alerts live in Aurora PostgreSQL with Drizzle. That split felt right: firehose in Dynamo, queryable HR data in Postgres.
We will be honest: wiring Aurora from a Vercel serverless app was not a five-minute job. Connection strings, SSL, cold starts, and making sure the worker did not open too many connections at once. When it finally worked, though, we had something that felt real. Not a demo running on SQLite in someone's laptop.
Vercel Cron runs the scoring worker on a schedule. On the Hobby plan you only get one cron slot per day, so we set a daily sweep and used an external cron for more frequent runs during the hackathon. Small constraint, easy workaround. Vercel did not get in our way.
Slack was the hardest room in the house
The dashboard and scoring engine were hard. Slack integration was harder.
Local development meant ngrok tunneling to localhost so Slack could reach /api/slack/events. Every time the tunnel URL changed, you went back into the Slack app settings and updated Event Subscriptions. We must have done that a dozen times.
Then there is the social side of Slack bots. The app can be installed perfectly and still hear nothing until someone types /invite @Ember in each channel. We learned that the hard way when messages were "not showing up" and the database was empty.
User sync was its own step: pulling profiles from Slack into Postgres so the dashboard had real names and departments. One curl to /api/slack/users/sync and suddenly the org map had people on it. Satisfying when it clicked.
The org risk map nearly broke us
If you open the dashboard, the org risk map is the thing that is supposed to make executives nod. It was also the thing that fought us the most.
We used a force-directed graph library. Locally it looked great. In production, nodes were missing or clustered in a corner, zoom was too tight on first load, labels overlapped. We tweaked nodeVal, padding on zoomToFit, and stared at canvas sizing for longer than we want to admit. We even tried rewriting it in SVG before stepping back to the original approach with smaller fixes.
Visualization is deceptively hard. The backend can be correct and the graph can still make it look broken. Judges and users judge with their eyes first.
Other bumps along the way
OpenRouter for sentiment was the right call for a hackathon. One API key, sensible defaults, attribution handled. We still built the keyword fallback because demos do not wait for rate limits.
We had a scary moment with shared infrastructure. A seed script that made sense for local testing got run against the real Aurora instance and suddenly the dashboard showed fake employees next to real Slack users. We cleared it, removed the seed tooling from the repo, and tightened our own discipline. Lesson learned: shared databases need shared caution.
Architecture diagrams were their own saga. Draw.io and Excalidraw in the browser kept turning out cluttered or ugly for a judge-facing doc. We ended up with a cleaner Mermaid diagram and an exported PNG in the README. Good enough to explain the flow: Slack in, DynamoDB, worker plus LLM, Postgres out, dashboard on top.
Even the favicon was a small win. The Ember mark from the sidebar became icon.svg and showed up in browser tabs. Tiny detail, but it made the project feel finished.
What we would tell someone starting similar
- Get Slack → DynamoDB → worker → Postgres working before you polish UI. A pretty dashboard with no events is just a screenshot.
- Treat the risk graph as a product surface, not a chart library exercise. Budget time.
- Vercel plus AWS is a strong combo if you are clear about what runs where. Serverless app on Vercel, durable stores on AWS.
- Document your env vars early. Slack, AWS, OpenRouter, cron secrets. You will thank yourself at 2 a.m. before a demo.
Where we want to take Ember
This hackathon version is intentionally scoped. Single workspace (demo-org), settings toggles that are UI-only, scoring on a schedule rather than true real-time. That was the right tradeoff for the deadline.
If we keep going, the list is long but exciting:
- Multi-tenant workspaces so Ember is not one hardcoded org.
- Finer privacy controls: who can see evidence, retention windows, audit logs. HR tools have to earn trust.
- Better relationship mapping using the graph data we already sketch on the dashboard.
- Alerts that reach people where they work (Slack DMs to HR, email digests, integrations with existing HRIS tools).
- Smarter scoring with more context and less noise from sarcasm, jokes, and channel culture.
- A risk map that behaves on every screen size and every deploy without manual zoom babysitting.
We also want to keep the principle we started with: signals, not surveillance. The dashboard should help someone intervene early, not create a panopticon.
Thank you to the stack
We did not build Ember alone. Vercel let us ship fast and focus on the product. AWS gave us DynamoDB and Aurora that could survive a real demo with real messages. Slack is where work already happens, which is the whole point. OpenRouter made LLM sentiment practical without running our own model.
We built Ember in a compressed timeline, hit real walls, and still ended up with something we would show to a room of judges without flinching. If you are working on people analytics, culture health, or just trying to connect Slack events to something actionable, we hope our stumbles save you a few.
Top comments (0)