Word filter: the built-in module as an example
The core module that ships in every Kommio build, as the example to copy: a before-send hook that blocks banned words, an inbound hook that drops spam phrases, a settings section rendered from JSON Schema, and an automation action, in a hundred lines of Go on the public plugin contract.
Last updated
What it is
A core module is Go code compiled into the Kommio server by whoever operates it, so it applies to self-hosted installs; in the app it is part of Kommio, never named as a plugin. The word filter is a first-party module that uses only the public contract in pkg/plugin and ships in the binary, which makes it the example that can never go stale: if the contract changed, the build would fail. It contributes four things:
BeforeMessageSend: blocks an agent reply containing a banned word; the agent sees "the reply contains a banned word: …" in the composer.OnInboundEvent: drops an inbound message that matches a spam phrase, before it becomes a conversation.- A settings section (
wordfilter.lists) holding both lists, shown under Settings › Marketplace › Installed as the item "Word filter" (kind "Message guard") and edited there from its JSON Schema by people withplugins:manage, per workspace. - An automation action, "Flag conversation with a note", which tags the conversation
flaggedand adds a private note; rules can use it like any built-in action.
When to use it
Copy this when the behaviour has to run inside the server, with no network hop and no service of your own to host: a check on every reply or every inbound message, a settings page for your operators, an action for rules, or a channel Kommio does not have yet. It needs a Go toolchain and your own build of the server. For anything you build for one workspace, or without touching the build, start with an app: it does the same hook over HTTP in any language and is installed per workspace by an administrator.
Set up and run
Nothing to set up: it is registered in cmd/kommio/main.go (ps := []plugin.Plugin{wordfilter.Plugin{}}), so every Kommio build has it. To write your own, copy this folder, change the package and the manifest name, implement the hook interfaces you need from pkg/plugin, add your plugin to that list (or to server.Options.Plugins in a main of your own, as the plugins guide shows) and build the server:
go build ./cmd/kommioThe code
// Package wordfilter is a first-party plugin that uses only the public
// plugin API: it blocks agent replies containing banned words, drops
// inbound spam by phrase, flags conversations from an automation action,
// and exposes its word lists as a settings section.
package wordfilter
import (
"context"
"encoding/json"
"strings"
"kommio.io/kommio/pkg/channel"
"kommio.io/kommio/pkg/plugin"
)
// Plugin implements plugin.Plugin plus four hooks.
type Plugin struct{}
// Section is the settings key this plugin owns.
const Section = "wordfilter.lists"
type lists struct {
BannedOutbound []string `json:"banned_outbound"`
SpamInbound []string `json:"spam_inbound"`
}
// Manifest implements plugin.Plugin.
func (Plugin) Manifest() plugin.Manifest {
return plugin.Manifest{Name: "wordfilter", Version: "1.0.0", APIVersion: plugin.APIVersion, Description: "Blocks replies with banned words and drops inbound spam phrases."}
}
func load(hc plugin.HookContext) lists {
var l lists
if hc.Settings != nil {
_ = json.Unmarshal(hc.Settings(Section), &l)
}
return l
}
func contains(text string, words []string) string {
lower := strings.ToLower(text)
for _, w := range words {
w = strings.TrimSpace(strings.ToLower(w))
if w != "" && strings.Contains(lower, w) {
return w
}
}
return ""
}
// BeforeMessageSend implements plugin.BeforeMessageSendHook.
func (Plugin) BeforeMessageSend(_ context.Context, hc plugin.HookContext, m *plugin.OutgoingMessage) error {
if w := contains(m.Content, load(hc).BannedOutbound); w != "" {
return plugin.Veto{Reason: "the reply contains a banned word: " + w}
}
return nil
}
// OnInboundEvent implements plugin.InboundEventHook.
func (Plugin) OnInboundEvent(_ context.Context, hc plugin.HookContext, _ int64, ev *channel.InboundEvent) error {
if ev.Kind != channel.InboundMessage {
return nil
}
if w := contains(ev.Text+" "+ev.Subject, load(hc).SpamInbound); w != "" {
return plugin.Veto{Reason: "inbound message matched spam phrase: " + w}
}
return nil
}
// ContributeSettingsSections implements plugin.SettingsSectionContributor.
func (Plugin) ContributeSettingsSections() []plugin.SettingsSection {
return []plugin.SettingsSection{{
Key: Section, Title: "Word filter", Description: "Words that block an agent reply, and phrases that drop an inbound message as spam.",
Schema: json.RawMessage(`{"type":"object","properties":{
"banned_outbound":{"type":"array","title":"Banned words in replies","items":{"type":"string"}},
"spam_inbound":{"type":"array","title":"Spam phrases in inbound messages","items":{"type":"string"}}},"additionalProperties":false}`),
Default: json.RawMessage(`{"banned_outbound":[],"spam_inbound":[]}`),
}}
}
// ContributeAutomationActions implements plugin.AutomationActionContributor.
func (Plugin) ContributeAutomationActions() []plugin.AutomationAction {
return []plugin.AutomationAction{{
Key: "wordfilter.flag", Label: "Flag conversation with a note",
ParamsSchema: json.RawMessage(`{"type":"object","properties":{"note":{"type":"string","title":"Note text"}},"additionalProperties":false}`),
Run: func(ctx context.Context, _ plugin.HookContext, conversationID int64, params json.RawMessage, ops plugin.Ops) error {
var p struct {
Note string `json:"note"`
}
_ = json.Unmarshal(params, &p)
if p.Note == "" {
p.Note = "Flagged by the word filter."
}
if err := ops.AddTag(ctx, conversationID, "flagged"); err != nil {
return err
}
return ops.AddNote(ctx, conversationID, p.Note)
},
}}
}Three things to notice. Every hook receives a HookContext for one workspace, and reads its settings through it, so the lists are per workspace without the plugin doing anything. A veto is a returned error of type plugin.Veto whose reason is shown to the person; any other error is logged and the message goes on. The automation action gets an Ops value with the few writes an action may make (tags, notes), rather than database access.
Verify
- Settings › Marketplace › Installed → Word filter: add a word under "Banned words in replies" and save.
- Reply with that word in any conversation: the reply is blocked with the plugin's reason.
- Automation: a rule can use "Flag conversation with a note".
- Tests:
go test ./plugins/wordfilter.
Which should I use?
An extension is one thing you add to Kommio from Settings › Marketplace; what it can do is one or more of these.
- Sidebar panel Your own page beside the conversation, such as recent orders or a CRM record. Orders panel · reference
- Message hook Check, rewrite or block a reply before it is sent, or an incoming message before it is stored. Orders panel · reference
- Event subscription Signed events at your URL when conversations, messages or contacts change. Webhook receiver · reference
- Agent bot Your bot answers new conversations first and hands off to a person. Echo bot · reference
- Remote data lookup Fetch fields such as balance or VIP tier from your API into the contact and a sidebar panel; set up from the Marketplace, no code. Marketplace · reference
- Automation action A rule or macro step that calls your system, such as sending a webhook. Send a webhook · reference
- Rule or flow template Start an automation rule or a chatbot flow from a ready-made template in the Marketplace. Marketplace · reference
A core module compiled into a self-hosted server (this page) is part of Kommio, not an extension; what it lets a workspace set up appears under Marketplace › Installed. API sync example
Every claim on these pages is covered by a test in the repository, and the API reference is generated from the same contract every server serves at /api/openapi. Corrections to [email protected].