Skip to content

Repository files navigation

Natcusp — Customer Support Platform

AI-native, omnichannel support workspace. Email · Web chat · WhatsApp · Telegram · Generic API · multi-tenant · RAG-grounded draft replies · SLA timers · routing-rule DSL · realtime SSE.

Natcusp is a single-binary, SQLite-backed Intercom/Zendesk-alternative built on Next.js 15 + React 19. Drop one process on a small VPS and you have a complete support workspace — inbox, customer 360, knowledge base, macros, routing rules, SLA, agent presence, AI draft replies, and a public chat widget — for an entire support team.

Highlights

Area What you get
Channels 5 inbound adapters: Mailgun-style email, web chat widget, WhatsApp Cloud webhook, Telegram bot webhook, generic JSON ingest. HMAC signature verification across the board.
Conversation engine Single ingestInbound() funnel: customer + thread resolution, de-dup on external id, routing rules applied, SLA stamped, broadcast fired — all inside one SQLite transaction.
Routing DSL JSON rules (when matchers + then actions) evaluated in priority order. Matchers: channel, priority, intent, keyword, sentiment, business_hours, customer.attribute. Actions: assign_team, assign_user, set_priority, add_tag, set_status, auto_reply, set_sla.
SLA Per-policy first_response_minutes + resolution_minutes. Best-match policy picked from applies_to_json. Live countdown pill in the inbox; breach events broadcast over SSE.
RAG Lightweight TF-IDF over kb_terms (built on save). No external vector DB. Inverted index + per-workspace DF cache. Cosine-ish score with idf².
AI Dual provider (OpenAI default, Anthropic fallback). Per-user keys encrypted at rest with AES-256-GCM (HKDF-derived). Prompts cover draft-reply, classify, sentiment, summarize, suggest-macro, and Atlas (the SSE side-panel co-pilot).
Realtime In-process EventEmitter SSE bus. Defensive teardown — listener exceptions can't kill the bus. Presence heartbeats, typing pings, new-message fanout, SLA-breach pushes.
Widget Public chat widget at /widget?inbox=<id> — visitor-id auto-generated, long-poll for agent replies, no auth required on the visitor side.
Aesthetic "Aurora" — snow-paper light + slate-night dark, electric-violet accent + mint/amber/rose pills. Inter UI, JetBrains Mono for hud/numbers.

Quick start

# 1. Install
npm install

# 2. Configure (.env.local — minimum)
JWT_SECRET=replace-with-a-long-random-string-at-least-16-chars

# 3. Seed the demo workspace (Caspian Telecom MMC)
npm run seed

# 4. Dev server
npm run dev
# http://localhost:5050

Sign in: admin@caspian.demo / demopass. Other seeded agents: sevda@, tural@, elnur@, nigar@, ramin@, lale@ (all demopass).

The seed creates 80 conversations across all 5 channels, 12 KB articles, 14 macros, 5 routing rules and 4 SLA policies so the UI is populated from the first page load.

Project map

app/
  (app)/                  ← authenticated shell (Sidebar + TopBar + AssistantProvider)
    dashboard/            ← KPIs + SLA heatmap + channel volume + agent load
    inbox/[bucket]/[id]/  ← 3-column view: list + thread + customer 360
    customers/            ← list + 360 detail
    kb/                   ← knowledge base editor
    macros/ rules/ sla/   ← admin DSL editors
    audit/ notifications/ settings/
  api/
    auth/                 ← login / register / logout / me
    ai/                   ← providers, draft, classify, sentiment, summarize, suggest-macro, atlas (SSE)
    conversations/        ← list + detail + messages + assign + status + tags + typing
    channels/             ← email, whatsapp, telegram, web, api  ← webhook receivers
    events/               ← SSE stream (workspace-scoped)
    {inboxes,kb,macros,rules,sla,tags,teams,presence,customers,users,dashboard,audit}
  widget/                 ← public chat widget (iframe-friendly)
