Supo is a multi-tenant AI support platform for businesses. It helps teams automate customer support with AI, escalate complex cases to human agents, and manage conversations, knowledge, widget configuration, admin controls, and analytics from one product.
Status: alpha. Supo is actively being built and should be treated as an early product, not a stable public API.
- Embeddable customer support widget with streaming AI replies.
- Product-scoped inbox for customer conversations.
- Knowledge base ingestion from articles, URLs, GitHub repositories, and product sitemaps.
- Retrieval-augmented AI answers with source-aware context.
- Automated knowledge suggestions from resolved conversations.
- Missing-knowledge detection that creates reviewable KB gaps.
- Review queue for approving or rejecting generated knowledge updates.
- Super-admin visibility across users, workspaces, products, conversations, and knowledge activity.
- Auth hardening, rate limits, audit logs, and security activity tracking.
flowchart LR
subgraph Ingest["Indexing (background jobs)"]
S["Sources: articles, files, websites, GitHub"] --> X["Extract text"] --> C["Chunk by heading"] --> E["Gemini embeddings"] --> DB[("Postgres: pgvector + full-text")]
end
subgraph Answer["Answering a customer"]
Q["Customer message"] --> R["Vector + keyword search, merged with RRF"]
DB --> R
R --> H{"Handoff needed?"}
H -- no --> G["Gemini answer with citations, streamed to the widget"]
H -- yes --> I["Shared inbox: agent takes over"]
R -. "nothing relevant" .-> K["Missing-knowledge gap for review"]
end
A conversation is handed to a person when the customer asks for one, seems frustrated, or the AI repeatedly finds nothing relevant. Unanswered questions become review items, so the knowledge base improves from real conversations.
Supo is organized around five product areas:
- Customer Widget - embedded chat, AI responses, conversation persistence, and escalation entry points.
- AI Orchestration Layer - prompt orchestration, retrieval, model fallback, confidence signals, and guardrails.
- Support Core - workspaces, products, customers, conversations, messages, tickets, and events.
- Agent Workspace - shared inbox, conversation timeline, human replies, and learning from support cases.
- Admin, Knowledge, and Analytics - knowledge management, team controls, admin oversight, and reporting.
- Framework: Next.js 16 App Router
- Runtime: React 19, TypeScript
- Styling: Tailwind CSS 4, shadcn-style primitives, shared theme tokens
- Auth: Better Auth with organization and admin plugins
- Database: Neon Postgres with Drizzle ORM
- Vector Search: pgvector with Gemini embeddings
- AI: Vercel AI SDK v6 with Google Gemini/Gemma model fallback
- Realtime: Ably with widget polling fallback
- Rate Limiting: Upstash Redis
- Email: Resend
- Deployment: Vercel
src/app/
(auth)/ Auth pages
(app)/
(workspace)/dashboard Workspace/product dashboard
(workspace)/settings Workspace settings
admin/ Super-admin overview and controls
products/[id]/
inbox/ Agent inbox
knowledge/ KB sources, suggestions, and test panel
widget/ Widget configuration
analytics/ Analytics surface
api/
auth/[...all]/ Better Auth route
ably/token Short-lived Ably token requests
chat/ Public widget chat API
chat/escalate Widget-to-agent escalation
knowledge/ingest Background KB ingestion
knowledge/query RAG query endpoint
messages/poll Widget polling fallback
cron/sync-knowledge Daily knowledge sync
src/components/ Shared app and marketing components
src/db/ Drizzle schema and database entrypoint
src/lib/ Auth, env, admin, security, and knowledge utilities
drizzle/ SQL migrationsSources: articles (markdown), single pages, whole websites (crawled via robots.txt / sitemap.xml, sitemap indexes, or by following links under the start URL; up to 200 pages), GitHub repositories, Confluence spaces, and approved answers learned from conversations. URL, website, GitHub and Confluence sources re-sync daily and are only re-embedded when their content changes; unchanged pages and files reuse their stored embeddings.
GitHub App: repositories connected through the Supo GitHub App (read-only, private repositories included) index README, changelog and documentation files anywhere in the repository (Markdown, MDX, reStructuredText, AsciiDoc), plus release notes and optionally recent issues. Source code, tests and dependencies are skipped. Pushes to the tracked branch, releases and issue changes re-index through the GitHub webhook, and only changed files are re-embedded. Answers from private repositories never link customers to the files. Public repositories can still be added by URL without the app.
Confluence: a workspace connects Atlassian once (OAuth 2.0, read-only page and space scopes), then adds spaces from a list. Every current page in a space is indexed (up to 400, most recently edited first), with Confluence markup such as code blocks, expand titles, tasks and page links converted to text. Pages are re-synced daily and only edited pages are re-embedded. Citations show page titles but never link to Confluence, because customers can't open those pages. Tokens are stored encrypted, and refreshes are serialized per workspace because Atlassian rotates the refresh token on every use. If access is revoked, sources keep serving their last index and show a reconnect message. Disconnecting (workspace owners and admins) removes every Confluence source in the workspace.
Pipeline (src/lib/knowledge/*):
safe-fetchdownloads pages with SSRF protection (private/internal addresses blocked, redirects re-validated), timeouts and size limits.extractturns HTML into structured text (headings, paragraphs, lists), dropping navigation, headers, footers and scripts.chunksplits by heading, then paragraph, then sentence, into passages of at most 1,200 characters with sentence overlap. The page title and heading path are embedded with each passage.- Gemini embeddings (768 dimensions) are stored in pgvector; Postgres maintains a full-text
tsvectorfor each passage. retrieveruns vector and keyword search in parallel, merges them with Reciprocal Rank Fusion and keeps only relevant passages. Retrieval is scoped to the product (pgvector ≥ 0.8 uses HNSW iterative scans; older versions use an exact per-product scan).promptbuilds one grounded system prompt, shared by the widget and the admin test panel. AI answers carry citations that the widget shows as source links.
Background jobs: indexing runs on a Postgres job queue (background_job, src/lib/jobs). Jobs start right after the request through Next.js after(), retry with backoff, and are recovered by cron if a function dies. /api/cron/sync-knowledge (daily) re-queues stuck sources, syncs stale ones and drains the queue; /api/cron/jobs drains the queue. On Vercel Pro you can schedule /api/cron/jobs every few minutes.
Review queue:
- Resolved conversations can produce pending FAQ suggestions.
- Unanswered customer questions become missing-knowledge gaps. Small talk is ignored and similar questions are merged with an "asked N×" counter.
- Reviewers can write answers to gaps and approve them into the knowledge base, or reject them.
- Re-indexing swaps chunks atomically, so the previous index keeps serving if a refresh fails.
- When: the customer asks for a person, is frustrated, or the knowledge base misses twice in a row (
src/lib/handoff/policy.ts). Customers can also press "talk to a person". - While waiting: the AI keeps helping and the customer can keep typing. Agents see the conversation live in the inbox's Waiting view, with an AI-written summary and the handoff reason.
- Nobody online: the customer is told the team will follow up by email (and asked for an address if needed), and inbox members are emailed. After the product's wait time (Widget → Human handoff), a reminder goes out.
- Agents: claim (atomic), take over, transfer, return to queue, hand back to AI, snooze, resolve. System messages keep the customer informed. If the customer has left, agent replies are also emailed to them.
- Identity verification: generate a secret under Widget → Identity verification, then compute
HMAC-SHA256(secret, externalId ?? email)on your server and pass it ascustomer.userHash. Verified customers can resume their conversations across devices; without verification, a conversation can only be resumed with its private token.
- Inbox views: Waiting, Mine, Open, Resolved and All, with full-text search, bulk resolve and
j/knavigation. - Composer: reply or internal note,
/macros with variables and actions, and "Draft with AI" grounded in the knowledge base. - Customer sidebar: priority, tags, profile, identity verification status and previous conversations.
- Inbox settings: manual claiming or round-robin routing with a per-agent cap, and macro management.
- Activity: recent customer threads, plus a product audit log for admins and developers.
- Authoring (Help center page): categories, Markdown articles with live preview, draft/publish, version history with restore, and import from PDF, Word (.docx), Markdown, text, CSV or HTML (up to 4 MB).
- Public site at
/help/{slug}: category home page, full-text search (stemmed Postgres FTS plus title prefix matching), article pages with "Was this helpful?" voting, related articles, canonical URLs, Open Graph and Article JSON-LD. Published articles are listed in/sitemap.xml. - AI and widget: published articles are mirrored into the knowledge base automatically, so the AI answers from them and cites the public article URL. The widget suggests popular articles before the first message.
- Article Markdown is rendered with
react-markdown(no raw HTML, unsafe URL schemes stripped). - The Knowledge page's File source type uses the same document parser.
- REST API (
/api/v1): conversations, messages and notes, customers, knowledge sources and search, and help center articles. Authenticated with product-scoped API keys (supo_sk_…, stored as SHA-256 hashes), with per-scope permissions, optional expiry, revocation, last-used tracking and per-key rate limits. - Webhooks: HMAC-signed (
Supo-Signature) deliveries for conversation, message, rating and article events. Retries go through the job queue with backoff, there is a per-endpoint delivery log with redelivery and a "Send test" button, and failing endpoints are paused automatically. Outbound requests use the SSRF-safe client. - Manage both under Integrations. Full reference:
docs/API.md.
- Email channel (Inbox settings): each product gets an inbound address on
INBOUND_EMAIL_DOMAIN, and you forward your support mailbox to it.- Inbound: Resend's signed
email.receivedwebhook queues a job that fetches the email, strips quoted history and opens or continues a conversation. Threading matches our Message-IDs, then the customer's ownReferences, then sender + subject. - Routing: emails go straight to the team, with SLAs and routing.
- Outbound: agent replies (dashboard or API) are emailed from the product address with
In-Reply-To/Referencesheaders. - Filtering: auto-replies, bounces and mail loops are ignored.
- Inbound: Resend's signed
- GDPR tools:
- product admins can export a customer's data (JSON) or erase the customer and all their conversations from the inbox sidebar
- the API has
GET /customers/{id}/exportandDELETE /customers/{id} - erasure also removes webhook delivery logs that copied the data
- Data retention (Inbox settings, admins): resolved conversations older than N days are deleted automatically, along with customers left without conversations. Webhook delivery logs are kept for 30 days.
- Business hours (Inbox settings): a timezone, a weekly schedule and holidays. Outside business hours customers are told the team will follow up by email, even if someone is signed in.
- SLA targets per priority (first response and resolution, in business minutes). Due dates are set when a conversation is handed to a human and recomputed when its priority changes. The inbox shows countdown and breach badges and can sort by "Due soon". The job runner emails the team once, 10 minutes before a first-response breach.
- CSAT: when an agent resolves a conversation, the widget asks for a 1–5 rating and an optional comment (
POST /api/widget/ratings, conversation-token auth, 7-day window). Ratings show in the inbox and are credited to the resolving agent. Turn it off under Inbox settings. - Analytics: 7/30/90-day windows (days in the business-hours timezone) compared with the previous period: AI deflection, first-response and resolution medians and p90, SLA attainment, CSAT, knowledge-base answer rate, per-agent performance, top unanswered questions and a conversation-level CSV export.
Supo currently includes early-stage SaaS hardening:
- Email/password verification is enforced.
- Google and GitHub OAuth are supported.
- Better Auth trusted origins are configured by environment.
- Super-admin access requires both verified email and exact env-configured Better Auth user id.
- Admin impersonation is intentionally disabled.
- Super-admin actions are audited.
- Admin can view/revoke sessions and ban/unban users.
- Public auth and all widget endpoints (including
/api/widget/*aliases) are rate limited through Upstash Redis. Better Auth's built-in limiter keeps its counters in the same Redis. - Products can restrict the widget to an allow-list of website origins (Widget → Allowed websites).
- Integration tokens (Atlassian) are encrypted at rest with AES-256-GCM under
INTEGRATIONS_ENCRYPTION_KEY, bound to their workspace and purpose. - Realtime tokens are scoped to a single product; agents cannot subscribe to other products' conversations or publish inbox events.
- Internal and cron routes use a dedicated
CRON_SECRETwith constant-time comparison. - Public widget conversation reads/subscriptions require
conversationId + conversationToken. - Widget polling is rate limited and rejects invalid
sincetimestamps. - Proxy-level checks reject oversized chat requests and invalid auth POST requests.
- Route-level quotas limit expensive AI and knowledge operations.
- Ably powers realtime inbox/widget updates and gracefully falls back to polling when unavailable.
- Suspicious new IP/user-agent fingerprints are logged after app auth.
- Owner alerts are sent for new super-admin fingerprints.
Paid WAF, CAPTCHA, bot-management products, and domain-based email hardening are intentionally deferred.
- Bun
- Node.js compatible with Next.js 16
- Neon Postgres database
- Upstash Redis database
- Google OAuth app
- GitHub OAuth app (sign-in)
- GitHub App (optional, connects repositories to the knowledge base)
- Atlassian OAuth 2.0 app (optional, connects Confluence spaces to the knowledge base)
- Google Gemini API key
- Resend API key
- Ably API key for realtime features
Copy .env.example to .env.local and fill in the values for your environment.
Notes:
DATABASE_URLshould usesslmode=verify-fullfor production Neon connections.BETTER_AUTH_URLmust match the deployed app URL in production.BETTER_AUTH_TRUSTED_ORIGINS(optional) lists extra exact origins allowed to use auth, comma-separated. This deployment's own Vercel URLs are trusted automatically.BETTER_AUTH_ADMIN_USER_IDSshould contain only the trusted owner/admin Better Auth user id.- Upstash Redis is required in production. Local development fails open when Redis env vars are absent.
- The email channel is optional. Set
INBOUND_EMAIL_DOMAIN(a receiving domain in Resend) andRESEND_WEBHOOK_SECRET(the signing secret of a Resend webhook foremail.receivedpointed at/api/inbound/email). - The GitHub App is optional. Set
GITHUB_APP_ID,GITHUB_APP_SLUG,GITHUB_APP_CLIENT_ID,GITHUB_APP_CLIENT_SECRET,GITHUB_APP_PRIVATE_KEY(the PEM, multi-line or with\nescapes) andGITHUB_APP_WEBHOOK_SECRET. Configure the app with callback URL/api/integrations/github/callbackand "Request user authorization (OAuth) during installation" enabled, webhook URL/api/webhooks/github, read-only Contents, Metadata and Issues permissions, and the Push, Release, Repository and Issues events. It is separate from the GitHub OAuth app used for sign-in. - Confluence is optional. Create an OAuth 2.0 (3LO) integration in the Atlassian developer console with the Confluence scopes
read:space:confluenceandread:page:confluenceand callback URL/api/integrations/atlassian/callback. SetATLASSIAN_CLIENT_ID,ATLASSIAN_CLIENT_SECRETandINTEGRATIONS_ENCRYPTION_KEY(openssl rand -base64 32). Keep the key stable: changing it makes stored tokens unreadable, and each workspace has to reconnect. NEXT_PUBLIC_SUPO_DEMO_PRODUCT_ID(optional) shows that product's widget on the landing page as a live demo. It is read at build time.ABLY_API_KEYenables realtime inbox/widget updates. The widget keeps polling as a fallback if Ably is absent.CRON_SECRETauthorizes Vercel Cron and internal job routes. Vercel sends it asAuthorization: Bearer $CRON_SECRETautomatically when the variable is set on the project.DATABASE_SSL=disable(orsslmode=disablein the URL) turns off TLS for a local Postgres.- Resend's
onboarding@resend.devsender is only reliable for the Resend account owner's inbox until a verified domain is configured.
Install dependencies:
bun installRun database migrations:
bun run db:migrateStart the development server:
bun run devOpen:
http://localhost:3000bun run dev # Start local dev server
bun run lint # Run ESLint
bun run typecheck # Type-check the app
bun run test # Run unit tests (Vitest)
bun run build # Production build and TypeScript check
bun run db:generate # Generate Drizzle migrations
bun run db:migrate # Apply Drizzle migrationsThe Drizzle schema lives in:
src/db/schema.tsCurrent core tables include:
- Better Auth:
user,session,account,verification - Organization plugin:
organization,member,invitation - Supo app data:
product,widget_config,customer,conversation,message - Knowledge:
knowledge_source,knowledge_suggestion,knowledge_chunk - Integrations:
github_installation,atlassian_connection - Admin/security:
admin_audit_log,security_event
Migrations from 0017 onward are hand-written, idempotent SQL registered in drizzle/meta/_journal.json (the Drizzle snapshots for 0012–0016 are missing, so do not rely on db:generate until a fresh baseline snapshot is created).
0018_repair_knowledge_schema repairs databases that skipped the knowledge-base migrations: journal entries 0002–0005 carry older timestamps than 0001, and Drizzle only applies migrations newer than the last applied one. To check a database, run:
select id, hash, created_at from drizzle.__drizzle_migrations order by created_at;Migrations live in:
drizzle/Supo is designed for Vercel deployment.
Before deploying:
- Set all required environment variables in Vercel.
- Set
BETTER_AUTH_URLto the production URL. - Ensure
DATABASE_URLuses secure SSL settings. - Set Upstash Redis env vars. Without them rate limiting fails open.
- Run migrations against the production database.
- Run
bun run lintandbun run build.
To show a live demo on the landing page:
- Create a product (for example "Supo") in your workspace and add its knowledge sources, such as this repository through the GitHub App.
- Under Widget → Allowed websites, add the production origin, or leave the list empty.
- Set
NEXT_PUBLIC_SUPO_DEMO_PRODUCT_IDto the product id and redeploy. Per-product quotas cap AI usage from public visitors.
Recommended first public tag:
v0.1.0-alpha.1Use an alpha tag until the product has:
- Stable onboarding.
- A verified production email domain.
- A clearer billing/usage story.
- More mature analytics and abuse monitoring.
- A mature support process for production users.
See CHANGELOG.md for release notes and SECURITY.md for the current alpha security policy.
AGENTS.mdis the living architecture and implementation guide for AI coding agents.- The app name is
Supo. - Do not introduce AWS services.
- Prefer Neon, Drizzle, Better Auth, Vercel, Upstash, Resend, and Cloudflare R2 where needed.
- Keep product-scoped data under
product. - Keep workspace tenancy anchored to Better Auth
organization.
No license has been selected yet. Until a license is added, this repository is not open source by default.