We just launched Brighten on Product Hunt — an employee recognition platform with native Slack/Teams integration. Here's how we built it.
The Challenge
Build a real-time employee recognition platform that:
- Integrates natively with Slack/Teams (webhook-driven)
- Handles enterprise compliance (SSO, audit logs, SOC2)
- Scales to thousands of recognitions per second
- Stays free for small teams (cost optimization)
Tech Stack
Frontend: Next.js 14 (App Router) + React 18
Backend: Next.js API Routes + Server Actions
Database: Supabase (PostgreSQL + Real-time)
Auth: Supabase Auth + OAuth + SAML
Hosting: Vercel (Edge Runtime)
Integrations: Slack Web API + Microsoft Graph API
Payments: Stripe Connect
Key Technical Decisions
1. Real-time over Batch Processing
Decision: Process recognitions in real-time, not batches.
Why: Recognition loses impact after 24 hours. If someone recognizes you at 2 PM and you get notified at 9 AM the next day, it feels stale.
Implementation:
// Supabase real-time subscription
const channel = supabase
.channel('recognitions')
.on(
'postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'recognitions' },
async (payload) => {
await sendSlackNotification(payload.new);
await updateEngagementScore(payload.new.recipient_id);
}
)
.subscribe();
Trade-off: Higher database load, but worth it for user experience.
2. Webhook-First Integrations
Decision: Build deep integrations, not just bots.
Why: Slack/Teams adoption dies if there's any friction. Users shouldn't have to @ a bot or remember commands.
Implementation:
// Slack slash command
app.command('/brighten', async ({ command, ack, client }) => {
await ack();
// Parse: /brighten @user for [reason]
const { user, reason } = parseCommand(command.text);
// Create recognition
const recognition = await createRecognition({
sender_id: command.user_id,
recipient_id: user,
reason: reason,
channel_id: command.channel_id,
});
// Post to channel
await client.chat.postMessage({
channel: command.channel_id,
text: `🎉 ${command.user_name} recognized ${user} for ${reason}`,
blocks: buildRecognitionBlocks(recognition),
});
});
Result: Teams actually keep using it — adoption stays strong well beyond the first week.
3. Compliance-First Architecture
Decision: Build security in, not bolt it on later.
Why: HR data requires SOC2, GDPR, audit logs from day 1. Retrofitting security is expensive and risky.
Implementation:
-- Row-level security
CREATE POLICY "Users can only see their own org data"
ON recognitions
FOR SELECT
USING (org_id = auth.uid()::uuid);
-- Audit logging
CREATE OR REPLACE FUNCTION audit_log()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO audit_logs (
table_name, action, user_id, old_data, new_data
) VALUES (
TG_TABLE_NAME, TG_OP, auth.uid(), to_jsonb(OLD), to_jsonb(NEW)
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Result: Enterprise-ready from launch.
4. Multi-Tenant with Single Database
Decision: Single database with row-level security, not separate databases per tenant.
Why:
- Easier to maintain (one schema, one migration path)
- Better resource utilization (shared connection pool)
- Simpler backup/restore
- Future-proof for cross-org features (benchmarking, industry comparisons)
Implementation:
-- Every table has org_id
CREATE TABLE recognitions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
org_id UUID NOT NULL REFERENCES organizations(id),
sender_id UUID NOT NULL,
recipient_id UUID NOT NULL,
reason TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- RLS ensures data isolation
ALTER TABLE recognitions ENABLE ROW LEVEL SECURITY;
Trade-off: Slightly more complex queries, but worth it for operational simplicity.
5. Edge Runtime for Global Performance
Decision: Deploy to Vercel Edge Runtime (not Node.js runtime).
Why: Recognition needs to feel instant everywhere. 200ms matters.
Implementation:
// app/api/recognitions/route.ts
export const runtime = 'edge'; // Deploy to 70+ edge locations
export async function POST(req: Request) {
const recognition = await req.json();
// Ultra-fast database queries via Supabase HTTP API
const { data } = await supabase
.from('recognitions')
.insert(recognition)
.select()
.single();
return Response.json(data);
}
Result: <100ms response times globally.
Hardest Problems
Problem 1: Making Peer-to-Peer Recognition Feel Authentic
Challenge: How do you encourage recognition without making it feel forced or gamified?
Bad solutions we rejected:
- ❌ Points system (turns recognition into a game)
- ❌ Leaderboards (creates competition, not gratitude)
- ❌ Forced cadence ("You must recognize 2 people this week")
Our solution:
- No points
- No leaderboards
- No forced behavior
- Just "thank you" when it matters
Result: Users consistently say recognition feels more authentic than previous tools.
Problem 2: Preventing Abuse
Challenge: What stops people from recognizing their friends 100 times for fake reasons?
Our solution:
// Recognition limits
const LIMITS = {
per_day: 10, // Max 10 recognitions per user per day
same_recipient: 3, // Max 3 to same person per week
cooldown: 60 * 5, // 5 min between recognitions
};
// Anomaly detection
async function detectAnomalies(recognition) {
const recent = await getRecentRecognitions(recognition.sender_id);
// Flag if all recognitions to same person
if (recent.every(r => r.recipient_id === recognition.recipient_id)) {
await flagForReview(recognition);
}
// Flag if reason is copy-paste
if (recent.some(r => r.reason === recognition.reason)) {
await flagForReview(recognition);
}
}
Result: <1% of recognitions flagged for review.
Problem 3: Cost Optimization for Free Tier
Challenge: How do you offer a free tier without losing money?
Our approach:
// Free tier limits
const FREE_TIER = {
max_users: 10,
custom_award_types: 2,
data_retention: 7, // days
reward_budget: 0, // Can give recognition, but no monetary rewards
};
Cost breakdown:
- Free tier: $0/mo (up to 10 users, limited features)
- Starter: $49/mo | Growth: $149/mo | Pro: $299/mo
- Platform fees on rewards range from 5%–12% depending on tier
Strategy: Free tier drives virality. Paid tiers unlock rewards marketplace, integrations, and analytics.
Performance Metrics
After 3 months in beta:
| Metric | Target | Actual |
|---|---|---|
| Page load (p95) | <2s | 1.3s |
| API response (p95) | <500ms | 230ms |
| Recognition delivery | <5s | 2.1s |
| Uptime | >99.5% | 99.8% |
| Database load | <60% | 42% |
Lessons Learned
- Real-time matters in HR tech - Users expect instant feedback
- Compliance is not optional - Build it from day 1
- Deep integrations > bots - Friction kills adoption
- Free tier drives growth - Teams upgrade naturally as they scale
- Edge runtime is worth it - Global performance matters
We're Launching Today
Brighten is live on Product Hunt. Free for up to 10 users ($0/mo).
Try it: hellobrighten.com
Product Hunt: Brighten on Product Hunt
Would love feedback from other devs building in the SaaS/HR space!
Questions?
Drop them in the comments. I'll be here all day answering questions about the tech stack, architecture decisions, or building in public.
Built with Next.js, Supabase, and too much coffee ☕
Top comments (0)