An AI-powered personal relationship manager that keeps your relationships warm via WhatsApp. Donna uses Claude to generate contextual, personality-matched messages, track relationship health, and automate outreach — so you never lose touch with the people who matter.
Donna connects to your WhatsApp account and runs a background worker that:
- Sends personalized outreach — generates context-aware messages based on past conversations, contact interests, and relationship history
- Tracks relationship health — computes daily health scores based on response rate, recency, and engagement patterns
- Manages your network — birthday wishes, call scheduling, weekly reports, and conversation summaries
- Learns over time — adapts outreach frequency and tone based on what works
You interact with Donna through a dark-themed dashboard with an AI chat assistant that can update contacts, send messages, and answer questions about your network.
| Layer | Technology |
|---|---|
| Frontend | Next.js 16, React 19, Tailwind CSS 4 |
| Backend | Next.js API Routes, Node.js 20 |
| Database | Supabase (PostgreSQL) |
| AI | Claude Sonnet 4 via Anthropic SDK |
| Messaging | whatsapp-web.js (Chromium-based) |
| Scheduling | node-cron + setInterval |
| Testing | Vitest 4 |
app/
api/ # REST API routes (auth, contacts, assistant, health, analytics, etc.)
dashboard/ # UI pages (chat, contacts, health, analytics, schedule, attention, pulse)
login/ # Authentication page
components/ # React components (layout, contacts, chat, health)
lib/
ai/ # Claude integration, prompts, personality config
db/ # Supabase client, queries, schema types
utils/ # Validation, phone normalization, tier config
worker/
scheduler/ # Cron jobs (outreach, birthdays, calls, reports, health scores)
whatsapp/ # WhatsApp client initialization and message handlers
supabase/
migrations/ # 22 SQL migration files (001-021)
__tests__/ # 307 tests across 46 test files
- Onboarding flow — QR → contact import → swipe-deck autonomy opt-in → "does this sound like you?" first-draft trust moment
- Sensitive-topic safety gate — six-category classifier (death, health, financial distress, relationship changes, emotional distress, prior Donna mistake) that drafts instead of auto-sending. Fails closed. Red-teamed with 112 adversarial prompts.
- Global pause switch — one-click pause (1 hour / 24 hours / indefinite) halts every autonomous path. Lives on the settings page.
- Undo window — 2-5 minute cancel button on the activity page for every auto-sent reply. Atomic cancel commits
'cancelled'; worker re-reads right before wire send. - Personality learning — Donna reads your last 50 outbound messages and extracts your writing voice (tone, emoji, greetings, things-to-avoid). Runs on-demand from settings. In-process cache refreshed every 30 min.
- Three-zone dashboard — Triage page splits into "Needs You", "Donna Did", "Relationship Health". Morning digest leads with an "Overnight" hero.
- Trust-breakdown recovery — bulk-cancel + flip-to-monitor quick actions on the activity page.
- Tiered contacts: Inner Circle (weekly), Close (bi-weekly), Casual (monthly)
- Autonomy levels per contact:
off,monitor,draft_review,full_auto(new contacts default tooff— opt-in safety) - Health scores with trend arrows (thriving/stable/cooling/at-risk)
- Communication streak counters with fire badges
- Profile enrichment from conversations (interests, birthdays, context)
- Chat interface powered by Claude Sonnet 4 (
claude-sonnet-4-6alias; override withCLAUDE_MODEL) - Knows all your contacts, recent conversations, and relationship context
- Can update profiles, queue messages, and analyze conversations
- Personality-matched: generates messages in your voice (Denglish support)
| Job | Schedule |
|---|---|
| Outreach messages | Every 30 min, 9am–9pm |
| Group monitor | Every 30 min, 9am–9pm |
| Conversation cleanup | Hourly |
| Pre-call brief generation | Hourly |
| Health score computation | Daily 6am |
| Adaptive outreach adjustment | Daily 6am |
| Knowledge graph population | Daily 7am |
| Daily digest email | Daily 8am |
| Birthday wishes | Daily 9am |
| Birthday prompts | Daily 10am |
| Call reminders | Daily 8am |
| Call scheduling | Sat/Sun 9am |
| Weekly reports | Sunday 8pm |
| Google Calendar sync | Every 15 min, 7am–10pm |
| Message queue processing | Every 30s |
| Send-recovery (stuck-pending retry) | Every 2 min |
| Import job poller | Every 30s |
| Worker heartbeat | Every 60s |
- Network Pulse — system status, health summary, activity feed
- Analytics — daily activity, per-contact metrics, event breakdown (30/90 day)
- System Health — job audit log, dead-letter viewer, worker heartbeat
- Global search across contacts, notes, interests, and context
- Node.js 20+
- A Supabase project
- An Anthropic API key
- A WhatsApp account to connect
npm installCopy .env.example to .env (Docker Compose) or .env.local (Next.js dev) and fill it in:
cp .env.example .env.local
$EDITOR .env.localMinimum required:
# Supabase
SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
# AI
ANTHROPIC_API_KEY=sk-ant-...
# Auth (single-user password for the dashboard)
DASHBOARD_PASSWORD=your-password
# Public URL of the dashboard (used in email links)
NEXT_PUBLIC_APP_URL=http://localhost:3000.env.example documents every variable including optional email (Resend), Google Calendar OAuth, proxy settings, and Chromium path overrides.
Apply the migrations in supabase/migrations/ to your Supabase project, either via the Supabase dashboard SQL editor or the Supabase CLI:
supabase db pushDashboard (Next.js):
npm run devBackground worker (WhatsApp + cron jobs):
npm run worker:devOn first run, the worker will display a QR code at /dashboard/setup — scan it with your WhatsApp mobile app to connect.
Navigate to http://localhost:3000 and enter the password you set in DASHBOARD_PASSWORD.
For the full per-friend runbook (Supabase project, VPS, Docker Compose, QR scan, backup, upgrade), see docs/DEPLOYMENT.md. The short version:
docker-compose.yml brings up the web dashboard and the worker together, with shm_size: 1gb for Chromium and a named volume for WhatsApp session persistence:
cp .env.example .env
$EDITOR .env
docker compose up -d --build
docker compose logs -f workerScan the QR at ${NEXT_PUBLIC_APP_URL}/dashboard/whatsapp. Sessions land on the wwebjs_auth volume — docker compose restart won't force a re-scan; docker compose down -v will.
Deploy the Next.js app to any platform that supports it (Vercel, Railway, self-hosted):
npm run build
npm start| Method | Path | Description |
|---|---|---|
| POST | /api/auth |
Login (returns donna_auth cookie) |
| GET | /api/csrf |
Get CSRF token |
| Method | Path | Description |
|---|---|---|
| GET | /api/contacts |
List contacts (optional ?tier= filter) |
| POST | /api/contacts |
Create contact |
| PATCH | /api/contacts?id= |
Update contact |
| DELETE | /api/contacts?id= |
Delete contact |
| GET | /api/contacts/summary |
Contact summaries by tier |
| PATCH | /api/contacts/:id/autonomy |
Set per-contact autonomy level |
| POST | /api/contacts/bulk-autonomy |
Bulk-set autonomy for onboarding swipe deck |
| Method | Path | Description |
|---|---|---|
| GET | /api/activity |
Recent auto-sent messages with cancel state |
| POST | /api/autopilot/pause |
Pause everything (1h / 24h / indefinite) |
| POST | /api/messages/:id/cancel |
Cancel an auto-send during the undo window |
| Method | Path | Description |
|---|---|---|
| GET | /api/onboarding/state |
Current onboarding step |
| GET | /api/onboarding/swipe-candidates |
Contacts for the autonomy swipe deck |
| POST | /api/onboarding/first-draft |
Generate trust-moment first drafts |
| POST | /api/onboarding/approve-first-draft |
Accept/adjust the first draft |
| Method | Path | Description |
|---|---|---|
| GET | /api/personality |
Current writing-voice profile |
| POST | /api/personality/analyze |
Re-run voice analysis (rate-limited 5/hour per IP) |
| GET | /api/digest/preview |
Preview the morning digest (rate-limited 10/hour per IP) |
| Method | Path | Description |
|---|---|---|
| POST | /api/assistant |
Chat with Claude (message + history) |
| POST | /api/analyze-chat |
Analyze conversation for improvements |
| PATCH | /api/drafts/approve |
Approve a pending draft reply |
| Method | Path | Description |
|---|---|---|
| GET | /api/schedules |
List outreach schedules |
| PATCH | /api/schedules |
Update schedule |
| GET | /api/calls |
List call schedules |
| POST | /api/calls |
Schedule a call |
| GET | /api/calendar/calls.ics |
iCalendar export |
| Method | Path | Description |
|---|---|---|
| GET | /api/health-scores |
Health scores (all or ?contact_id=) |
| GET | /api/analytics |
Activity analytics (?days=30) |
| GET | /api/streaks |
Communication streaks |
| Method | Path | Description |
|---|---|---|
| GET | /api/system/health |
System health + job audit log |
| GET | /api/queue/status |
Message queue status + delivery tracking |
| GET/PATCH | /api/system/dead-letter/:id |
Dead-letter job viewer/resolver |
| GET | /api/whatsapp/status |
WhatsApp connection status |
| GET | /api/search |
Global search (?q=) |
| GET/PATCH | /api/settings |
User settings |
| POST | /api/export |
Full JSON backup |
- Auth: Password-based with SHA-256 hashed session cookie (httpOnly, secure in production, 30-day expiry)
- CSRF: Token validation on all mutating requests (POST/PUT/PATCH/DELETE)
- Middleware coverage:
/api/activity,/api/autopilot,/api/digest,/api/messages,/api/onboarding,/api/personality,/api/queueall auth-gated (contact names and message bodies no longer leak) - Rate limiting: 5 login attempts per 15 minutes per IP; 5/hour on
/api/personality/analyze; 10/hour on/api/digest/preview - Input validation: Prompt injection protection on all user inputs. Call FSM wraps inbound text in
<untrusted_message>and escapes delimiters. - Sensitive-topic gate: inbound messages about death, health, financial distress, relationship changes, emotional distress, or prior Donna mistakes are drafted, not auto-sent. Classifier fails closed on API error, timeout, or parse failure.
- Message dedup: Database-backed WhatsApp message ID index prevents duplicate processing
- Send state machine:
pending → sending → sent/failed/cancelled. Send-recovery only retriespendingrows (atomic claim viamarkMessageSending), preventing the duplicate-delivery class of bug.
npm run dev # Start Next.js dev server
npm run build # Production build
npm start # Start production server
npm run lint # Run ESLint
npm test # Run tests (307 across 46 files)
npm run test:watch # Run tests in watch mode
npm run worker # Run background worker
npm run worker:dev # Run worker with file watchingtsx scripts/red-team-sensitive-topic.ts # 112 adversarial prompts against the sensitive-topic gate┌─────────────────┐ ┌──────────────────┐
│ Next.js App │ │ Background │
│ (Dashboard) │ │ Worker │
│ │ │ │
│ • UI pages │ │ • WhatsApp client│
│ • API routes │ │ • Cron scheduler │
│ • AI assistant │ │ • Message queue │
└────────┬────────┘ └────────┬──────────┘
│ │
└───────────┬───────────┘
│
┌──────┴──────┐
│ Supabase │
│ (PostgreSQL) │
└──────┬──────┘
│
┌──────┴──────┐
│ Claude API │
│ (Anthropic) │
└─────────────┘
The Next.js app serves the dashboard and API. The worker runs independently, connecting to WhatsApp via Chromium and executing scheduled jobs. Both share the same Supabase database and Anthropic API key.