@consilioweb/payload-support
v2.0.1
Published
Payload CMS plugin — professional support & ticketing system with AI, SLA, time tracking, live chat, and more
Maintainers
Readme
📑 Table of Contents
- Features
- Quick Start
- Installation
- Usage
- API Reference
- Configuration
- Performance
- Examples
- FAQ
- Troubleshooting
- Security
- Contributing
- Changelog
- Roadmap
- Support
- License
✨ Features
🎫 Complete ticketing
Statuses, priorities, categories, tags, merge/split, snooze, scheduled replies, internal notes, an immutable activity log and a keyboard-driven Superhuman-style inbox.
⏱️ SLA & automation
SLA policies (business hours + pause-on-hold), escalation, macros, round-robin, and a visual automation-rules engine (conditions → actions).
🤖 Built-in AI
Sentiment, reply suggestions, multi-style rewriting, cached synthesis, a KB chatbot, and an autonomous AI agent that answers or escalates. Anthropic, OpenAI or self-hosted Ollama.
💬 Omnichannel & portal
Inbound/outbound email, live chat (SSE), widget, a full client portal (auth, 2FA, Google OAuth), knowledge base and deflection.
💶 Time tracking & billing
Timer, manual entries, time dashboard, per-project pre-billing and a print-ready invoice (HTML → PDF).
📊 Reporting & privacy
Real-time dashboard, CSAT + NPS, email tracking, HMAC-signed webhooks, notification digests, CSV exports. 100% self-hostable.
🚀 Quick Start
// payload.config.ts
import { buildConfig } from 'payload'
import { supportPlugin } from '@consilioweb/payload-support'
export default buildConfig({
plugins: [
supportPlugin({
features: { ai: true, sla: true, timeTracking: true, chat: true },
ai: { provider: 'anthropic', model: 'claude-haiku-4-5-20251001' },
locale: 'fr',
}),
],
})pnpm payload generate:importmap
pnpm devOpen /admin/support/inbox for the agent inbox, or /support for the client portal. The plugin injects collections, API endpoints and admin views automatically — no external SaaS.
📦 Installation
npm
npm install @consilioweb/payload-supportyarn
yarn add @consilioweb/payload-supportpnpm
pnpm add @consilioweb/payload-supportPeer dependencies: payload@^3, react@^18 || ^19, react-dom@^18 || ^19, next@^14 || ^15 || ^16. lucide-react and the @payloadcms/* packages are optional depending on the features you enable. After adding admin components, run pnpm payload generate:importmap.
💻 Usage
Basic
// All features are on by default — turn off what you don't need:
supportPlugin({ features: { chat: false, pendingEmails: false }, locale: 'en' })Advanced
supportPlugin({
features: { ai: true, sla: true, roundRobin: true, webhooks: true, snooze: true },
rateLimitStore: 'payload', // shared, persistent limits for multi-instance deployments
ticketNumber: { prefix: 'TK-', padding: 6 },
ai: { provider: 'ollama', model: 'qwen2.5', baseUrl: process.env.OLLAMA_API_URL },
email: { fromName: 'Support ACME', fromAddress: '[email protected]', replyTo: '[email protected]' },
allowedEmailDomains: ['acme.com'], // restrict OAuth auto-registration
collectionSlugs: { tickets: 'support-tickets' }, // slug overrides
navGroup: 'Support',
basePath: '/support',
})Deployment adapters
Version 2 keeps provider-specific code in the host application while the plugin owns the generic support workflow. Optional capabilities include SMS, inbound email, digests, AI titles and summaries, detailed billing, volunteering, thread cleanup and project suggestions.
supportPlugin({
rateLimitStore: 'payload',
capabilities: {
sms: {
adapter: {
isConfigured: () => Boolean(process.env.SMS_PROVIDER_ACCOUNT),
send: async ({ message, to }) => mySmsProvider.send({ message, to }),
},
},
inboundEmail: {
secret: process.env.SUPPORT_WEBHOOK_SECRET,
secretHeader: 'x-webhook-secret',
handle: handleInboundSupportEmail,
},
detailedBilling: true,
volunteering: true,
},
})Upgrading from 1.x
Version 2.0 is a security and ownership release with intentional breaking changes:
- Generate an additive Payload migration for the new
support-counterscollection and, whenrateLimitStore: 'payload'is enabled,support-rate-limits. Run it before restart. - Regenerate Payload types and the admin import map.
- Remove duplicated host routes and enable the plugin endpoints (
skipEndpoints: false). - Read portal authentication exclusively from the
HttpOnlycookie. Login, OAuth and 2FA responses no longer expose the JWT in JSON. - Send cron and webhook secrets only through their configured headers. Query-string secrets are rejected.
- Custom rate-limit stores must implement the asynchronous
RateLimitStoreinterface.
The process-local memory store remains the default for development and single-instance use.
Use rateLimitStore: 'payload' or a shared custom store in production with multiple instances.
Run the AI agent on a ticket
await fetch('/api/support/ai-agent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ticketId, confidenceThreshold: 0.7 }),
})🔌 API Reference
supportPlugin(config)
The Payload plugin. Adds collections, admin views, editor components and API endpoints.
supportPlugin(config?: SupportPluginConfig): PluginExports
import { supportPlugin, generateTicketSynthesis, dispatchWebhook } from '@consilioweb/payload-support'
import type { SupportPluginConfig, SupportFeatures } from '@consilioweb/payload-support'Key HTTP endpoints (under /api/support)
| Method | Path | Purpose |
|---|---|---|
| POST | /ai | Sentiment, reply suggestion, rewrite, synthesis (admin). |
| POST | /ai-agent | Autonomous AI agent — answers from the KB or escalates. |
| POST | /login, /2fa, /oauth/google | Client portal auth (password, 2FA, OAuth). |
| GET | /admin-stats | Dashboard KPIs (real volume series, CSAT, NPS). |
| GET | /billing, /billing/invoice | Pre-billing + print-ready invoice. |
| POST | /process-snooze, /process-digests, /process-scheduled, /auto-close | Cron jobs (guarded by x-cron-secret). |
⚙️ Configuration
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| features | SupportFeatures | all true | Toggle each feature on/off. |
| ai | AIProviderConfig | anthropic | AI provider: anthropic | openai | ollama | custom. |
| email | EmailConfig | — | fromName, fromAddress, replyTo. |
| rateLimitStore | RateLimitStore \| 'payload' | memory | Persistent/shared storage for endpoint limits. |
| ticketNumber | { prefix?, padding? } | TK-, no padding | Atomic sequential ticket-number formatting. |
| capabilities | SupportCapabilities | — | Optional host adapters for SMS, inbound email, AI and deployment-specific workflows. |
| locale | 'fr' \| 'en' | 'fr' | Admin/portal language. |
| basePath | string | '/support' | Admin views prefix. |
| userCollectionSlug | string | 'users' | Agents collection. |
| allowedEmailDomains | string[] | — | Domains allowed for OAuth auto-registration. |
| collectionSlugs | object | — | Collection slug overrides. |
| skipCollections / skipViews / skipEndpoints | boolean | false | Skip injecting that part. |
Environment variables
| Env var | Required | Description |
|---------|----------|-------------|
| PAYLOAD_SECRET | ✅ | Payload secret (also signs 2FA & tracking). |
| NEXT_PUBLIC_SERVER_URL | ✅ | Public URL (email/portal links). |
| CRON_SECRET | for crons | x-cron-secret header for the cron endpoints. |
| ANTHROPIC_API_KEY / OLLAMA_API_URL | if AI | AI provider keys/URL. |
| GOOGLE_OAUTH_CLIENT_ID / _SECRET | if OAuth | Portal Google sign-in. |
| VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY | if push | Web Push keys (npx web-push generate-vapid-keys). |
| VAPID_SUBJECT | optional | Push contact (mailto: or URL), defaults to mailto:[email protected]. |
| SUPPORT_TEAM_SCOPING | optional | 1 to scope agents to their team's tickets. |
| SUPPORT_EMAIL / SUPPORT_REPLY_TO | optional | From/reply-to addresses. |
⚡ Performance
Engineered to stay fast under load (no fabricated benchmarks — figures depend on your volume and host):
| Concern | Approach |
|---------|----------|
| Settings reads | In-process cache (TTL + invalidation) — avoids ~8 redundant DB reads per ticket mutation. |
| List queries | SQLite indexes on filtered fields — inbox/dashboard/SLA in O(index) instead of full scans. |
| Email sends | Fire-and-forget — the response no longer waits on the SMTP round-trip (−200-500 ms). |
| Stats & billing | Paginated aggregation + bounded select — never loads all tickets in memory. |
| AI synthesis | Cached per ticket — no LLM recompute on every view. |
📚 Examples
Automation rule (no code)
Create a Automation Rules entry: event = ticket_created, condition category = bug, action set_priority = urgent. New "bug" tickets become "urgent" automatically.
Wire the cron jobs
# Hourly: auto-close, snooze wake-up, scheduled replies
curl -X POST https://your-app/api/support/auto-close -H "x-cron-secret: $CRON_SECRET"
curl -X POST https://your-app/api/support/process-snooze -H "x-cron-secret: $CRON_SECRET"
# Daily / weekly: notification digests
curl -X POST https://your-app/api/support/process-digests -H "x-cron-secret: $CRON_SECRET" -d '{"frequency":"daily"}'❓ FAQ
Built and tested on SQLite (@payloadcms/db-sqlite) with a sequential seed and busyTimeout. It works with any Payload adapter, but indexes and queries were validated on SQLite.
Node.js 18+, Payload 3.x, React 18 or 19.
Yes — strict TypeScript, with business types exported (SupportPluginConfig, SupportFeatures, …).
No. AI features are optional and you can run self-hosted Ollama to depend on no cloud (sovereignty/GDPR). Anthropic and OpenAI are also supported.
In standalone mode the schema isn't auto-migrated: generate/push the schema (new fields and collections) before deploying. See Troubleshooting.
🔧 Troubleshooting
useServerFunctions must be used within ServerFunctionsProvider
Use Payload 3.75+ for all @payloadcms/* packages (aligned versions).
SQLITE_BUSY / database is locked during seed
Make the seed sequential (no Promise.all on inserts) and add busyTimeout: 10000 to the SQLite adapter.
Admin views don't load
Regenerate the import map after adding components: pnpm payload generate:importmap. In a plugin/headless context, use skipViews: true if you don't mount the admin UI.
New fields/collections missing in production
Standalone doesn't run migrations: push the schema for googleId, twoFactorVerifiedAt, slaPausedAt, nps, mentions, and the notification-queue / automation-rules collections.
🔐 Security
Security is a first-class concern — several guardrails are validated by integration tests.
- Cross-client isolation — a client can never read another's tickets/messages (filtered by owned tickets).
- 2FA enforced server-side (
beforeLogin); OAuth verifies the Google email. - Sanitization of message HTML server-side (stored-XSS protection).
- HMAC-signed webhooks and tracking pixels with constant-time verification and idempotent writes.
- Cookie-only JWTs for portal authentication (
HttpOnly,Secure,SameSite=Lax). - Persistent rate limiting for authentication, 2FA, chats, invitations, transfers and AI endpoints.
- Bounded inbound email payloads and attachments; cron/webhook secrets are accepted in headers only.
Reporting Security Issues
Please email [email protected] instead of opening a public issue.
Best Practices
- ✅ Set a strong
PAYLOAD_SECRET(never a default value) - ✅ Set
CRON_SECRETto protect the cron endpoints - ✅ Restrict read access to the
mediacollection to the ticket owner - ✅ Put the app behind a trusted proxy (
x-forwarded-forheader) - ✅ Keep the plugin up-to-date
🤝 Contributing
Contributions are very welcome!
- Fork the repository
- Create a feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'feat: add AmazingFeature') - Push the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
Run the checks before submitting:
pnpm typecheck && pnpm test && pnpm build📝 Changelog
See CHANGELOG.md for the full history.
[2.0.1] — 2026-07-16
- 🌍 Loads the complete shared FR/EN catalogs in every support admin view, fixing raw keys such as ticket statuses, relative dates, time tracking, tags and billing labels.
- 🧭 Removes translation-key collisions in the support dashboard and settings views.
- ✅ Adds regression coverage for all 518 literal translation keys used by the support UI and keeps both locales aligned (132 tests passing).
[2.0.0] — 2026-07-16
- 🔐 Security hardening for authentication, tracking pixels, webhook/cron secrets, HTML/AI output and inbound-email limits.
- 🚦 Asynchronous persistent rate limiting with a Payload-backed store and memory fallback.
- 🔢 Atomic persistent ticket counters with configurable prefix and padding.
- 🧩 Typed deployment adapters for SMS, inbound email, AI workflows, billing, volunteering, cleanup and project suggestions.
- 🏗️ The plugin is now the single owner of generic support collections, hooks, views and endpoints.
- ✅ 124 tests covering the security regressions and new capabilities.
[1.1.1] — 2026-06-26
- 🐛 Fix client ticket reopen: allow clients to set
waiting_support(the status the portal's "Reopen" button sends). It was silently rejected, leaving the ticketresolved.
[1.1.0] — 2026-06-25
- 🎯 Per-team SLA policies & dashboards (team policy overrides the default,
?teamId=scoping). - 🔔 Native push / browser notifications (Web Push / VAPID, agent pushed on new client messages).
- 🎭 End-to-end UI test harness (browser-driven admin via Playwright,
pnpm test:e2e). - ✅ 109 integration tests (up from 102).
[1.0.0] — 2026-06-25
- ✨ Complete ticketing, SLA (+ pause-on-hold), automation & visual rules engine.
- 🤖 AI (sentiment, suggestion, synthesis, chatbot) + autonomous AI agent.
- 💬 Live chat, client portal (2FA, OAuth), knowledge base.
- 💶 Time tracking, pre-billing & print-ready invoice.
- 📊 CSAT + NPS, real volume series, digests, webhooks.
- 🔐 Security hardening (cross-client isolation, anti-XSS, server-side 2FA).
- ✅ Integration test harness (Payload + in-memory SQLite), 102 tests.
- 0.16.0 — Manual client reminder + auto-close after 24h
- 0.15.0 — Next 16 compatibility + accumulated features
- 0.9.0 → 0.9.13 — Enriched views (Client Intelligence, Billing), bundled RichTextEditor, code blocks in emails, inline message editing, enriched pre-billing, per-ticket cached AI synthesis, multi-style rewriting
- 0.6.0 → 0.6.4 — Split build (
bundle:false) for Next.js RSC compatibility + barrel exports - 0.5.0 — Full i18n across the 13 admin views
- 0.4.0 — Per-user preferences (locale, signature) vs global settings
- 0.3.0 — Feature parity with ConsilioWEB +
skipCollections/skipViews/skipEndpoints - 0.2.0 — 2026-04-08 — Security overhaul, SSE live chat, webhooks, SLA, scheduled replies, client portal
- 0.1.0 — 2026-04-08 — Initial scaffold:
supportPlugin(), 15 collections, feature flags, AI provider abstraction
🗺️ Roadmap
- [x] Ticketing, SLA, time tracking & billing
- [x] AI assists + autonomous AI agent
- [x] Live chat, client portal, knowledge base
- [x] Visual automation-rules engine
- [x] Native binary PDF invoice (alongside the print-ready HTML)
- [x] Full admin i18n (all views)
- [x] Social channels (WhatsApp, Messenger)
- [x] Multi-team / workspaces mode
- [x] Per-team SLA policies & dashboards
- [x] Native push / browser notifications
- [x] End-to-end UI test harness (browser-driven admin)
☕ Support
If this plugin saves you time, consider buying me a coffee!
📄 License
Licensed under the MIT License. See the LICENSE file for details.
