AI-SmartSupport is a multi-tenant, AI-powered helpdesk platform that automates ticket triage, intelligent routing, and agent workload balancing. Built on an event-driven architecture with Google Gemini for NLP-based classification and prioritization.
Production setup (MongoDB, API, Inngest, Next.js, env vars, CORS) is documented in DEPLOY.md.
# 1. Install dependencies
npm install
# 2. Copy environment variables and fill in secrets
cp .env.example .env
# 3. Start the Inngest dev server (separate terminal)
npm run inngest:dev
# 4. (Optional) Seed demo org + users + tickets in MongoDB
npm run seed
# 5. Start the application
npm run devThe API runs at http://localhost:3000 with the Inngest dashboard at http://localhost:8288.
The SaaS dashboard and marketing site live in web/.
cd web
cp .env.local.example .env.local
# Ensure NEXT_PUBLIC_API_URL points at the API (default http://localhost:3000)
npm install
npm run devThe UI defaults to port 3001 (http://localhost:3001). Run the API on 3000 and the web app on 3001 in separate terminals.
An optional native client lives in android/. It uses the same backend-oriented patterns (ViewModel, repository, Firebase Crashlytics/Analytics, release R8) as a production baseline. Build with JDK 17+ and the Android SDK:
cd android
./gradlew :app:assembleDebugReplace android/app/google-services.json with the file from the Firebase Console for package com.smartsupport.app if you want real Crashlytics and Analytics. Optional API base URL: copy android/local.properties.example to local.properties and set API_BASE_URL. More detail: android/BUILD.txt and android/PERF.md.
Set in web/.env.local:
NEXT_PUBLIC_DEMO_MODE=trueRestart the Next.js dev server. Use View demo on the marketing home or login page to open the dashboard with in-memory sample data (tickets, analytics, notifications). Changes persist for the browser session only and are not sent to the API. Omit this flag for normal development against the Express backend.
Production CORS: set CORS_ORIGIN in the API .env to your deployed web origin (comma-separated if multiple), e.g. https://app.example.com. Leave it unset in local dev to allow any origin.
android/ # Kotlin + Compose client (Firebase, OkHttp/Retrofit) β optional
web/ # Next.js 15 β marketing + authenticated app (TanStack Query, shadcn/ui)
scripts/ # e.g. MongoDB seed script (npm run seed)
src/
βββ config/ # Database, env validation, structured logging
βββ models/ # Mongoose schemas + tenant-scoping plugins
βββ middleware/ # Auth, RBAC, rate limiting, quota enforcement, error handling
βββ modules/
β βββ auth/ # Registration, login, JWT rotation + reuse detection
β βββ tickets/ # Full lifecycle management, event emission, validation
β βββ users/ # Profile management, org-scoped user listing
β βββ moderators/ # Skill-based routing, workload-aware assignment
β βββ notifications/ # Transactional email + in-app notification delivery
β βββ ai/ # Gemini integration, prompt engineering, schema-validated responses
β βββ analytics/ # Aggregation pipelines β ticket KPIs + agent performance
β βββ billing/ # Quota middleware, usage summary API, checkout placeholder
β βββ organizations/ # Tenant provisioning, member invitations, org settings
βββ inngest/ # Event-driven workflow orchestration
β βββ functions/ # AI triage, SLA escalation, CSAT collection
βββ utils/ # Cursor pagination, typed errors, retry helpers
βββ app.js # Express middleware pipeline
βββ server.js # Server bootstrap + graceful shutdown
Gemini analyzes inbound tickets to extract category, priority (P0βP3), required skill tags, and structured agent guidance notes. All AI responses are schema-validated via Zod to guarantee downstream contract compliance.
Tickets are routed to the best-fit agent using regex-scored skill matching against agent profiles. Ties are broken by current workload (open ticket count), with automatic admin fallback when no qualified agent is available.
Inngest orchestrates background workflows decoupled from the request cycle:
- Ticket Created β AI triage + auto-assignment pipeline
- Hourly Cron β SLA breach detection + escalation
- Ticket Resolved β CSAT survey dispatch after configurable cooldown
Organization-scoped data partitioning via a Mongoose query plugin. Every query is automatically filtered by orgId through middleware injection β no tenant data leakage by default.
Three-tier permission model enforced at the route level:
| Role | Scope |
|---|---|
| User | Create tickets, view own tickets, manage profile |
| Moderator | All user permissions + ticket updates, assignment, org user listing |
| Admin | Full access β org settings, analytics dashboards, skill management, soft deletes |
Short-lived access tokens (15 min) paired with single-use refresh tokens (7 day). Refresh token reuse triggers immediate revocation of all active sessions for that user, mitigating token-theft replay attacks.
Dual-channel delivery β templated transactional emails (Handlebars + Nodemailer) and in-app notification polling. Supports Mailtrap in development and SES in production.
MongoDB aggregation pipelines powering:
- Ticket KPIs β Volume trends, category/priority breakdowns, resolution time distributions (30-day window)
- Agent Performance β Tickets handled, avg. resolution time, workload distribution
Plan-tier limits enforced via middleware before ticket creation:
| Plan | Monthly Ticket Quota |
|---|---|
| Free | 10 |
| Pro | 500 |
| Enterprise | Unlimited |
Billing: monthly quotas are enforced in middleware; GET /api/billing/summary exposes plan and current-month ticket usage for the dashboard. Stripe Checkout is not wiredβPOST /api/billing/checkout returns a placeholder message for future activation.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/register |
Provision user + create/join tenant |
| POST | /api/auth/login |
Authenticate, returns token pair |
| POST | /api/auth/refresh |
Rotate refresh token |
| POST | /api/auth/logout |
Revoke refresh token |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/tickets |
User+ | Submit ticket (triggers triage pipeline) |
| GET | /api/tickets |
User+ | List with filters + cursor pagination |
| GET | /api/tickets/:id |
User+ | Ticket detail + audit trail |
| PATCH | /api/tickets/:id |
Moderator+ | Update status / reassign |
| DELETE | /api/tickets/:id |
Admin | Soft delete (preserves audit trail) |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/users/me |
Any | Current user profile |
| PATCH | /api/users/me |
Any | Update profile |
| GET | /api/users |
Moderator+ | List tenant users |
| GET | /api/moderators |
Moderator+ | List agents + workload stats |
| PATCH | /api/moderators/:id/skills |
Admin | Update agent skill tags |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/org |
Any | Tenant details |
| PATCH | /api/org |
Admin | Update tenant settings |
| POST | /api/org/invite |
Admin | Invite member via email |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/notifications |
Any | Poll in-app notifications |
| PATCH | /api/notifications/:id/read |
Any | Mark notification as read |
| GET | /api/analytics/tickets |
Admin | Ticket KPI dashboard (30-day) |
| GET | /api/analytics/moderators |
Admin | Agent performance metrics |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/billing/summary |
Any | Plan tier, monthly ticket count vs quota |
| POST | /api/billing/checkout |
Any | Placeholder until Stripe is enabled |
| Layer | Technology |
|---|---|
| Mobile (optional) | Kotlin, Jetpack Compose, Firebase (Crashlytics/Analytics), OkHttp/Retrofit |
| Runtime | Node.js (ESM) |
| Framework | Express |
| Database | MongoDB (Mongoose ODM) |
| Auth | JWT (access + refresh token rotation) |
| Background Jobs | Inngest (event-driven orchestration) |
| AI / NLP | Google Gemini via @inngest/agent-kit |
| Validation | Zod (runtime schema enforcement) |
| Nodemailer (Mailtrap dev / SES prod) | |
| Templates | Handlebars |
| Logging | Winston (structured) + Morgan (HTTP) |
| Rate Limiting | express-rate-limit (global + per-tenant) |
Audit events are stored in a dedicated auditlogs collection rather than embedded in ticket documents. This avoids the 16 MB BSON document size limit for high-volume tickets, enables independent compliance queries, and supports efficient time-range scans via compound (ticketId, timestamp) indexing. Trade-off: requires an additional query when loading a ticket's full history.
_id-based cursor pagination prevents page drift caused by concurrent writes and avoids the O(n) cost of skip() on large collections. Trade-off: clients must paginate sequentially β no random page access.
Tickets use a deletedAt timestamp instead of physical deletion. Mongoose query middleware auto-filters deleted documents, preserving audit trail integrity and enabling recovery. Trade-off: slightly more complex queries when admins need to surface deleted records.
Single-use refresh tokens with family-based revocation. If a previously consumed token is presented again, all tokens for that user are invalidated β a strong signal of credential theft. Trade-off: one DB lookup per refresh request.
The org-scoping Mongoose plugin reads orgId from query options rather than relying on AsyncLocalStorage or CLS. This keeps tenant context explicit and avoids CLS performance overhead. Trade-off: services must pass { orgId } as a query option.
Plan-tier limits are enforced in Express middleware rather than in the service layer, keeping billing concerns decoupled from business logic. Only applied to ticket-creation routes to minimize overhead.
- HTTP 429 on
/api/tickets,/api/org, etc. β The API usesexpress-rate-limit(global per IP + a stricter per-organization limit on POST /api/tickets because it triggers AI triage). Limits are much higher inNODE_ENV=developmentthan in production. If you still hit 429 after many requests, wait for the 15-minute window to reset or restart the dev server (limits use an in-memory store by default). - Invitation or notification email not received β Ensure
MAILTRAP_SMTP_USERandMAILTRAP_SMTP_PASSare set to a valid Mailtrap inbox (see.env.example). If SMTP fails,POST /api/org/invitereturns 503 with a hint (the invited user is not left half-created). Check server logs forEmail failedorInvite email failed.
Copy .env.example to .env and fill in the required values. The server validates all required variables at startup and fails fast on missing configuration.
- Inngest β Event-driven workflow orchestration
- Google Gemini β NLP-powered ticket analysis
- Mailtrap β Transactional email testing
- MongoDB β Document database