@consilioweb/payload-support 2.0.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +636 -402
- package/dist/components/RichTextEditor/index.d.ts +19 -0
- package/dist/components/TicketConversation/RewriteDropdown.d.ts +6 -0
- package/dist/components/TicketConversation/SkeletonText.d.ts +4 -0
- package/dist/components/TicketConversation/components/AISummaryPanel.d.ts +14 -0
- package/dist/components/TicketConversation/components/ActionPanels.d.ts +47 -0
- package/dist/components/TicketConversation/components/ActivityLog.d.ts +5 -0
- package/dist/components/TicketConversation/components/ClientBar.d.ts +5 -0
- package/dist/components/TicketConversation/components/ClientHistory.d.ts +24 -0
- package/dist/components/TicketConversation/components/CodeBlock.d.ts +26 -0
- package/dist/components/TicketConversation/components/CodeBlockInserter.d.ts +12 -0
- package/dist/components/TicketConversation/components/QuickActions.d.ts +32 -0
- package/dist/components/TicketConversation/components/TicketHeader.d.ts +19 -0
- package/dist/components/TicketConversation/components/TimeTrackingPanel.d.ts +24 -0
- package/dist/components/TicketConversation/config.d.ts +10 -0
- package/dist/components/TicketConversation/config.js +1 -40
- package/dist/components/TicketConversation/constants.d.ts +56 -0
- package/dist/components/TicketConversation/context.d.ts +53 -0
- package/dist/components/TicketConversation/hooks/useAI.d.ts +21 -0
- package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.d.ts +3 -0
- package/dist/components/TicketConversation/hooks/useFeatures.d.ts +23 -0
- package/dist/components/TicketConversation/hooks/useFeatures.js +61 -0
- package/dist/components/TicketConversation/hooks/useMessageActions.d.ts +20 -0
- package/dist/components/TicketConversation/hooks/useReply.d.ts +27 -0
- package/dist/components/TicketConversation/hooks/useTicketActions.d.ts +58 -0
- package/dist/components/TicketConversation/hooks/useTimeTracking.d.ts +18 -0
- package/dist/components/TicketConversation/hooks/useTranslation.d.ts +33 -0
- package/dist/components/TicketConversation/index.d.ts +4 -0
- package/dist/components/TicketConversation/index.js +2 -2
- package/dist/components/TicketConversation/types.d.ts +56 -0
- package/dist/components/TicketConversation/utils.d.ts +2 -0
- package/dist/index.cjs +392 -320
- package/dist/index.d.cts +100 -3
- package/dist/index.d.ts +100 -3
- package/dist/index.js +388 -321
- package/dist/utils/features.d.ts +123 -0
- package/dist/utils/features.js +147 -0
- package/dist/views/BillingView/client.d.ts +2 -0
- package/dist/views/BillingView/index.d.ts +4 -0
- package/dist/views/ChatView/client.d.ts +2 -0
- package/dist/views/ChatView/client.js +9 -9
- package/dist/views/ChatView/index.d.ts +4 -0
- package/dist/views/CrmView/client.d.ts +2 -0
- package/dist/views/CrmView/client.js +6 -9
- package/dist/views/CrmView/index.d.ts +4 -0
- package/dist/views/EmailTrackingView/client.d.ts +2 -0
- package/dist/views/EmailTrackingView/index.d.ts +4 -0
- package/dist/views/ImportConversationView/client.d.ts +2 -0
- package/dist/views/ImportConversationView/index.d.ts +4 -0
- package/dist/views/LogsView/client.d.ts +2 -0
- package/dist/views/LogsView/index.d.ts +4 -0
- package/dist/views/NewTicketView/client.d.ts +2 -0
- package/dist/views/NewTicketView/index.d.ts +4 -0
- package/dist/views/PendingEmailsView/client.d.ts +2 -0
- package/dist/views/PendingEmailsView/index.d.ts +4 -0
- package/dist/views/SupportDashboardView/client.d.ts +2 -0
- package/dist/views/SupportDashboardView/index.d.ts +4 -0
- package/dist/views/TicketDetailView/NextActionItem.d.ts +6 -0
- package/dist/views/TicketDetailView/RewriteDropdown.d.ts +7 -0
- package/dist/views/TicketDetailView/client.d.ts +2 -0
- package/dist/views/TicketDetailView/client.js +2 -2
- package/dist/views/TicketDetailView/constants.d.ts +13 -0
- package/dist/views/TicketDetailView/helpers.d.ts +2 -0
- package/dist/views/TicketDetailView/index.d.ts +4 -0
- package/dist/views/TicketDetailView/types.d.ts +40 -0
- package/dist/views/TicketInboxView/client.d.ts +2 -0
- package/dist/views/TicketInboxView/client.js +5 -5
- package/dist/views/TicketInboxView/index.d.ts +4 -0
- package/dist/views/TicketingSettingsView/client.d.ts +2 -0
- package/dist/views/TicketingSettingsView/client.js +5 -4
- package/dist/views/TicketingSettingsView/index.d.ts +4 -0
- package/dist/views/TimeDashboardView/client.d.ts +2 -0
- package/dist/views/TimeDashboardView/index.d.ts +4 -0
- package/dist/views/shared/AdminViewHeader.d.ts +13 -0
- package/dist/views/shared/ErrorBoundary.d.ts +17 -0
- package/dist/views/shared/Skeleton.d.ts +21 -0
- package/dist/views/shared/StatusPill.d.ts +8 -0
- package/dist/views/shared/adminTokens.d.ts +19 -0
- package/dist/views/shared/config.d.ts +8 -0
- package/dist/views/shared/config.js +1 -40
- package/dist/views/shared/dateLocale.d.ts +5 -0
- package/dist/views/shared/index.d.ts +12 -0
- package/dist/views/shared/index.js +2 -1
- package/dist/views/shared/sla.d.ts +14 -0
- package/dist/views.d.ts +13 -13
- package/package.json +16 -31
- package/src/collections/SupportClients.ts +29 -0
- package/src/collections/TicketCollaborators.ts +6 -1
- package/src/collections/TicketMessages.ts +46 -24
- package/src/collections/Tickets.ts +117 -79
- package/src/collections/TimeEntries.ts +57 -32
- package/src/components/TicketConversation/config.ts +16 -80
- package/src/components/TicketConversation/hooks/useFeatures.ts +109 -0
- package/src/components/TicketConversation/index.tsx +2 -2
- package/src/endpoints/ai.ts +2 -1
- package/src/endpoints/client-intelligence.ts +2 -1
- package/src/endpoints/process-scheduled.ts +13 -8
- package/src/endpoints/round-robin-config.ts +30 -20
- package/src/endpoints/settings.ts +28 -77
- package/src/index.ts +6 -2
- package/src/portal/LiveChat.tsx +33 -22
- package/src/portal/auth/tickets/detail/TicketPolling.tsx +5 -1
- package/src/types/lucide-react.d.ts +10 -2
- package/src/utils/aiAgent.ts +2 -1
- package/src/utils/aiProvider.ts +21 -0
- package/src/utils/features.ts +297 -0
- package/src/utils/fireWebhooks.ts +6 -0
- package/src/utils/generateTicketSynthesis.ts +2 -1
- package/src/utils/index.ts +4 -2
- package/src/utils/readSettings.ts +82 -9
- package/src/utils/webhookDispatcher.ts +19 -7
- package/src/views/ChatView/client.tsx +13 -9
- package/src/views/CrmView/client.tsx +10 -11
- package/src/views/TicketDetailView/client.tsx +2 -2
- package/src/views/TicketInboxView/client.tsx +10 -5
- package/src/views/TicketingSettingsView/client.tsx +8 -6
- package/src/views/shared/config.ts +14 -80
- package/src/views/shared/index.ts +3 -1
- package/dist/components/RichTextEditor/index.cjs +0 -279
- package/dist/components/TicketConversation/RewriteDropdown.cjs +0 -98
- package/dist/components/TicketConversation/SkeletonText.cjs +0 -21
- package/dist/components/TicketConversation/components/AISummaryPanel.cjs +0 -85
- package/dist/components/TicketConversation/components/ActionPanels.cjs +0 -193
- package/dist/components/TicketConversation/components/ActivityLog.cjs +0 -24
- package/dist/components/TicketConversation/components/ClientBar.cjs +0 -43
- package/dist/components/TicketConversation/components/ClientHistory.cjs +0 -138
- package/dist/components/TicketConversation/components/CodeBlock.cjs +0 -171
- package/dist/components/TicketConversation/components/CodeBlockInserter.cjs +0 -156
- package/dist/components/TicketConversation/components/QuickActions.cjs +0 -87
- package/dist/components/TicketConversation/components/TicketHeader.cjs +0 -93
- package/dist/components/TicketConversation/components/TimeTrackingPanel.cjs +0 -134
- package/dist/components/TicketConversation/config.cjs +0 -44
- package/dist/components/TicketConversation/constants.cjs +0 -127
- package/dist/components/TicketConversation/context.cjs +0 -13
- package/dist/components/TicketConversation/hooks/useAI.cjs +0 -196
- package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.cjs +0 -20
- package/dist/components/TicketConversation/hooks/useMessageActions.cjs +0 -136
- package/dist/components/TicketConversation/hooks/useReply.cjs +0 -188
- package/dist/components/TicketConversation/hooks/useTicketActions.cjs +0 -290
- package/dist/components/TicketConversation/hooks/useTimeTracking.cjs +0 -123
- package/dist/components/TicketConversation/hooks/useTranslation.cjs +0 -101
- package/dist/components/TicketConversation/index.cjs +0 -1114
- package/dist/components/TicketConversation/types.cjs +0 -2
- package/dist/components/TicketConversation/utils.cjs +0 -27
- package/dist/views/BillingView/client.cjs +0 -469
- package/dist/views/BillingView/index.cjs +0 -34
- package/dist/views/ChatView/client.cjs +0 -301
- package/dist/views/ChatView/index.cjs +0 -34
- package/dist/views/CrmView/client.cjs +0 -458
- package/dist/views/CrmView/index.cjs +0 -34
- package/dist/views/EmailTrackingView/client.cjs +0 -171
- package/dist/views/EmailTrackingView/index.cjs +0 -34
- package/dist/views/ImportConversationView/client.cjs +0 -238
- package/dist/views/ImportConversationView/index.cjs +0 -34
- package/dist/views/LogsView/client.cjs +0 -148
- package/dist/views/LogsView/index.cjs +0 -32
- package/dist/views/NewTicketView/client.cjs +0 -213
- package/dist/views/NewTicketView/index.cjs +0 -32
- package/dist/views/PendingEmailsView/client.cjs +0 -471
- package/dist/views/PendingEmailsView/index.cjs +0 -34
- package/dist/views/SupportDashboardView/client.cjs +0 -437
- package/dist/views/SupportDashboardView/index.cjs +0 -34
- package/dist/views/TicketDetailView/NextActionItem.cjs +0 -40
- package/dist/views/TicketDetailView/RewriteDropdown.cjs +0 -80
- package/dist/views/TicketDetailView/client.cjs +0 -1412
- package/dist/views/TicketDetailView/constants.cjs +0 -20
- package/dist/views/TicketDetailView/helpers.cjs +0 -23
- package/dist/views/TicketDetailView/index.cjs +0 -34
- package/dist/views/TicketDetailView/types.cjs +0 -2
- package/dist/views/TicketInboxView/client.cjs +0 -359
- package/dist/views/TicketInboxView/index.cjs +0 -32
- package/dist/views/TicketingSettingsView/client.cjs +0 -740
- package/dist/views/TicketingSettingsView/index.cjs +0 -34
- package/dist/views/TimeDashboardView/client.cjs +0 -180
- package/dist/views/TimeDashboardView/index.cjs +0 -34
- package/dist/views/shared/AdminViewHeader.cjs +0 -68
- package/dist/views/shared/ErrorBoundary.cjs +0 -55
- package/dist/views/shared/Skeleton.cjs +0 -77
- package/dist/views/shared/StatusPill.cjs +0 -47
- package/dist/views/shared/adminTokens.cjs +0 -34
- package/dist/views/shared/config.cjs +0 -44
- package/dist/views/shared/dateLocale.cjs +0 -5
- package/dist/views/shared/index.cjs +0 -72
- package/dist/views/shared/sla.cjs +0 -37
- package/dist/views.cjs +0 -86
- package/dist/views.d.cts +0 -13
- package/src/__tests__/aiSummaryRendering.test.ts +0 -21
- package/src/__tests__/authResponses.test.ts +0 -69
- package/src/__tests__/capabilities.test.ts +0 -71
- package/src/__tests__/generateTrackingToken.test.ts +0 -67
- package/src/__tests__/integration/ai-agent.test.ts +0 -46
- package/src/__tests__/integration/auth.test.ts +0 -43
- package/src/__tests__/integration/automation-rules.test.ts +0 -68
- package/src/__tests__/integration/buildTestPayload.ts +0 -52
- package/src/__tests__/integration/channels.test.ts +0 -52
- package/src/__tests__/integration/core-behaviors.test.ts +0 -81
- package/src/__tests__/integration/dashboard-volume.test.ts +0 -31
- package/src/__tests__/integration/digest.test.ts +0 -48
- package/src/__tests__/integration/invoice-pdf.test.ts +0 -30
- package/src/__tests__/integration/isolation.test.ts +0 -81
- package/src/__tests__/integration/mentions-invoice.test.ts +0 -50
- package/src/__tests__/integration/nps.test.ts +0 -37
- package/src/__tests__/integration/per-team-sla.test.ts +0 -44
- package/src/__tests__/integration/push.test.ts +0 -75
- package/src/__tests__/integration/sanitization.test.ts +0 -35
- package/src/__tests__/integration/sla-pause.test.ts +0 -39
- package/src/__tests__/integration/smoke.test.ts +0 -26
- package/src/__tests__/integration/snooze.test.ts +0 -68
- package/src/__tests__/integration/teams.test.ts +0 -38
- package/src/__tests__/rateLimiter.test.ts +0 -83
- package/src/__tests__/sanitizeHtml.test.ts +0 -165
- package/src/__tests__/sla.test.ts +0 -258
- package/src/__tests__/trackOpenEndpoint.test.ts +0 -54
- package/src/__tests__/translations.test.ts +0 -67
- package/src/__tests__/webhookSecurity.test.ts +0 -58
package/README.md
CHANGED
|
@@ -1,98 +1,115 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
# @consilioweb/payload-support
|
|
2
|
+
|
|
3
|
+
> A self-hosted support and ticketing system for Payload CMS 3 + Next.js: tickets, SLA, live chat, a client portal, time tracking and invoicing, AI assists and an automation-rules engine.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@consilioweb/payload-support)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://payloadcms.com)
|
|
8
|
+
|
|
9
|
+
## About
|
|
10
|
+
|
|
11
|
+
`supportPlugin()` turns a Payload 3 admin into a helpdesk: it injects 14 to 25 collections
|
|
12
|
+
(depending on the feature flags), 13 admin views and around 60 REST endpoints covering the whole
|
|
13
|
+
ticket lifecycle — intake, assignment, SLA, replies by email or live chat, resolution, CSAT and
|
|
14
|
+
billing. It is aimed at agencies and product teams who already run Payload and do not want to pay
|
|
15
|
+
for, or export their customers' data to, a third-party support SaaS.
|
|
16
|
+
|
|
17
|
+
Two things it deliberately does *not* do: it does not mount the client portal for you (the portal
|
|
18
|
+
ships as a template you copy into your own `app/` directory — see
|
|
19
|
+
[Mounting the client portal](#mounting-the-client-portal)), and it does not bundle an LLM SDK
|
|
20
|
+
(install `@anthropic-ai/sdk` yourself if you enable the AI features).
|
|
21
|
+
|
|
22
|
+
## Table of Contents
|
|
23
|
+
|
|
24
|
+
- [Features](#features)
|
|
25
|
+
- [Installation](#installation)
|
|
26
|
+
- [Quick Start](#quick-start)
|
|
27
|
+
- [Mounting the client portal](#mounting-the-client-portal)
|
|
28
|
+
- [Configuration](#configuration)
|
|
29
|
+
- [Deployment adapters](#deployment-adapters)
|
|
30
|
+
- [API Endpoints](#api-endpoints)
|
|
31
|
+
- [Webhooks](#webhooks)
|
|
32
|
+
- [Collections](#collections)
|
|
33
|
+
- [Package Exports](#package-exports)
|
|
34
|
+
- [Requirements](#requirements)
|
|
35
|
+
- [Security](#security)
|
|
36
|
+
- [Performance](#performance)
|
|
37
|
+
- [Troubleshooting](#troubleshooting)
|
|
38
|
+
- [FAQ](#faq)
|
|
39
|
+
- [Upgrading](#upgrading)
|
|
40
|
+
- [Contributing](#contributing)
|
|
41
|
+
- [Changelog](#changelog)
|
|
42
|
+
- [Support](#support)
|
|
43
|
+
- [License](#license)
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
- **Ticketing** — statuses, priorities, categories, tags, merge, split, snooze, scheduled replies,
|
|
48
|
+
internal notes, collaborators, and an append-only activity log (`create` and `update` are denied
|
|
49
|
+
to everyone; only hooks write to it).
|
|
50
|
+
- **Agent inbox** — a keyboard-driven inbox view, bulk actions, macros, canned responses, and typing
|
|
51
|
+
and presence indicators.
|
|
52
|
+
- **SLA** — per-team or default policies, business-hours deadlines, pause-on-hold, escalation, and a
|
|
53
|
+
`sla-check` endpoint that lists what is breaching.
|
|
54
|
+
- **Automation** — an `automation-rules` collection (event → conditions → actions) and optional
|
|
55
|
+
round-robin assignment.
|
|
56
|
+
- **AI assists** — sentiment, reply suggestion, multi-style rewrite, per-ticket cached synthesis, a
|
|
57
|
+
knowledge-base chatbot, a client-intelligence summary, and an autonomous agent that either answers
|
|
58
|
+
from the knowledge base or escalates. Anthropic, or `provider: 'ollama'` to reach a self-hosted
|
|
59
|
+
gateway through the same SDK pointed at your own `OLLAMA_API_URL`.
|
|
60
|
+
- **Omnichannel** — inbound and outbound email with open tracking, live chat (SSE in the portal
|
|
61
|
+
widget, polling in the shipped agent console; the portal widget stays hidden until
|
|
62
|
+
`NEXT_PUBLIC_ENABLE_CHAT_WIDGET=true`), and a normalized inbound webhook for WhatsApp /
|
|
63
|
+
Messenger.
|
|
64
|
+
- **Client portal** — password login, email 2FA, Google OAuth, ticket list and detail, FAQ, profile,
|
|
65
|
+
GDPR data export and account deletion. Shipped as a template you copy (see below).
|
|
66
|
+
- **Time tracking and billing** — timer and manual entries, a per-ticket rollup, a time dashboard,
|
|
67
|
+
per-project pre-billing, and an invoice rendered as HTML or PDF.
|
|
68
|
+
- **Reporting** — dashboard KPIs, CSAT and NPS, email statistics, CSV export.
|
|
69
|
+
- **Notifications** — admin notifications, Web Push (VAPID), and daily/weekly digest emails.
|
|
70
|
+
- **Webhooks** — HMAC-SHA256 signed outbound deliveries on ticket events.
|
|
71
|
+
- **i18n** — French and English catalogs across the admin views, selectable per user.
|
|
72
|
+
|
|
73
|
+
## Installation
|
|
5
74
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
[](https://payloadcms.com)
|
|
12
|
-
[](src/__tests__)
|
|
13
|
-
[](https://www.typescriptlang.org)
|
|
14
|
-
|
|
15
|
-
</div>
|
|
16
|
-
|
|
17
|
-
<img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" width="100%" alt="" />
|
|
18
|
-
|
|
19
|
-
## 📑 Table of Contents
|
|
20
|
-
|
|
21
|
-
- [Features](#-features)
|
|
22
|
-
- [Quick Start](#-quick-start)
|
|
23
|
-
- [Installation](#-installation)
|
|
24
|
-
- [Usage](#-usage)
|
|
25
|
-
- [API Reference](#-api-reference)
|
|
26
|
-
- [Configuration](#-configuration)
|
|
27
|
-
- [Performance](#-performance)
|
|
28
|
-
- [Examples](#-examples)
|
|
29
|
-
- [FAQ](#-faq)
|
|
30
|
-
- [Troubleshooting](#-troubleshooting)
|
|
31
|
-
- [Security](#-security)
|
|
32
|
-
- [Contributing](#-contributing)
|
|
33
|
-
- [Changelog](#-changelog)
|
|
34
|
-
- [Roadmap](#-roadmap)
|
|
35
|
-
- [Support](#-support)
|
|
36
|
-
- [License](#-license)
|
|
37
|
-
|
|
38
|
-
<img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" width="100%" alt="" />
|
|
39
|
-
|
|
40
|
-
## ✨ Features
|
|
41
|
-
|
|
42
|
-
<table>
|
|
43
|
-
<tr>
|
|
44
|
-
<td width="50%">
|
|
45
|
-
|
|
46
|
-
**🎫 Complete ticketing**
|
|
47
|
-
|
|
48
|
-
Statuses, priorities, categories, tags, merge/split, snooze, scheduled replies, internal notes, an immutable activity log and a keyboard-driven Superhuman-style inbox.
|
|
49
|
-
|
|
50
|
-
</td>
|
|
51
|
-
<td width="50%">
|
|
52
|
-
|
|
53
|
-
**⏱️ SLA & automation**
|
|
54
|
-
|
|
55
|
-
SLA policies (business hours + pause-on-hold), escalation, macros, round-robin, and a **visual automation-rules engine** (conditions → actions).
|
|
56
|
-
|
|
57
|
-
</td>
|
|
58
|
-
</tr>
|
|
59
|
-
<tr>
|
|
60
|
-
<td width="50%">
|
|
61
|
-
|
|
62
|
-
**🤖 Built-in AI**
|
|
63
|
-
|
|
64
|
-
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.
|
|
65
|
-
|
|
66
|
-
</td>
|
|
67
|
-
<td width="50%">
|
|
68
|
-
|
|
69
|
-
**💬 Omnichannel & portal**
|
|
70
|
-
|
|
71
|
-
Inbound/outbound email, live chat (SSE), widget, a full **client portal** (auth, 2FA, Google OAuth), knowledge base and deflection.
|
|
72
|
-
|
|
73
|
-
</td>
|
|
74
|
-
</tr>
|
|
75
|
-
<tr>
|
|
76
|
-
<td width="50%">
|
|
77
|
-
|
|
78
|
-
**💶 Time tracking & billing**
|
|
75
|
+
```bash
|
|
76
|
+
pnpm add @consilioweb/payload-support
|
|
77
|
+
# or: npm install @consilioweb/payload-support
|
|
78
|
+
# or: yarn add @consilioweb/payload-support
|
|
79
|
+
```
|
|
79
80
|
|
|
80
|
-
|
|
81
|
+
**Peer dependencies** — all six are required, none is optional:
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
```bash
|
|
84
|
+
pnpm add payload@^3.37.0 @payloadcms/next@^3.37.0 next@^15.2.9 react@^19 react-dom@^19 lucide-react@">=0.300.0"
|
|
85
|
+
```
|
|
84
86
|
|
|
85
|
-
|
|
87
|
+
- `payload@^3.37.0` — the Google OAuth endpoint imports `jwtSign` from the `payload` barrel, which
|
|
88
|
+
only exists from 3.37.0 on. Below that, the missing named export fails the whole
|
|
89
|
+
`payload.config.ts`, not just that endpoint.
|
|
90
|
+
- `@payloadcms/next` and `next` — the 13 admin views are registered unless you pass
|
|
91
|
+
`skipViews: true`, and each imports `DefaultTemplate` from `@payloadcms/next/templates` plus
|
|
92
|
+
`next/navigation` / `next/link` statically. Both are already present in any Payload 3 admin app.
|
|
93
|
+
- `lucide-react` — imported statically by `PendingEmailsView` and `TicketingSettingsView`. (The
|
|
94
|
+
third importer, `src/portal/LiveChat.tsx`, is a leftover: nothing in the shipped portal mounts
|
|
95
|
+
it.)
|
|
96
|
+
|
|
97
|
+
**AI runtime dependency** — the AI features load `@anthropic-ai/sdk` through a runtime
|
|
98
|
+
`require`/dynamic `import` and it is marked `external` at build time. It is intentionally not a
|
|
99
|
+
dependency of this package, so install it yourself if you use `features.ai`, the chatbot, the
|
|
100
|
+
autonomous agent, ticket synthesis, client intelligence or the AI conversation import:
|
|
86
101
|
|
|
87
|
-
|
|
102
|
+
```bash
|
|
103
|
+
pnpm add @anthropic-ai/sdk
|
|
104
|
+
```
|
|
88
105
|
|
|
89
|
-
|
|
90
|
-
</tr>
|
|
91
|
-
</table>
|
|
106
|
+
After adding admin components, regenerate the import map:
|
|
92
107
|
|
|
93
|
-
|
|
108
|
+
```bash
|
|
109
|
+
pnpm payload generate:importmap
|
|
110
|
+
```
|
|
94
111
|
|
|
95
|
-
##
|
|
112
|
+
## Quick Start
|
|
96
113
|
|
|
97
114
|
```ts
|
|
98
115
|
// payload.config.ts
|
|
@@ -103,8 +120,7 @@ export default buildConfig({
|
|
|
103
120
|
plugins: [
|
|
104
121
|
supportPlugin({
|
|
105
122
|
features: { ai: true, sla: true, timeTracking: true, chat: true },
|
|
106
|
-
|
|
107
|
-
locale: 'fr',
|
|
123
|
+
ticketNumber: { prefix: 'TK-', padding: 6 },
|
|
108
124
|
}),
|
|
109
125
|
],
|
|
110
126
|
})
|
|
@@ -115,61 +131,234 @@ pnpm payload generate:importmap
|
|
|
115
131
|
pnpm dev
|
|
116
132
|
```
|
|
117
133
|
|
|
118
|
-
Open `/admin/support/inbox` for the agent inbox
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
134
|
+
Open `/admin/support/inbox` for the agent inbox and `/admin/support/settings` to pick the AI
|
|
135
|
+
provider, the sender addresses, the SLA targets and the runtime feature flags.
|
|
136
|
+
|
|
137
|
+
> **The client portal is not a route the plugin mounts.** `/support` returns 404 until you copy the
|
|
138
|
+
> portal template into your own `app/` directory — see
|
|
139
|
+
> [Mounting the client portal](#mounting-the-client-portal). Everything the portal talks to (the REST
|
|
140
|
+
> endpoints, the auth/2FA/OAuth endpoints, the collections) *is* installed by the plugin; only the
|
|
141
|
+
> Next.js pages have to live in your app.
|
|
142
|
+
|
|
143
|
+
## Mounting the client portal
|
|
144
|
+
|
|
145
|
+
The portal ships as a **template you copy**, not as an importable subpath — and that is a
|
|
146
|
+
constraint, not an oversight: `src/portal/auth/layout.tsx` and three of its pages import
|
|
147
|
+
`@payload-config`, an alias that only exists inside your application, and `src/portal/layout.tsx`
|
|
148
|
+
renders its own `<html>`/`<body>` as a Next.js **root layout**. Neither can be resolved from
|
|
149
|
+
`node_modules`, so there is no `./portal` export and `plugin.ts` registers no Next route.
|
|
150
|
+
|
|
151
|
+
The sources are published in the tarball (`files` includes `src`), so copy them out of
|
|
152
|
+
`node_modules` into a route group of your own `app/` directory. Two directories have to be
|
|
153
|
+
renamed on the way in — the shipped names are plain folders, not Next.js route syntax — and
|
|
154
|
+
`layout.tsx` has to sit at the **group** level, because it renders its own `<html>`/`<body>`
|
|
155
|
+
and only one root layout per group may do that (the same convention `create-payload-app`
|
|
156
|
+
uses for its `(payload)` / `(frontend)` groups):
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
src/app/
|
|
160
|
+
└── (support)/
|
|
161
|
+
├── layout.tsx ← src/portal/layout.tsx (root layout of the group)
|
|
162
|
+
└── support/
|
|
163
|
+
├── page.tsx ← src/portal/page.tsx → /support
|
|
164
|
+
├── login/ register/ forgot-password/ reset-password/
|
|
165
|
+
└── (auth)/ ← src/portal/auth/ (route group: no URL segment)
|
|
166
|
+
├── layout.tsx (auth guard + header + chatbot and chat widgets)
|
|
167
|
+
├── dashboard/ faq/ profile/
|
|
168
|
+
└── tickets/
|
|
169
|
+
├── new/
|
|
170
|
+
└── [id]/ ← src/portal/auth/tickets/detail/ (the page reads `params.id`)
|
|
127
171
|
```
|
|
128
172
|
|
|
129
|
-
### yarn
|
|
130
173
|
```bash
|
|
131
|
-
|
|
174
|
+
mkdir -p 'src/app/(support)/support'
|
|
175
|
+
cp -R node_modules/@consilioweb/payload-support/src/portal/. 'src/app/(support)/support/'
|
|
176
|
+
cd 'src/app/(support)'
|
|
177
|
+
mv support/layout.tsx layout.tsx
|
|
178
|
+
mv support/auth 'support/(auth)'
|
|
179
|
+
mv 'support/(auth)/tickets/detail' 'support/(auth)/tickets/[id]'
|
|
132
180
|
```
|
|
133
181
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
182
|
+
That yields `/support`, `/support/login`, `/support/register`, `/support/forgot-password`,
|
|
183
|
+
`/support/reset-password` and, behind the auth layout, `/support/dashboard`, `/support/faq`,
|
|
184
|
+
`/support/profile`, `/support/tickets/new` and `/support/tickets/<id>` — exactly the paths the
|
|
185
|
+
plugin's notification emails and the portal's own links point to, so the renames are
|
|
186
|
+
mandatory, not cosmetic.
|
|
138
187
|
|
|
139
|
-
|
|
188
|
+
What your app must provide for those pages to work:
|
|
140
189
|
|
|
141
|
-
|
|
190
|
+
| Requirement | Why |
|
|
191
|
+
|---|---|
|
|
192
|
+
| The `@payload-config` alias | The authenticated pages call `getPayload({ config })` directly. Standard in a `create-payload-app` project. |
|
|
193
|
+
| **Tailwind CSS** | 28 of the 35 portal files are styled with Tailwind utility classes only. Without Tailwind the portal renders unstyled. |
|
|
194
|
+
| `@payloadcms/richtext-lexical` | `auth/faq/page.tsx` renders knowledge-base entries with `RichText` from `@payloadcms/richtext-lexical/react`. It is **not** a peer dependency of this plugin — add it yourself if you mount the FAQ page. |
|
|
195
|
+
| `NEXT_PUBLIC_SERVER_URL` | Email and portal links are built from it. |
|
|
196
|
+
| `NEXT_PUBLIC_SUPPORT_PHONE` | The ticket detail page prints a support phone number; without this variable it prints the placeholder `01 23 45 67 89` to your clients. |
|
|
142
197
|
|
|
143
|
-
|
|
198
|
+
`lucide-react` is **not** needed by the pages above: the only portal file importing it is
|
|
199
|
+
`src/portal/LiveChat.tsx`, which the shipped `(auth)` layout never mounts. It stays a required peer
|
|
200
|
+
for the admin views.
|
|
144
201
|
|
|
145
|
-
|
|
202
|
+
The `(auth)` layout mounts two widgets. `ChatbotWidget` is always rendered; `ChatWidget` — the
|
|
203
|
+
floating live-chat FAB — returns `null` unless the host app sets
|
|
204
|
+
`NEXT_PUBLIC_ENABLE_CHAT_WIDGET=true`, whatever `features.chat` says.
|
|
146
205
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
```
|
|
206
|
+
Because it is a copy, the portal is yours to restyle and re-brand; the trade-off is that
|
|
207
|
+
plugin upgrades do not update it. Diff it against
|
|
208
|
+
`node_modules/@consilioweb/payload-support/src/portal` after a version bump.
|
|
151
209
|
|
|
152
|
-
|
|
210
|
+
## Configuration
|
|
153
211
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
212
|
+
### Plugin options
|
|
213
|
+
|
|
214
|
+
| Option | Type | Default | Description |
|
|
215
|
+
|---|---|---|---|
|
|
216
|
+
| `features` | `SupportFeatures` | see below | Build-time feature flags — decide which collections, endpoints and views exist. |
|
|
217
|
+
| `rateLimitStore` | `RateLimitStore \| 'payload'` | in-memory | Shared storage for the endpoint rate limits. `'payload'` adds the `support-rate-limits` collection. |
|
|
218
|
+
| `ticketNumber` | `{ prefix?, padding? }` | `{ prefix: 'TK-', padding: 4 }` | Sequential ticket-number formatting, backed by an atomic counter. |
|
|
219
|
+
| `capabilities` | `SupportCapabilities` | — | Host adapters for SMS, inbound email, digests, AI titles/summaries, detailed billing, volunteering, thread cleanup and project suggestions. See [Deployment adapters](#deployment-adapters). |
|
|
220
|
+
| `basePath` | `string` | `'/support'` | Prefix of the admin view routes (`/admin<basePath>/inbox`, …). The conversation-import view is the exception: it is registered at the fixed `/admin/import-conversation`. |
|
|
221
|
+
| `userCollectionSlug` | `string` | `'users'` | The staff/agent auth collection. |
|
|
222
|
+
| `allowedEmailDomains` | `string[]` | — | Restricts Google OAuth **auto-registration** to these domains. Existing accounts are unaffected. |
|
|
223
|
+
| `collectionSlugs` | `object` | see [Collections](#collections) | Per-collection slug overrides. |
|
|
224
|
+
| `notificationSlug` | `string` | `'admin-notifications'` | Collection the plugin writes admin notifications to. |
|
|
225
|
+
| `conversationComponent` | `string` | `'@consilioweb/payload-support/components/TicketConversation'` | Import path of the conversation UI field rendered on a ticket. |
|
|
226
|
+
| `projectCollectionSlug` | `string` | — | When set, adds a `project` relationship to tickets (and enables per-project billing). |
|
|
227
|
+
| `documentsCollectionSlug` | `string` | — | When set, adds quote/invoice upload fields to tickets. |
|
|
228
|
+
| `skipCollections` | `boolean` | `false` | Do not inject the collections. |
|
|
229
|
+
| `skipViews` | `boolean` | `false` | Do not inject the admin views. |
|
|
230
|
+
| `skipEndpoints` | `boolean` | `false` | Do not inject the REST endpoints. |
|
|
231
|
+
|
|
232
|
+
### Options accepted but not read
|
|
233
|
+
|
|
234
|
+
Four keys are part of `SupportPluginConfig` but no code path reads them. They are listed here so you
|
|
235
|
+
do not configure them and wonder why nothing changes:
|
|
236
|
+
|
|
237
|
+
| Option | What actually decides | Where to set it |
|
|
238
|
+
|---|---|---|
|
|
239
|
+
| `ai` (`AIProviderConfig`) | `settings.ai.provider` / `settings.ai.model` | Admin → Support → Settings, or `POST /api/support/settings`. Defaults to `anthropic` / `claude-haiku-4-5-20251001`. |
|
|
240
|
+
| `email` (`EmailConfig`) | `settings.email.*`, falling back to `SUPPORT_EMAIL` / `SUPPORT_REPLY_TO` | Same settings view, or the environment. |
|
|
241
|
+
| `locale` | The per-user preference | `GET`/`POST /api/support/user-prefs` (defaults to `fr`). |
|
|
242
|
+
| `navGroup` | — | Nowhere: each collection hard-codes its own `admin.group`. 20 sit under `Support`, three under `Gestion` (`canned-responses`, `ticket-activity-log`, `time-entries`), and `support-counters` / `support-rate-limits` declare none — they are `hidden` anyway. |
|
|
243
|
+
|
|
244
|
+
### Build-time feature flags
|
|
245
|
+
|
|
246
|
+
`supportPlugin({ features })` accepts `SupportFeatures`. Every flag defaults to `true` except
|
|
247
|
+
`roundRobin` and `customStatuses`; `autoCloseDays` defaults to `7`.
|
|
248
|
+
|
|
249
|
+
| Flag | Default | Turning it off removes |
|
|
250
|
+
|---|---|---|
|
|
251
|
+
| `ai` | `true` | `client-summaries`, `/support/ai`, `/support/ai-agent`, `/support/client-intelligence`, `/support/ticket-synthesis` |
|
|
252
|
+
| `timeTracking` | `true` | `time-entries`, `/support/billing`, `/support/billing/invoice`, the Time dashboard view |
|
|
253
|
+
| `chat` | `true` | `chat-messages`, the four chat endpoints, the Chat view |
|
|
254
|
+
| `emailTracking` | `true` | `email-logs`, `/support/email-stats`, `/support/track-open`, the Tracking view |
|
|
255
|
+
| `pendingEmails` | `true` | `pending-emails`, `/support/pending-emails/:id/process`, the Pending emails view |
|
|
256
|
+
| `sla` | `true` | `sla-policies`, `/support/sla-check` |
|
|
257
|
+
| `webhooks` | `true` | `webhook-endpoints` (and therefore every outbound delivery) |
|
|
258
|
+
| `macros` | `true` | `macros`, `/support/apply-macro` |
|
|
259
|
+
| `authLogs` | `true` | `auth-logs` |
|
|
260
|
+
| `customStatuses` | `false` | *(off by default)* `ticket-statuses`, `/support/statuses` |
|
|
261
|
+
| `autoClose` | `true` | `/support/auto-close`, `/support/send-reminder` |
|
|
262
|
+
| `autoCloseDays` | `7` | — (projection of `settings.autoClose.daysBeforeClose`) |
|
|
263
|
+
| `snooze` | `true` | `/support/process-snooze` |
|
|
264
|
+
| `scheduledReplies` | `true` | `/support/process-scheduled` |
|
|
265
|
+
| `satisfaction` | `true` | `/support/satisfaction` |
|
|
266
|
+
| `merge` | `true` | `/support/merge-tickets` |
|
|
267
|
+
| `splitTicket` | `true` | `/support/split-ticket` |
|
|
268
|
+
| `bulkActions` | `true` | `/support/bulk-action` |
|
|
269
|
+
| `collisionDetection` | `true` | `/support/typing`, `/support/presence` |
|
|
270
|
+
| `signatures` | `true` | `/support/signature` |
|
|
271
|
+
| `chatbot` | `true` | `/support/chatbot` |
|
|
272
|
+
| `roundRobin` | `false` | *(off by default)* `/support/round-robin-config` |
|
|
273
|
+
| `canned`, `externalMessages`, `clientHistory`, `activityLog` | `true` | Nothing at build time — they exist as runtime flags (below) and only hide UI sections. |
|
|
274
|
+
| `commandPalette`, `knowledgeBase` | `true` | Nothing — no code reads either flag, at build time or at runtime. |
|
|
275
|
+
|
|
276
|
+
### Build-time vs runtime feature flags
|
|
277
|
+
|
|
278
|
+
Two distinct things share the word "features", and they act at different moments:
|
|
279
|
+
|
|
280
|
+
| | `supportPlugin({ features })` | Runtime flags (Settings view) |
|
|
281
|
+
|---|---|---|
|
|
282
|
+
| Type | `SupportFeatures` (28 keys) | `TicketingFeatures` (16 keys) |
|
|
283
|
+
| Decides | which collections and endpoints **exist** | which UI sections are **shown**, plus round-robin and auto-close |
|
|
284
|
+
| Changed by | editing `payload.config.ts` + redeploy | an admin, in the ticketing settings view |
|
|
285
|
+
| Stored in | code | the `features` block of the `support-settings` preference row |
|
|
286
|
+
|
|
287
|
+
Turning a feature off at build time removes its collection and endpoints, so the matching runtime
|
|
288
|
+
flag has nothing left to show. Runtime flags only ever narrow what a build-time-enabled feature
|
|
289
|
+
exposes.
|
|
290
|
+
|
|
291
|
+
Runtime flags are read through `GET /api/support/settings` and written through
|
|
292
|
+
`POST /api/support/settings` (admin-only). They apply to every browser and to server-side code — the
|
|
293
|
+
auto-assign hook and the auto-close cron read the same values. `localStorage` is still written, but
|
|
294
|
+
only as a cache so an admin screen stays usable when the settings call fails; it is never
|
|
295
|
+
authoritative.
|
|
296
|
+
|
|
297
|
+
Two flags are projections, deliberately not stored twice: `features.autoClose` mirrors
|
|
298
|
+
`settings.autoClose.enabled`, and `features.autoCloseDays` mirrors
|
|
299
|
+
`settings.autoClose.daysBeforeClose`.
|
|
300
|
+
|
|
301
|
+
> **Upgrading from ≤ 2.0.1** — flags used to live in each browser's `localStorage`
|
|
302
|
+
> (`ticketing_features`). The first admin view loaded after the upgrade pushes that browser's values
|
|
303
|
+
> to the server, once, if the server has none yet; from then on the server wins. Nothing to run by
|
|
304
|
+
> hand, nothing is lost. If several machines disagree, the first one to load seeds the server and the
|
|
305
|
+
> others adopt its copy.
|
|
306
|
+
|
|
307
|
+
### Environment variables
|
|
308
|
+
|
|
309
|
+
| Variable | Required | Description |
|
|
310
|
+
|---|---|---|
|
|
311
|
+
| `PAYLOAD_SECRET` | yes | Payload secret. Also signs the 2FA tokens and the email tracking pixels; the 2FA endpoint refuses to operate without it. |
|
|
312
|
+
| `NEXT_PUBLIC_SERVER_URL` | yes | Public URL used to build every email and portal link. |
|
|
313
|
+
| `CRON_SECRET` | for the cron endpoints | Expected value of the `x-cron-secret` header. |
|
|
314
|
+
| `ANTHROPIC_API_KEY` | with the `anthropic` provider | Anthropic API key. |
|
|
315
|
+
| `OLLAMA_API_URL` | with the `ollama` provider | Base URL of your own AI gateway, used as the SDK `baseURL`. **Mandatory** — there is no default host, and the AI paths throw `500 "provider 'ollama' requires OLLAMA_API_URL"` rather than sending ticket content anywhere you did not choose. |
|
|
316
|
+
| `GOOGLE_OAUTH_CLIENT_ID` / `GOOGLE_OAUTH_CLIENT_SECRET` | for portal OAuth | Google sign-in on the portal. |
|
|
317
|
+
| `VAPID_PUBLIC_KEY` / `VAPID_PRIVATE_KEY` | for Web Push | Generate with `npx web-push generate-vapid-keys`. |
|
|
318
|
+
| `VAPID_SUBJECT` | optional | Push contact (`mailto:` or URL). Defaults to `mailto:support@example.com`. |
|
|
319
|
+
| `SUPPORT_EMAIL` / `SUPPORT_REPLY_TO` | optional | Fallback sender / reply-to addresses when the settings row leaves them empty. |
|
|
320
|
+
| `SUPPORT_CLIENT_BCC` | optional | Archive mailbox blind-copied on client-facing emails. Use a dedicated address, not `support@`, or the inbound anti-loop guard will re-create tickets from the copies. |
|
|
321
|
+
| `SUPPORT_TEAM_SCOPING` | optional | `1` or `true` to scope agents to their own team's tickets. |
|
|
322
|
+
| `SUPPORT_WEBHOOK_SECRET` | for machine imports | Expected value of the `x-webhook-secret` header on `POST /api/support/import-conversation`. |
|
|
323
|
+
| `CHANNELS_WEBHOOK_SECRET` | for social channels | Expected value of the `x-channel-secret` header on `POST /api/support/channels/webhook`. |
|
|
324
|
+
| `WHATSAPP_TOKEN` / `WHATSAPP_PHONE_ID` | for WhatsApp replies | Outbound WhatsApp credentials. |
|
|
325
|
+
| `MESSENGER_PAGE_TOKEN` | for Messenger replies | Outbound Messenger page token. |
|
|
326
|
+
| `CONTACT_EMAIL` | optional | Address treated as "the admin" when importing an existing email conversation. |
|
|
327
|
+
| `NEXT_PUBLIC_ENABLE_CHAT_WIDGET` | to show the portal chat widget | `'true'` mounts the floating live-chat widget in the portal `(auth)` layout. Anything else — including leaving it unset — makes `ChatWidget` return `null`, so `features.chat: true` on its own displays nothing to clients. |
|
|
328
|
+
| `NEXT_PUBLIC_SUPPORT_PHONE` | for the portal ticket page | Support phone number shown on the portal ticket detail page. Set it: the fallback is the placeholder `01 23 45 67 89`. |
|
|
329
|
+
| `NEXT_PUBLIC_SUPPORT_SEND_ALIASES` | optional | Comma-separated "send as" identities offered in the agent composer. Empty by default, and then the composer offers only the agent themselves. |
|
|
330
|
+
| `NEXT_PUBLIC_SMTP_HOST` / `NEXT_PUBLIC_SMTP_PORT` | optional | Shown read-only in the Settings view. Informational only — mail is sent through `payload.sendEmail`, so these change nothing about delivery. |
|
|
331
|
+
|
|
332
|
+
### Cron jobs
|
|
333
|
+
|
|
334
|
+
Four endpoints are meant to be called by a scheduler and are guarded by the `x-cron-secret` header.
|
|
335
|
+
Note that `auto-close` is a **GET**:
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
# Hourly
|
|
339
|
+
curl -X GET https://your-app/api/support/auto-close -H "x-cron-secret: $CRON_SECRET"
|
|
340
|
+
curl -X POST https://your-app/api/support/process-snooze -H "x-cron-secret: $CRON_SECRET"
|
|
341
|
+
curl -X POST https://your-app/api/support/process-scheduled -H "x-cron-secret: $CRON_SECRET"
|
|
342
|
+
|
|
343
|
+
# Daily / weekly
|
|
344
|
+
curl -X POST https://your-app/api/support/process-digests -H "x-cron-secret: $CRON_SECRET" \
|
|
345
|
+
-H "Content-Type: application/json" -d '{"frequency":"daily"}'
|
|
166
346
|
```
|
|
167
347
|
|
|
168
|
-
###
|
|
348
|
+
### Automation rules
|
|
349
|
+
|
|
350
|
+
`automation-rules` is edited in the admin, no code required. A rule has a trigger
|
|
351
|
+
(`ticket_created`, `ticket_updated` or `ticket_status_changed`), a match mode (`all` / `any`), a list
|
|
352
|
+
of conditions (field, `equals` / `not_equals` / `contains`, value) and a list of actions
|
|
353
|
+
(`set_status`, `set_priority`, `set_category`, `assign`, `add_tag`). For example: trigger
|
|
354
|
+
`ticket_created`, condition `category equals bug`, action `set_priority = urgent`.
|
|
169
355
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
356
|
+
## Deployment adapters
|
|
357
|
+
|
|
358
|
+
The plugin owns the generic support workflow; anything provider-specific stays in your application
|
|
359
|
+
and is injected through `capabilities`. Every key is optional and a missing adapter simply disables
|
|
360
|
+
the corresponding behaviour (`inboundEmail`, `projectSuggestions` and `aiTitles` also gate the
|
|
361
|
+
endpoints they need).
|
|
173
362
|
|
|
174
363
|
```ts
|
|
175
364
|
supportPlugin({
|
|
@@ -192,24 +381,69 @@ supportPlugin({
|
|
|
192
381
|
})
|
|
193
382
|
```
|
|
194
383
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
384
|
+
| Capability | Shape | Effect |
|
|
385
|
+
|---|---|---|
|
|
386
|
+
| `sms` | `{ adapter, buildMessage? }` | Sends an SMS alongside the client notification. |
|
|
387
|
+
| `digests` | `boolean` | **Inert** — no code reads it. `notification-queue` and `POST /api/support/process-digests` are registered unconditionally. |
|
|
388
|
+
| `inboundEmail` | `{ handle, secret?, secretHeader? }` | Registers `POST /api/support-webhook/inbound-email`, validated and rate-limited by the plugin. |
|
|
389
|
+
| `aiTitles` | `{ generate }` | Registers `POST /api/support/ticket-title` and `POST /api/support/generate-missing-titles`, and adds the title fields on clients. |
|
|
390
|
+
| `aiSummaries` | `{ generate }` | Overrides the default resolved-ticket synthesis generator. |
|
|
391
|
+
| `detailedBilling` | `boolean` | Adds the billing-line fields and their invoicing state. |
|
|
392
|
+
| `volunteering` | `boolean` | Adds pro-bono tracking and its estimated value. |
|
|
393
|
+
| `threadCleanup` | `{ clean }` | Best-effort cleanup of noisy messages after an inbound client reply. |
|
|
394
|
+
| `projectSuggestions` | `{ suggest }` | Registers `POST /api/support/suggest-projects`. |
|
|
395
|
+
|
|
396
|
+
## API Endpoints
|
|
397
|
+
|
|
398
|
+
All paths are relative to `/api`. "Staff" means a user of `userCollectionSlug` (default `users`);
|
|
399
|
+
"client" means a `support-clients` user. The *Flag* column names the build-time feature flag that has
|
|
400
|
+
to stay enabled for the endpoint to be registered.
|
|
401
|
+
|
|
402
|
+
### Tickets and agent tools
|
|
403
|
+
|
|
404
|
+
| Method | Path | Access | Flag | Purpose |
|
|
405
|
+
|---|---|---|---|---|
|
|
406
|
+
| `GET` | `/support/search` | staff | — | Global search over tickets, messages, clients and the knowledge base. |
|
|
407
|
+
| `GET` | `/support/kb/search` | public | — | Knowledge-base search (used by the portal and the chatbot). |
|
|
408
|
+
| `GET` | `/support/statuses` | authenticated | `customStatuses` | Ticket statuses, sorted. |
|
|
409
|
+
| `POST` | `/support/bulk-action` | staff | `bulkActions` | Apply one action to many tickets. |
|
|
410
|
+
| `POST` | `/support/merge-tickets` | staff | `merge` | Merge a source ticket into a target. |
|
|
411
|
+
| `POST` | `/support/split-ticket` | staff | `splitTicket` | Extract a message into a new ticket. |
|
|
412
|
+
| `POST` | `/support/apply-macro` | staff | `macros` | Run a multi-action macro on a ticket. |
|
|
413
|
+
| `POST` | `/support/tickets/:id/escalate` | staff | — | Move a ticket to `escalated`. |
|
|
414
|
+
| `POST` | `/support/tickets/:id/transfer` | staff or ticket owner | — | Email a conversation recap to an external address. |
|
|
415
|
+
| `POST` | `/support/tickets/:id/invite` | staff or ticket owner | — | Invite a collaborator on the ticket. |
|
|
416
|
+
| `POST` | `/support/tickets/:id/feedback` | client | — | Submit a rating (1-5) and an optional comment. |
|
|
417
|
+
| `POST` | `/support/satisfaction` | client | `satisfaction` | Submit the CSAT survey of a resolved ticket. |
|
|
418
|
+
| `POST` | `/support/send-reminder` | staff | `autoClose` | Nudge a client whose ticket awaits their reply. |
|
|
419
|
+
| `POST` | `/support/resend-notification` | staff | — | Re-send the email notification of one message. |
|
|
420
|
+
| `POST` | `/support/import-conversation` | staff, or `x-webhook-secret` | — | Import an existing email thread as a ticket. |
|
|
421
|
+
| `GET`/`POST` | `/support/typing` | authenticated | `collisionDetection` | Typing indicator. |
|
|
422
|
+
| `GET`/`POST` | `/support/presence` | staff | `collisionDetection` | Who is looking at this ticket. |
|
|
423
|
+
| `GET`/`POST` | `/support/signature` | staff | `signatures` | Per-agent email signature. |
|
|
424
|
+
| `GET`/`POST` | `/support/user-prefs` | staff | — | Per-user locale and signature. |
|
|
425
|
+
|
|
426
|
+
### Portal authentication and GDPR
|
|
427
|
+
|
|
428
|
+
| Method | Path | Access | Purpose |
|
|
429
|
+
|---|---|---|---|
|
|
430
|
+
| `POST` | `/support/login` | public, rate-limited | Portal password login. Sets an `HttpOnly` cookie; the JWT is never returned in JSON. |
|
|
431
|
+
| `POST` | `/support/2fa` | public, rate-limited | Email second factor. |
|
|
432
|
+
| `POST` | `/support/oauth/google` | public | Google sign-in, optionally restricted by `allowedEmailDomains`. |
|
|
433
|
+
| `GET` | `/support/export-data` | client | GDPR data export. |
|
|
434
|
+
| `POST` | `/support/delete-account` | client | GDPR right to erasure. |
|
|
435
|
+
| `POST` | `/support/merge-clients` | staff | Merge two client records. |
|
|
436
|
+
|
|
437
|
+
### AI
|
|
438
|
+
|
|
439
|
+
| Method | Path | Access | Flag | Purpose |
|
|
440
|
+
|---|---|---|---|---|
|
|
441
|
+
| `POST` | `/support/ai` | staff, rate-limited | `ai` | `sentiment`, `synthesis`, `suggest_reply` or `rewrite`. |
|
|
442
|
+
| `POST` | `/support/ai-agent` | staff, rate-limited | `ai` | Autonomous agent: answers from the knowledge base or escalates. |
|
|
443
|
+
| `POST` | `/support/ticket-synthesis` | staff, rate-limited | `ai` | Generate (or return the cached) per-ticket synthesis. |
|
|
444
|
+
| `GET`/`POST` | `/support/client-intelligence` | staff, rate-limited | `ai` | Cached client summary. |
|
|
445
|
+
| `POST` | `/support/chatbot` | public, rate-limited | `chatbot` | Knowledge-base chatbot used for deflection. |
|
|
446
|
+
| `POST` | `/support/seed-kb` | staff | — | Seed the knowledge base. |
|
|
213
447
|
|
|
214
448
|
```ts
|
|
215
449
|
await fetch('/api/support/ai-agent', {
|
|
@@ -219,300 +453,300 @@ await fetch('/api/support/ai-agent', {
|
|
|
219
453
|
})
|
|
220
454
|
```
|
|
221
455
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
456
|
+
### Live chat
|
|
457
|
+
|
|
458
|
+
| Method | Path | Access | Flag | Purpose |
|
|
459
|
+
|---|---|---|---|---|
|
|
460
|
+
| `GET`/`POST` | `/support/chat` | client | `chat` | Portal chat polling and send. |
|
|
461
|
+
| `GET` | `/support/chat-stream` | client | `chat` | SSE stream for the portal widget. |
|
|
462
|
+
| `GET`/`POST` | `/support/admin-chat` | staff | `chat` | Agent console: sessions, messages, send. |
|
|
463
|
+
| `GET` | `/support/admin-chat-stream` | staff | `chat` | SSE stream of the session list, or of one session's messages with `?session=`. Available for a custom agent console — the shipped Chat view polls `/support/admin-chat` instead. |
|
|
464
|
+
|
|
465
|
+
### Reporting, billing and email
|
|
466
|
+
|
|
467
|
+
| Method | Path | Access | Flag | Purpose |
|
|
468
|
+
|---|---|---|---|---|
|
|
469
|
+
| `GET` | `/support/admin-stats` | staff | — | Dashboard KPIs, volume series, CSAT and NPS. |
|
|
470
|
+
| `GET` | `/support/export-csv` | staff | — | CSV export of the tickets. |
|
|
471
|
+
| `GET` | `/support/sla-check` | staff | `sla` | Tickets breaching or close to breaching SLA. |
|
|
472
|
+
| `GET` | `/support/billing` | staff | `timeTracking` | Pre-billing for a period, optionally per project. |
|
|
473
|
+
| `GET` | `/support/billing/invoice` | staff | `timeTracking` | Invoice for the period, `format=html\|pdf`. |
|
|
474
|
+
| `GET` | `/support/email-stats` | authenticated | `emailTracking` | Email open/sent aggregates. |
|
|
475
|
+
| `GET` | `/support/track-open` | public, HMAC-signed | `emailTracking` | Tracking pixel. |
|
|
476
|
+
| `POST` | `/support/pending-emails/:id/process` | staff | `pendingEmails` | Turn a pending email into a ticket or a reply. |
|
|
477
|
+
| `DELETE` | `/support/purge-logs` | staff | — | Delete logs older than N days. |
|
|
478
|
+
|
|
479
|
+
### Settings, notifications and machine-to-machine
|
|
480
|
+
|
|
481
|
+
| Method | Path | Access | Flag | Purpose |
|
|
482
|
+
|---|---|---|---|---|
|
|
483
|
+
| `GET`/`POST` | `/support/settings` | staff | — | Read and write the support settings, including the runtime feature flags. |
|
|
484
|
+
| `GET`/`POST` | `/support/round-robin-config` | staff | `roundRobin` | Read and toggle round-robin assignment. |
|
|
485
|
+
| `GET` | `/support/push/vapid-public-key` | public | — | VAPID public key for the browser subscription. |
|
|
486
|
+
| `POST` | `/support/push/subscribe` | staff | — | Register an agent's push subscription. |
|
|
487
|
+
| `GET` | `/support/auto-close` | `x-cron-secret` | `autoClose` | Remind, then close, inactive tickets. |
|
|
488
|
+
| `POST` | `/support/process-snooze` | `x-cron-secret` | `snooze` | Wake snoozed tickets. |
|
|
489
|
+
| `POST` | `/support/process-scheduled` | `x-cron-secret` | `scheduledReplies` | Release scheduled replies. |
|
|
490
|
+
| `POST` | `/support/process-digests` | `x-cron-secret` | — | Send the daily or weekly digests. |
|
|
491
|
+
| `POST` | `/support/channels/webhook` | `x-channel-secret` | — | Normalized inbound WhatsApp / Messenger webhook. |
|
|
492
|
+
| `POST` | `/support-webhook/inbound-email` | configured secret header | `capabilities.inboundEmail` | Inbound email, validated and bounded by the plugin. |
|
|
493
|
+
| `POST` | `/support/suggest-projects` | staff | `capabilities.projectSuggestions` | Deployment-specific project suggestions. |
|
|
494
|
+
| `POST` | `/support/ticket-title` | staff | `capabilities.aiTitles` | Generate a short display title for one ticket. |
|
|
495
|
+
| `POST` | `/support/generate-missing-titles` | staff | `capabilities.aiTitles` | Backfill the missing display titles. |
|
|
496
|
+
|
|
497
|
+
## Webhooks
|
|
498
|
+
|
|
499
|
+
Create a row in `webhook-endpoints` with a URL, a `secret` and the events you want, and set it
|
|
500
|
+
`active`. Each event is delivered **once** per matching endpoint (up to 50, 10 s timeout each), as a
|
|
501
|
+
single `POST`:
|
|
502
|
+
|
|
503
|
+
```http
|
|
504
|
+
Content-Type: application/json
|
|
505
|
+
User-Agent: PayloadSupport-Webhook/1.0
|
|
506
|
+
X-Webhook-Signature: <HMAC-SHA256 of the raw body, keyed with the endpoint secret>
|
|
507
|
+
|
|
508
|
+
{ "event": "ticket_created", "data": { … }, "timestamp": "2026-09-07T10:00:00.000Z" }
|
|
232
509
|
```
|
|
233
510
|
|
|
234
|
-
|
|
511
|
+
The signature header is only sent when the endpoint has a `secret`, so always set one. There is no
|
|
512
|
+
`X-Webhook-Secret` header any more — verify the HMAC signature instead. `lastTriggeredAt` and
|
|
513
|
+
`lastStatus` are written back on the endpoint after every attempt. Requires `features.webhooks`.
|
|
514
|
+
|
|
515
|
+
| Event | `data` |
|
|
516
|
+
|---|---|
|
|
517
|
+
| `ticket_created` | `ticketId`, `id`, `ticketNumber`, `subject`, `status`, `priority`, `category` |
|
|
518
|
+
| `ticket_resolved` | `ticketId`, `id`, `ticketNumber`, `subject`, `previousStatus` |
|
|
519
|
+
| `ticket_assigned` | `ticketId`, `id`, `ticketNumber`, `subject`, `assignedTo` |
|
|
520
|
+
| `ticket_replied` | `ticketId`, `messageId`, `authorType`, `body` (first 500 characters), plus `scheduled: true` when released by the scheduler |
|
|
521
|
+
| `sla_breached` | Selectable on an endpoint, but nothing dispatches it — subscribing to it delivers nothing. |
|
|
522
|
+
|
|
523
|
+
## Collections
|
|
524
|
+
|
|
525
|
+
"Staff" is `userCollectionSlug` (default `users`), "client" is a `support-clients` user.
|
|
526
|
+
|
|
527
|
+
`collectionSlugs` accepts 19 keys: `tickets`, `ticketMessages`, `supportClients`, `timeEntries`,
|
|
528
|
+
`cannedResponses`, `ticketActivityLog`, `satisfactionSurveys`, `knowledgeBase`, `chatMessages`,
|
|
529
|
+
`pendingEmails`, `emailLogs`, `authLogs`, `webhookEndpoints`, `slaPolicies`, `macros`,
|
|
530
|
+
`ticketStatuses`, `ticketFeedback`, `rateLimits` and `counters`. Three caveats:
|
|
531
|
+
|
|
532
|
+
- **`media` has no key, and it is the blocking one.** The internal `CollectionSlugs` type carries a
|
|
533
|
+
`media` slug (default `media`) and uses it as the `relationTo` of the attachment fields on
|
|
534
|
+
`ticket-messages` and `pending-emails`, but `collectionSlugs` exposes no way to set it. If your
|
|
535
|
+
uploads collection is named anything else, Payload throws `InvalidFieldRelationship` at config
|
|
536
|
+
sanitisation and the whole `payload.config.ts` fails to build. Your uploads collection has to be
|
|
537
|
+
named `media`.
|
|
538
|
+
- Three slugs are genuinely fixed: `ticketFeedback` is accepted but ignored, and
|
|
539
|
+
`ticket-collaborators` and `client-summaries` have no key at all — those three files write their
|
|
540
|
+
own `slug:` as a literal.
|
|
541
|
+
- `notificationQueue`, `automationRules`, `supportTeams` and `pushSubscriptions` are a different
|
|
542
|
+
case: their collections *do* read `slugs.*`, only the public option type omits the keys. The
|
|
543
|
+
plumbing is there; widening the type would be enough.
|
|
544
|
+
|
|
545
|
+
| Slug | Injected | Role | Read | Write |
|
|
546
|
+
|---|---|---|---|---|
|
|
547
|
+
| `tickets` | always | The tickets themselves | staff; clients see the tickets they own or collaborate on | staff; a client create keeps only `subject`, `category`, `priority`, `project` |
|
|
548
|
+
| `ticket-messages` | always | Conversation, internal notes, scheduled replies | staff; clients see non-internal, already-sent messages of their tickets | staff; clients only on an accessible ticket, and may edit only their own messages |
|
|
549
|
+
| `support-clients` | always | Portal auth collection (2FA, OAuth, notification preferences) | staff; a client reads their own row | staff; a client updates their own row minus `tier`, `accountManager`, `opportunities`, `notes`, `googleId` and the 2FA internals |
|
|
550
|
+
| `ticket-collaborators` | always | Extra clients invited on a ticket — source of truth for client read scope | staff and the invited client | staff only (`update` denied to everyone); the invite endpoint writes with `overrideAccess`. **Fixed slug.** |
|
|
551
|
+
| `ticket-activity-log` | always | Append-only audit trail | staff; clients on their own tickets | nobody — hooks only (`create`/`update` denied) |
|
|
552
|
+
| `canned-responses` | always | Quick reply templates | staff | staff |
|
|
553
|
+
| `satisfaction-surveys` | always | CSAT after resolution | staff and the surveyed client | immutable once submitted |
|
|
554
|
+
| `ticket-feedback` | always | Portal rating and comment | staff and the author | immutable once submitted. **Fixed slug.** |
|
|
555
|
+
| `knowledge-base` | always | FAQ and chatbot corpus | staff; published entries publicly | staff |
|
|
556
|
+
| `notification-queue` | always | Digest queue | staff | hooks only |
|
|
557
|
+
| `automation-rules` | always | Event → conditions → actions | staff | staff |
|
|
558
|
+
| `support-teams` | always | Teams, used by team SLA and `SUPPORT_TEAM_SCOPING` | staff | staff |
|
|
559
|
+
| `push-subscriptions` | always | Agent Web Push subscriptions | staff | the subscribe endpoint only |
|
|
560
|
+
| `support-counters` | always | Atomic ticket-number counter | nobody | internal only |
|
|
561
|
+
| `auth-logs` | `authLogs` | Portal authentication audit | staff | system-generated |
|
|
562
|
+
| `time-entries` | `timeTracking` | Time spent, rolled up into `tickets.totalTimeMinutes` | staff | staff |
|
|
563
|
+
| `email-logs` | `emailTracking` | Sent/opened tracking | staff | system-generated |
|
|
564
|
+
| `webhook-endpoints` | `webhooks` | Outbound webhook subscriptions | staff | staff |
|
|
565
|
+
| `sla-policies` | `sla` | Default and per-team SLA targets | staff | staff |
|
|
566
|
+
| `macros` | `macros` | Multi-action shortcuts | staff | staff |
|
|
567
|
+
| `chat-messages` | `chat` | Live chat sessions | staff and the session's client | authenticated |
|
|
568
|
+
| `pending-emails` | `pendingEmails` | Inbound emails awaiting triage | staff | staff, or a create carrying a valid `x-webhook-secret` |
|
|
569
|
+
| `ticket-statuses` | `customStatuses` | Configurable statuses | authenticated | staff |
|
|
570
|
+
| `client-summaries` | `ai` | Cached client-intelligence summaries | staff | staff. **Fixed slug**, and its access rules compare against the literal `users` collection, so a renamed staff collection loses access to it. |
|
|
571
|
+
| `support-rate-limits` | `rateLimitStore: 'payload'` | Shared rate-limit counters | nobody | internal only |
|
|
572
|
+
|
|
573
|
+
## Package Exports
|
|
574
|
+
|
|
575
|
+
| Subpath | Exposes | Formats | Environment |
|
|
576
|
+
|---|---|---|---|
|
|
577
|
+
| `@consilioweb/payload-support` | `supportPlugin`, the collection factories, the SLA and status-email hooks, `dispatchWebhook`, `generateTicketSynthesis`, the rate-limit stores, the settings and feature helpers, and the public types | ESM + CJS, typed | Server (`payload.config.ts`) |
|
|
578
|
+
| `@consilioweb/payload-support/client` | `SUPPORT_CLIENT_VERSION` — the `'use client'` barrel marker | ESM + CJS, typed | Client |
|
|
579
|
+
| `@consilioweb/payload-support/views` | The 13 admin views, for the Payload import map | **ESM only**, typed | Server components |
|
|
580
|
+
| `@consilioweb/payload-support/components/TicketConversation` | The conversation field component | **ESM only**, typed | Client |
|
|
581
|
+
|
|
582
|
+
The two view/component subpaths lost their CJS build in 3.0.0: the emitted `.cjs` barrels
|
|
583
|
+
`require`d their ESM siblings and threw `ERR_REQUIRE_ESM`, so they were unreachable. Payload 3 and
|
|
584
|
+
Next are ESM-first; import them with ESM.
|
|
235
585
|
|
|
236
|
-
```
|
|
237
|
-
import { supportPlugin,
|
|
586
|
+
```ts
|
|
587
|
+
import { supportPlugin, dispatchWebhook, generateTicketSynthesis } from '@consilioweb/payload-support'
|
|
238
588
|
import type { SupportPluginConfig, SupportFeatures } from '@consilioweb/payload-support'
|
|
239
589
|
```
|
|
240
590
|
|
|
241
|
-
|
|
591
|
+
## Requirements
|
|
242
592
|
|
|
243
|
-
|
|
|
593
|
+
| Requirement | Range | Source |
|
|
244
594
|
|---|---|---|
|
|
245
|
-
|
|
|
246
|
-
|
|
|
247
|
-
| `
|
|
248
|
-
|
|
|
249
|
-
|
|
|
250
|
-
| `
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
595
|
+
| Node.js | `>=20.9.0` | `engines.node` |
|
|
596
|
+
| Payload | `^3.37.0` | `peerDependencies.payload` |
|
|
597
|
+
| `@payloadcms/next` | `^3.37.0` | `peerDependencies` |
|
|
598
|
+
| Next.js | `^15.2.9 \|\| ^16.0.0` | `peerDependencies` |
|
|
599
|
+
| React / React DOM | `^19.0.0` | `peerDependencies` |
|
|
600
|
+
| `lucide-react` | `>=0.300.0` | `peerDependencies` |
|
|
601
|
+
| `@anthropic-ai/sdk` | any recent version | Runtime-only, install it yourself if you use the AI features |
|
|
602
|
+
| `@payloadcms/richtext-lexical` | any 3.x | Only if you mount the portal FAQ page |
|
|
603
|
+
|
|
604
|
+
Installing on Node 18, React 18, Next 14 or Payload < 3.37 warns, and fails outright under
|
|
605
|
+
`engine-strict` or a strict peer resolver.
|
|
606
|
+
|
|
607
|
+
## Security
|
|
608
|
+
|
|
609
|
+
- **Cross-client isolation** — reads on `tickets`, `ticket-messages` and `ticket-activity-log` are
|
|
610
|
+
constrained to the ticket ids a client owns or collaborates on, resolved server-side and failing
|
|
611
|
+
closed on an empty set. Client writes are validated against that same set.
|
|
612
|
+
- **Allow-listed client writes** — a client-initiated ticket create keeps only `subject`, `category`,
|
|
613
|
+
`priority` and `project`; `client`, `status` and `source` are forced. A client updating their own
|
|
614
|
+
profile cannot touch `tier`, `accountManager`, `opportunities`, `notes`, `googleId` or the 2FA
|
|
615
|
+
internals.
|
|
616
|
+
- **Cookie-only portal JWTs** — `HttpOnly`, `SameSite=Lax`, `Path=/`, 2 h max age, and `Secure`
|
|
617
|
+
outside development. Login, OAuth and 2FA responses never return the token in JSON.
|
|
618
|
+
- **2FA enforced server-side** (`beforeLogin`), and the endpoint refuses to run without
|
|
619
|
+
`PAYLOAD_SECRET` rather than falling back to an insecure default. OAuth verifies the Google email.
|
|
620
|
+
- **Stored-XSS protection** — message HTML is sanitized on write, not on render.
|
|
621
|
+
- **HMAC everywhere it matters** — webhook deliveries and tracking pixels are signed and verified in
|
|
622
|
+
constant time, with idempotent writes. Cron and webhook secrets are read from headers only; a
|
|
623
|
+
query-string secret is rejected.
|
|
624
|
+
- **Persistent rate limiting** on login, 2FA, chat, invitations, transfers, imports and every AI
|
|
625
|
+
endpoint. Use `rateLimitStore: 'payload'` (or your own shared store) on multi-instance deployments.
|
|
626
|
+
- **No silent third-party AI host** — the `ollama` provider fails loudly on a missing
|
|
627
|
+
`OLLAMA_API_URL` instead of defaulting to someone else's server.
|
|
628
|
+
- **Bounded inbound email** payloads and attachments.
|
|
629
|
+
|
|
630
|
+
### Reporting security issues
|
|
631
|
+
|
|
632
|
+
Email **contact@consilioweb.fr** instead of opening a public issue.
|
|
633
|
+
|
|
634
|
+
### Best practices
|
|
635
|
+
|
|
636
|
+
- Set a strong `PAYLOAD_SECRET` and a real `CRON_SECRET`.
|
|
637
|
+
- Restrict read access on your `media` collection to the ticket owner.
|
|
638
|
+
- Put the app behind a trusted proxy so `x-forwarded-for` is meaningful.
|
|
639
|
+
- Rotate every webhook endpoint secret that existed before 3.0.0 — the legacy transport sent it in
|
|
640
|
+
clear on every delivery.
|
|
641
|
+
|
|
642
|
+
## Performance
|
|
643
|
+
|
|
644
|
+
No benchmarks are claimed here; the figures depend on your volume and host. What the code actually
|
|
645
|
+
does:
|
|
290
646
|
|
|
291
647
|
| Concern | Approach |
|
|
292
|
-
|
|
293
|
-
| Settings reads | In-process cache
|
|
294
|
-
| List queries |
|
|
295
|
-
|
|
|
296
|
-
|
|
|
297
|
-
|
|
|
298
|
-
|
|
299
|
-
<img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" width="100%" alt="" />
|
|
300
|
-
|
|
301
|
-
## 📚 Examples
|
|
302
|
-
|
|
303
|
-
### Automation rule (no code)
|
|
304
|
-
|
|
305
|
-
Create a **Automation Rules** entry: `event = ticket_created`, condition `category = bug`, action `set_priority = urgent`. New "bug" tickets become "urgent" automatically.
|
|
306
|
-
|
|
307
|
-
### Wire the cron jobs
|
|
308
|
-
|
|
309
|
-
```bash
|
|
310
|
-
# Hourly: auto-close, snooze wake-up, scheduled replies
|
|
311
|
-
curl -X POST https://your-app/api/support/auto-close -H "x-cron-secret: $CRON_SECRET"
|
|
312
|
-
curl -X POST https://your-app/api/support/process-snooze -H "x-cron-secret: $CRON_SECRET"
|
|
313
|
-
# Daily / weekly: notification digests
|
|
314
|
-
curl -X POST https://your-app/api/support/process-digests -H "x-cron-secret: $CRON_SECRET" -d '{"frequency":"daily"}'
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
<img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" width="100%" alt="" />
|
|
318
|
-
|
|
319
|
-
## ❓ FAQ
|
|
320
|
-
|
|
321
|
-
<details>
|
|
322
|
-
<summary><b>Which databases are supported?</b></summary>
|
|
323
|
-
|
|
324
|
-
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.
|
|
325
|
-
|
|
326
|
-
</details>
|
|
327
|
-
|
|
328
|
-
<details>
|
|
329
|
-
<summary><b>What Node / Payload versions are supported?</b></summary>
|
|
330
|
-
|
|
331
|
-
Node.js 18+, Payload 3.x, React 18 or 19.
|
|
332
|
-
|
|
333
|
-
</details>
|
|
334
|
-
|
|
335
|
-
<details>
|
|
336
|
-
<summary><b>Is there TypeScript support?</b></summary>
|
|
337
|
-
|
|
338
|
-
Yes — strict TypeScript, with business types exported (`SupportPluginConfig`, `SupportFeatures`, …).
|
|
648
|
+
|---|---|
|
|
649
|
+
| Settings reads | In-process cache with a 60 s TTL and explicit invalidation on save — the `afterChange` hook chain reads the same preference row many times per ticket mutation. |
|
|
650
|
+
| List queries | Indexed fields on `tickets` for the columns the inbox, dashboard and SLA views filter on. |
|
|
651
|
+
| Stats and billing | Paginated aggregation (100 rows per page) with a bounded `select` — never loads all tickets in memory. |
|
|
652
|
+
| Counters | The inbox counters and the portal poller call `/api/*/count` instead of fetching rows. |
|
|
653
|
+
| Client time totals | The CRM sums the per-ticket `totalTimeMinutes` rollup instead of scanning `time-entries`. |
|
|
654
|
+
| AI synthesis | Persisted on the ticket (`aiSummary`, `aiSummaryGeneratedAt`) and reused until forced. |
|
|
339
655
|
|
|
340
|
-
|
|
656
|
+
## Troubleshooting
|
|
341
657
|
|
|
342
|
-
|
|
343
|
-
|
|
658
|
+
**`useServerFunctions must be used within ServerFunctionsProvider`** — align every `@payloadcms/*`
|
|
659
|
+
package on the same version, 3.75 or later.
|
|
344
660
|
|
|
345
|
-
|
|
661
|
+
**`SQLITE_BUSY` / database is locked during seed** — make the seed sequential (no `Promise.all` on
|
|
662
|
+
inserts) and add `busyTimeout: 10000` to the SQLite adapter.
|
|
346
663
|
|
|
347
|
-
|
|
664
|
+
**Admin views do not load** — regenerate the import map (`pnpm payload generate:importmap`). In a
|
|
665
|
+
headless context, pass `skipViews: true`.
|
|
348
666
|
|
|
349
|
-
|
|
350
|
-
|
|
667
|
+
**New fields or collections missing in production** — standalone builds do not run migrations. Push
|
|
668
|
+
the schema before deploying, including `googleId`, `twoFactorVerifiedAt`, `slaPausedAt`, `nps`,
|
|
669
|
+
`mentions`, and the `notification-queue`, `automation-rules`, `ticket-collaborators`,
|
|
670
|
+
`support-counters` collections.
|
|
351
671
|
|
|
352
|
-
|
|
672
|
+
**`Cannot find module '@anthropic-ai/sdk'`** — install it in your app; the plugin does not bundle or
|
|
673
|
+
declare it.
|
|
353
674
|
|
|
354
|
-
|
|
675
|
+
**`ERR_REQUIRE_ESM` on `@consilioweb/payload-support/views`** — that subpath is ESM-only since 3.0.0.
|
|
355
676
|
|
|
356
|
-
|
|
677
|
+
## FAQ
|
|
357
678
|
|
|
358
|
-
|
|
679
|
+
**Which databases are supported?** — built and tested on SQLite (`@payloadcms/db-sqlite`) with a
|
|
680
|
+
sequential seed and `busyTimeout`. It works with any Payload adapter, but the indexes and queries
|
|
681
|
+
were validated on SQLite.
|
|
359
682
|
|
|
360
|
-
|
|
683
|
+
**Do I need an external AI service?** — no. The AI features are optional, and `provider: 'ollama'`
|
|
684
|
+
points the SDK at the `OLLAMA_API_URL` you choose, so no ticket content has to leave your
|
|
685
|
+
infrastructure. `provider: 'openai'` and `provider: 'custom'` are accepted by the `AIProviderConfig`
|
|
686
|
+
type but not implemented: at runtime everything that is not `ollama` goes to Anthropic.
|
|
361
687
|
|
|
362
|
-
|
|
688
|
+
**Is TypeScript supported?** — yes, strict, with the business types exported
|
|
689
|
+
(`SupportPluginConfig`, `SupportFeatures`, `TicketData`, …). All four export subpaths ship
|
|
690
|
+
declarations.
|
|
363
691
|
|
|
364
|
-
|
|
692
|
+
**How do I migrate the schema in production?** — in standalone mode the schema is not
|
|
693
|
+
auto-migrated: generate and push it before deploying. See [Troubleshooting](#troubleshooting).
|
|
365
694
|
|
|
366
|
-
|
|
695
|
+
## Upgrading
|
|
367
696
|
|
|
368
|
-
###
|
|
697
|
+
### 2.x → 3.0
|
|
369
698
|
|
|
370
|
-
|
|
699
|
+
1. **Rotate the secret of every webhook endpoint.** The legacy transport sent it in clear in
|
|
700
|
+
`X-Webhook-Secret` on every delivery. Subscribers must now verify `X-Webhook-Signature`.
|
|
701
|
+
2. **Set `OLLAMA_API_URL`** before restarting if you use `provider: 'ollama'` — the hard-coded
|
|
702
|
+
fallback host is gone and its absence is now a hard error.
|
|
703
|
+
3. **Move any integration that authenticated as a portal client** to a staff account or to a
|
|
704
|
+
server-side call with `overrideAccess: true`: client writes are scoped and allow-listed now.
|
|
705
|
+
4. **Recompute `tickets.totalTimeMinutes`** where time entries were deleted under 2.x. The new
|
|
706
|
+
`afterDelete` hook only fires on future deletions, and the CRM now trusts the rollup instead of
|
|
707
|
+
recomputing it — so a stale total keeps over-billing until something touches a time entry on that
|
|
708
|
+
ticket.
|
|
709
|
+
5. **Import `./views` and `./components/TicketConversation` with ESM** — their CJS builds are gone.
|
|
710
|
+
6. Check your platform: Node 20.9+, React 19, Next 15.2.9+, Payload 3.37+, and install
|
|
711
|
+
`lucide-react` explicitly if you were relying on it being optional.
|
|
371
712
|
|
|
372
|
-
###
|
|
713
|
+
### 1.x → 2.0
|
|
373
714
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
- **2FA** enforced server-side (`beforeLogin`); **OAuth** verifies the Google email.
|
|
384
|
-
- **Sanitization** of message HTML server-side (stored-XSS protection).
|
|
385
|
-
- **HMAC-signed** webhooks and tracking pixels with constant-time verification and idempotent writes.
|
|
386
|
-
- **Cookie-only JWTs** for portal authentication (`HttpOnly`, `Secure`, `SameSite=Lax`).
|
|
387
|
-
- **Persistent rate limiting** for authentication, 2FA, chats, invitations, transfers and AI endpoints.
|
|
388
|
-
- **Bounded inbound email** payloads and attachments; cron/webhook secrets are accepted in headers only.
|
|
389
|
-
|
|
390
|
-
### Reporting Security Issues
|
|
391
|
-
|
|
392
|
-
Please email **contact@consilioweb.fr** instead of opening a public issue.
|
|
393
|
-
|
|
394
|
-
### Best Practices
|
|
395
|
-
|
|
396
|
-
- ✅ Set a strong `PAYLOAD_SECRET` (never a default value)
|
|
397
|
-
- ✅ Set `CRON_SECRET` to protect the cron endpoints
|
|
398
|
-
- ✅ Restrict read access to the `media` collection to the ticket owner
|
|
399
|
-
- ✅ Put the app behind a trusted proxy (`x-forwarded-for` header)
|
|
400
|
-
- ✅ Keep the plugin up-to-date
|
|
401
|
-
|
|
402
|
-
<img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" width="100%" alt="" />
|
|
403
|
-
|
|
404
|
-
## 🤝 Contributing
|
|
715
|
+
1. Generate an additive Payload migration for the `support-counters` collection and, when
|
|
716
|
+
`rateLimitStore: 'payload'` is enabled, `support-rate-limits`. Run it before restart.
|
|
717
|
+
2. Regenerate the Payload types and the admin import map.
|
|
718
|
+
3. Remove duplicated host routes and enable the plugin endpoints (`skipEndpoints: false`).
|
|
719
|
+
4. Read portal authentication exclusively from the `HttpOnly` cookie. Login, OAuth and 2FA responses
|
|
720
|
+
no longer expose the JWT in JSON.
|
|
721
|
+
5. Send cron and webhook secrets only through their configured headers. Query-string secrets are
|
|
722
|
+
rejected.
|
|
723
|
+
6. Custom rate-limit stores must implement the asynchronous `RateLimitStore` interface.
|
|
405
724
|
|
|
406
|
-
|
|
725
|
+
## Contributing
|
|
407
726
|
|
|
408
|
-
1.
|
|
409
|
-
2.
|
|
410
|
-
3.
|
|
411
|
-
4.
|
|
412
|
-
5. **Open** a Pull Request
|
|
727
|
+
1. Fork the repository.
|
|
728
|
+
2. Create a feature branch (`git checkout -b feature/AmazingFeature`).
|
|
729
|
+
3. Commit your changes (`git commit -m 'feat: add AmazingFeature'`).
|
|
730
|
+
4. Push the branch and open a Pull Request.
|
|
413
731
|
|
|
414
|
-
Run the checks before submitting:
|
|
732
|
+
Run the checks before submitting — the suite is 179 vitest tests:
|
|
415
733
|
|
|
416
734
|
```bash
|
|
417
735
|
pnpm typecheck && pnpm test && pnpm build
|
|
418
736
|
```
|
|
419
737
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
## 📝 Changelog
|
|
738
|
+
## Changelog
|
|
423
739
|
|
|
424
740
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
425
741
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
- 🌍 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.
|
|
429
|
-
- 🧭 Removes translation-key collisions in the support dashboard and settings views.
|
|
430
|
-
- ✅ Adds regression coverage for all 518 literal translation keys used by the support UI and keeps both locales aligned (132 tests passing).
|
|
431
|
-
|
|
432
|
-
### [2.0.0] — 2026-07-16
|
|
433
|
-
|
|
434
|
-
- 🔐 Security hardening for authentication, tracking pixels, webhook/cron secrets, HTML/AI output and inbound-email limits.
|
|
435
|
-
- 🚦 Asynchronous persistent rate limiting with a Payload-backed store and memory fallback.
|
|
436
|
-
- 🔢 Atomic persistent ticket counters with configurable prefix and padding.
|
|
437
|
-
- 🧩 Typed deployment adapters for SMS, inbound email, AI workflows, billing, volunteering, cleanup and project suggestions.
|
|
438
|
-
- 🏗️ The plugin is now the single owner of generic support collections, hooks, views and endpoints.
|
|
439
|
-
- ✅ 124 tests covering the security regressions and new capabilities.
|
|
440
|
-
|
|
441
|
-
### [1.1.1] — 2026-06-26
|
|
442
|
-
|
|
443
|
-
- 🐛 **Fix client ticket reopen**: allow clients to set `waiting_support` (the status the portal's "Reopen" button sends). It was silently rejected, leaving the ticket `resolved`.
|
|
444
|
-
|
|
445
|
-
### [1.1.0] — 2026-06-25
|
|
446
|
-
|
|
447
|
-
- 🎯 **Per-team SLA policies & dashboards** (team policy overrides the default, `?teamId=` scoping).
|
|
448
|
-
- 🔔 **Native push / browser notifications** (Web Push / VAPID, agent pushed on new client messages).
|
|
449
|
-
- 🎭 **End-to-end UI test harness** (browser-driven admin via Playwright, `pnpm test:e2e`).
|
|
450
|
-
- ✅ 109 integration tests (up from 102).
|
|
451
|
-
|
|
452
|
-
### [1.0.0] — 2026-06-25
|
|
453
|
-
|
|
454
|
-
- ✨ Complete ticketing, SLA (+ pause-on-hold), automation & **visual rules engine**.
|
|
455
|
-
- 🤖 AI (sentiment, suggestion, synthesis, chatbot) + **autonomous AI agent**.
|
|
456
|
-
- 💬 Live chat, client portal (2FA, OAuth), knowledge base.
|
|
457
|
-
- 💶 Time tracking, pre-billing & print-ready invoice.
|
|
458
|
-
- 📊 CSAT + NPS, real volume series, digests, webhooks.
|
|
459
|
-
- 🔐 Security hardening (cross-client isolation, anti-XSS, server-side 2FA).
|
|
460
|
-
- ✅ Integration test harness (Payload + in-memory SQLite), 102 tests.
|
|
461
|
-
|
|
462
|
-
<details>
|
|
463
|
-
<summary><b>Previous versions (0.x)</b></summary>
|
|
464
|
-
|
|
465
|
-
- **0.16.0** — Manual client reminder + auto-close after 24h
|
|
466
|
-
- **0.15.0** — Next 16 compatibility + accumulated features
|
|
467
|
-
- **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
|
|
468
|
-
- **0.6.0 → 0.6.4** — Split build (`bundle:false`) for Next.js RSC compatibility + barrel exports
|
|
469
|
-
- **0.5.0** — Full i18n across the 13 admin views
|
|
470
|
-
- **0.4.0** — Per-user preferences (locale, signature) vs global settings
|
|
471
|
-
- **0.3.0** — Feature parity with ConsilioWEB + `skipCollections` / `skipViews` / `skipEndpoints`
|
|
472
|
-
- **0.2.0** — 2026-04-08 — Security overhaul, SSE live chat, webhooks, SLA, scheduled replies, client portal
|
|
473
|
-
- **0.1.0** — 2026-04-08 — Initial scaffold: `supportPlugin()`, 15 collections, feature flags, AI provider abstraction
|
|
474
|
-
|
|
475
|
-
</details>
|
|
476
|
-
|
|
477
|
-
<img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" width="100%" alt="" />
|
|
478
|
-
|
|
479
|
-
## 🗺️ Roadmap
|
|
480
|
-
|
|
481
|
-
- [x] Ticketing, SLA, time tracking & billing
|
|
482
|
-
- [x] AI assists + autonomous AI agent
|
|
483
|
-
- [x] Live chat, client portal, knowledge base
|
|
484
|
-
- [x] Visual automation-rules engine
|
|
485
|
-
- [x] Native binary PDF invoice (alongside the print-ready HTML)
|
|
486
|
-
- [x] Full admin i18n (all views)
|
|
487
|
-
- [x] Social channels (WhatsApp, Messenger)
|
|
488
|
-
- [x] Multi-team / workspaces mode
|
|
489
|
-
- [x] Per-team SLA policies & dashboards
|
|
490
|
-
- [x] Native push / browser notifications
|
|
491
|
-
- [x] End-to-end UI test harness (browser-driven admin)
|
|
492
|
-
|
|
493
|
-
<img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" width="100%" alt="" />
|
|
494
|
-
|
|
495
|
-
## ☕ Support
|
|
496
|
-
|
|
497
|
-
If this plugin saves you time, consider buying me a coffee!
|
|
498
|
-
|
|
499
|
-
<a href="https://buymeacoffee.com/pown3d">
|
|
500
|
-
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" width="217" />
|
|
501
|
-
</a>
|
|
502
|
-
|
|
503
|
-
<img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" width="100%" alt="" />
|
|
742
|
+
## Support
|
|
504
743
|
|
|
505
|
-
|
|
744
|
+
If this plugin saves you time, consider [buying me a coffee](https://buymeacoffee.com/pown3d).
|
|
506
745
|
|
|
507
|
-
|
|
746
|
+
## License
|
|
508
747
|
|
|
509
|
-
|
|
748
|
+
MIT — see [LICENSE](LICENSE).
|
|
510
749
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
<a href="https://github.com/pOwn3d/payload-support#readme" style="color: #1f8a5b; text-decoration: none; margin: 0 15px;">Documentation</a>
|
|
515
|
-
<a href="https://github.com/pOwn3d/payload-support/issues" style="color: #1f8a5b; text-decoration: none; margin: 0 15px;">Issues</a>
|
|
516
|
-
<a href="https://www.npmjs.com/package/@consilioweb/payload-support" style="color: #1f8a5b; text-decoration: none; margin: 0 15px;">npm</a>
|
|
517
|
-
</p>
|
|
518
|
-
</div>
|
|
750
|
+
Built and maintained by [ConsilioWEB](https://consilioweb.fr) ·
|
|
751
|
+
[Issues](https://github.com/pOwn3d/payload-support/issues) ·
|
|
752
|
+
[npm](https://www.npmjs.com/package/@consilioweb/payload-support)
|