lib/
  db.ts                   ← migrations + audit() helper
  types.ts                ← Channel | ConvStatus | Priority | SenderKind | Matcher | Action
  auth.ts                 ← bcrypt + JWT 7d cookie, requireUser/requireRole
  crypto.ts               ← AES-256-GCM via HKDF
  channels.ts             ← ingestInbound() + sendOutbound() funnel
  routing.ts              ← rule DSL evaluator
  sla.ts                  ← stampInitialSla + checkBreaches + markResolved
  rag.ts                  ← TF-IDF tokenizer + scorer
  events.ts               ← in-process EventEmitter (with defensive broadcast)
  ai/                     ← provider abstraction (openai / anthropic / prompts)
  webhooks.ts             ← HMAC verification helpers
components/
  Sidebar / TopBar / Modal / ConfirmModal / PromptModal
  ChannelBadge / StatusPill / PriorityPill / SlaPill / Avatar
  ThemeToggle / AssistantProvider / AssistantTrigger
  PresenceHeartbeat / EventStream
  Brand / Logo
scripts/
  seed.ts                 ← reproducible Caspian Telecom demo

Webhook recipes

Email (Mailgun-style)

curl -X POST http://localhost:5050/api/channels/email \
  -H 'content-type: application/json' \
  -d '{
    "recipient": "support@caspian.demo",
    "sender":    "vusal@example.az",
    "subject":   "Cannot send SMS abroad",
    "body-plain":"Hi, my SMS to UK numbers fails since yesterday.",
    "Message-Id":"<msg-001@mail.example.az>"
  }'

WhatsApp (Cloud API)

curl -X POST http://localhost:5050/api/channels/whatsapp \
  -H 'content-type: application/json' \
  -d '{ "entry": [{ "changes": [{ "value": {
    "metadata": { "phone_number_id": "994702000000" },
    "contacts": [{ "wa_id": "994552223344", "profile": { "name": "Aysel" } }],
    "messages": [{ "id":"wamid.xyz","from":"994552223344","text":{ "body":"Where is my refund?" } }]
  } }] }] }'

Telegram (bot webhook)

curl -X POST 'http://localhost:5050/api/channels/telegram?bot=@caspian_support_bot' \
  -H 'content-type: application/json' \
  -d '{ "message": { "message_id": 99, "chat": { "id": 555000, "first_name": "Murad" }, "text": "Outage in Sumqayit?" } }'

Generic API

curl -X POST http://localhost:5050/api/channels/api \
  -H 'content-type: application/json' \
  -d '{
    "inboxId": "<api inbox id from seed>",
    "contact": { "handle": "user-001", "displayName": "Test User", "email": "u1@example.com" },
    "body":    "App crashes when I open the wallet tab."
  }'

AI providers

Settings → AI providers → + Add key. Pick OpenAI or Anthropic, paste the key (we store it AES-256-GCM-encrypted, derived from NATCUSP_ENC_KEY via HKDF). The Test button hits each provider's cheapest verification call.

OpenAI is the default; Anthropic is the fallback. Server-only jobs (seeding, scheduled summarisation) can also read OPENAI_API_KEY / ANTHROPIC_API_KEY from the environment when no per-user key is present.

Realtime / SSE

A single shared EventSource('/api/events') fans out to the rest of the app via window.dispatchEvent('natcusp:event', ...). The inbox list and conversation view debounce-refresh on new_message, conversation_updated and sla_breach. Typing pings auto-expire 3.5s after the last event.

The bus uses Node's EventEmitter with setMaxListeners(0) and a defensive try/catch around emit() so a single dead SSE connection can't take down unrelated API requests (a bug we shipped to DA-EMP once and never want to ship again).

Production notes

  • Swap SQLite for Postgres by re-pointing lib/db.ts. The SQL is mostly portable; the datetime('now') defaults and INSERT…ON CONFLICT…DO UPDATE syntax both translate cleanly.
  • Replace the in-process bus with Redis pub/sub for multi-node deployments — the bus() abstraction stays the same.
  • Set JWT_SECRET and NATCUSP_ENC_KEY to 32+ random characters before going to production. The dev defaults in seed.ts are deliberately weak.
  • Webhook secrets are stored encrypted in channel_inboxes.secret_enc. Set them via the Inboxes UI; never commit raw secrets to .env.

Licence

MIT. Make it yours.

About

AI-native omnichannel customer support workspace — Intercom/Zendesk alternative, single-binary Next.js + SQLite. Email, web chat, WhatsApp, Telegram, generic API; RAG-grounded AI drafts; routing-rule DSL; SLA timers; realtime SSE.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages