Notifications (Admin/Operator)
CoPilot can send a message when something happens — a new alert lands, an AI investigation finishes, an analyst is assigned a case. You choose what triggers it, who receives it, which channel carries it, and how the message is worded. This guide covers the notification routing engine as a whole, plus setup for each channel: Email (Resend), Microsoft Teams, Shuffle, and direct webhooks.Not to be confused with Notification Workflows. CoPilot has an older, separate per-customer feature that fires a single Shuffle workflow on alert creation, configured under Customers → (customer) → Notification Workflows. Both still work. See Overlap with Notification Workflows before enabling both.
The idea
A route is one rule. It answers four questions:
You can have as many routes as you like. Every route matching an event fires independently, so one alert can notify a customer’s Teams channel and a webhook into your automation platform and nothing else, depending on what you’ve configured.
Two kinds of route
This distinction matters more than any other setting. Customer routes deliver to the end customer. They live under Customers → (customer) → Notifications and carry that customer’s code. Use them for things the customer should know about: an alert was raised, an investigation concluded. Internal routes deliver to your SOC. They live under Notifications → Internal Routes in the main navigation, belong to no customer, and are admin-only. Use them for things your team should know about: who picked up which alert. Assignment notifications are internal by design. If you assign an ACME alert to an analyst, that notification reaches your team — never ACME’s channel. This isn’t configurable, and it isn’t meant to be: telling a customer which of your analysts is handling their incident is rarely intended.Which trigger do I want?
This is the single most common source of confusion, so it’s worth a section of its own. When an alert arrives and AI analysis is enabled for that customer, two separate events happen minutes apart:Triggers
Customer-facing
Internal
Both
This is the only trigger offered on both customer and internal routes, because analyst sign-off is legitimately both audiences’ business. It’s what lets you hold customer delivery until a human has checked the AI’s work — see Two audiences, two moments.
It counts as AI-written content, so a customer-facing review route is governed by the same Customers → (customer) → AI Report opt-in as the rest. An internal route is not: if the switch is off, the customer’s route is suppressed and your internal one still fires.
Assignment triggers only fire on an actual change. Re-saving the same assignee sends nothing. By default, assigning something to yourself also sends nothing — there’s a per-route Notify on self-assign option if your team wants the audit trail anyway.
Severity filtering
Minimum severity is inclusive: a route set to High fires on High and Critical, not Medium. For alert creation, severity comes from the Wazuh rule level:
Alerts from sources with no rule level default to
High. Office 365, CrowdStrike, Carbon Black, Huntress and similar integrations carry no Wazuh rule level, and they are deliberately loud by default rather than silently filtered out. Change it deployment-wide with DEFAULT_ALERT_SEVERITY in your .env:
The AI’s severity is a different number. An investigation assesses the finding, which often disagrees with the alert. Alert #14 in our own testing wasAssignment notifications are Informational — being assigned something isn’t a security severity. Set assignment routes to Informational and above, or they’ll never fire.Criticalby rule level while its investigation concludedMedium. An AI investigation completes route filters on the AI’s assessment, not the alert’s — so a route gated at High will drop investigations the AI graded Medium, even for a Critical alert. If that surprises you, set AI routes lower than you’d set alert-creation routes.
Channels
Only email can address a person. A webhook targets a fixed URL and Teams targets a fixed channel, so “notify whoever this was assigned to” requires the Resend channel.
Only email renders HTML. Markdown and HTML templates are both converted to a formatted email body, with a plain-text part alongside for clients that refuse HTML. On chat channels markdown is already the native format.
Shuffle is unavailable on internal routes. A Shuffle integration belongs to a specific customer, and an internal route has no customer — so the option isn’t offered there.
Worked examples
Complete recipes. The confusion usually comes from the combination of settings rather than any single field, so each of these shows every choice together.Email the customer a full AI report after every investigation
The setup most people are actually after when they ask “why isn’t the report being emailed?”
Also required: Customers → (customer) → AI Report must be enabled. It’s opt-in, and while it’s off every AI notification to that customer is suppressed and logged as
skipped.
The customer receives the complete write-up — timeline, IOC verdicts, recommended actions — with its tables rendered, in their brand colours.
To send just a short summary instead, use the AI investigation — customer summary template. Same route, different template.
Teams card for Critical alerts only
Leave the template empty. Teams renders its own card with a severity-coloured header, a facts table and an Open in CoPilot link; a custom template replaces the body but not the card structure.
Email an analyst when they’re handed an alert
Two things bite here. Assignment events are always Informational, so any higher floor means the route never fires. And this must be an internal route — assignment triggers aren’t offered on customer routes because they could never match.
Analysts need an email address on their CoPilot account. A missing one is recorded as
failed with the reason rather than silently falling back to anyone else.
Push everything into your own automation
Leave the template empty so CoPilot sends its structured JSON object. A template replaces the entire body with its rendered output, which is what you want for a provider-specific shape like Discord’s
{"content": …} — and not what you want when you’re parsing the payload.
Two audiences, two moments
Internal first, customer only after a human has checked it. This is the configuration the An AI report is reviewed trigger exists for:- Route A — internal, An AI investigation completes, Teams. Your team sees every investigation the moment it lands, unreviewed.
- Route B — customer, An AI report is reviewed, email, full-report template. The customer hears nothing until an analyst has signed off in AI Analyst → (report) → Review.
Setting up Email (Resend)
Resend is a transactional email service. Its free tier covers 1,000 emails per month.1. Create a Resend account and API key
- Sign up at resend.com
- Create an API key. A send-only (“Sending access”) key is sufficient and recommended — CoPilot never needs to manage your account.
- Verify a sending domain under Domains. Until you do, Resend only delivers to the email address that owns the account, which is fine for testing and useless in production.
2. Configure the connector
Add these to your.env and restart CoPilot:
A send-only key is expected to report success here even though CoPilot probes a management endpoint it can’t reach. Verification recognises restricted keys and treats them as valid. If you used a full-access key, verification additionally reports which sending domains are verified.
3. Create a route
Under Customers → (customer) → Notifications (or Notifications → Internal Routes for assignments):
Click Send test to confirm delivery before relying on it.
Watch the quota
The 1,000/month free tier is shared across your entire deployment — every customer’s routes draw from the same allowance. That’s roughly 33 emails per day for everyone combined. The route form shows current usage and warns as the month fills. Two things keep it under control:- Minimum severity. An alert-creation route set to Informational on a busy customer will exhaust the tier quickly. Start at High.
- Max emails per hour. A per-route ceiling, so one noisy route can’t drain the allowance for everybody. Throttled sends are recorded in the dispatch log with the reason.
Notifying the assignee
Set Deliver to → Whoever it’s assigned to. At delivery time CoPilot looks up the assignee’s CoPilot account and uses the email address on it. This means analysts must have a valid email on their user account. If one is missing, the notification is recorded as failed with the reason — it won’t silently vanish, and it won’t fall back to the static list.Setting up Microsoft Teams
1. Create a webhook in Teams
Microsoft retired the old Office 365 connectors in 2026. The current mechanism is the Workflows app:- In Teams, go to the channel you want notifications in
- More options (…) next to the channel → Workflows
- Choose the Send webhook alerts to a channel template
- Configure and save, then copy the webhook URL
2. Create a route
Choose Microsoft Teams as the channel and paste the URL. That’s the only field. Then click Send test — and actually look at Teams.
Why that matters here specifically. If the payload shape is wrong, Teams responds 200 OK and displays nothing. A successful-looking test with no visible card means something is wrong. A correct test produces a card with a coloured header and a working Open in CoPilot link.
What the card looks like
The header is coloured by severity — red for Critical and High, amber for Medium, green for Low — which makes a busy channel scannable. Below it, a facts table (customer, entity, assignee) and the message body.Limits worth knowing
- 28 KB per message. Teams rejects anything larger. A long AI report is truncated with a note saying so, and the deep link is preserved so the full detail is one click away.
- ~4 requests per second. Above that Teams throttles and CoPilot records the notification as failed with a rate-limit message. This is transient — it means retry later, not that anything is misconfigured.
Setting up Shuffle
Shuffle routes deliver through a customer’s own authenticated Shuffle org, giving access to its app catalogue (Slack, Outlook, Jira, and several thousand more).- Add a Shuffle integration for the customer — Customers → (customer) → Notifications → Shuffle integrations
- Create a route with Shuffle as the channel
- Pick the integration, then the app within it
- Set a destination hint — a channel name, email address or handle. This is prepended to the outgoing message so Shuffle’s app agent knows where to deliver.
Setting up a webhook
The generic option: an HTTPS POST to any endpoint.
By default CoPilot sends a structured JSON object:
{"content": …} or Slack’s {"text": …}. If you’re parsing the payload programmatically, leave the template empty.
Message templates
Every route sends a sensible default message. Templates let you change the wording, the structure, and — on email — the formatting. There are two ways to set one, and they don’t behave the same.Named templates (recommended)
Notifications → Message Templates in the main navigation. Write a template once, attach it to as many routes as you like, edit it in one place. A named template carries:Inline templates (per-route override)
The Custom message body field on an individual route. Use it when one route needs to deviate without forking a shared template.Precedence
Inline → named → channel default. An inline body always wins; if there’s none, the route’s named template is used; if there’s neither, you get the channel’s built-in wording.Writing a template
Templates are Jinja — conditionals, loops and filters all work:{{ alert_id }}, {{ alert_name }} and {{ report_url }} are kept as aliases so anything written before templates became Jinja still works.
A misspelled variable is an error, not an empty string.{{ summry }}doesn’t render blank — the whole template fails and the route falls back to the channel default, with the reason recorded on the dispatch-log row. The editor validates when you save, and the live preview shows exactly what will be sent. The one exception iscontext.…, where a missing key does render empty. The same trigger legitimately carries different keys on different events — one alert has an asset name, the next doesn’t — so guard with{% if context.asset_name %}rather than assuming it’s there.
Including the AI investigation report
{{ context.ai_report }} gives a template the full investigation:
Always guard it — most alerts have no investigation:
ai_report, so templates that don’t reference it cost nothing.
Built-in templates
Seven ship with every deployment, as working starting points and as worked examples of the syntax:
Built-ins are read-only — the next startup would recreate them anyway. Use Duplicate to get an editable copy.
Sending something manually
Sometimes you want to push one specific alert or case to a channel — outside the triggers and severity filters entirely. Open an alert or case and choose Send to channel….
This sends a real notification: it consumes provider quota and is recorded in the dispatch log with
trigger_source = manual. Sending twice sends twice — deliberately, since clicking send twice on purpose should work.
The customer AI opt-out still applies. If Customers → (customer) → AI Report is off, including the report is refused rather than quietly dropped.
Testing and troubleshooting
Send test
Every saved route has a Send test button. It sends a real notification through the real path — so it consumes quota and appears in the dispatch log, exactly like a live one. That’s deliberate: a test that took a different path wouldn’t prove much.The dispatch log
Customers → (customer) → Notifications → Dispatch log records every attempt:skipped is not an error. It’s CoPilot recording a decision it made, which is usually what you want to see when asking “why didn’t I get an email”.
A row also carries a template error when a custom template failed and the channel default went out in its place — so a broken template is visible without having to reproduce it.
Nothing arrived
Work down this list:- Is there a route for that trigger at all? The most common cause by far. An alert is created route does not fire when an AI investigation completes — see Which trigger do I want?. Check the backend log for
no route is configured for this trigger. - Is the route enabled?
- Is the severity high enough? Assignment routes must be set to Informational and above. AI routes filter on the AI’s assessment, which is often lower than the alert’s.
- Right scope? Assignment notifications only reach internal routes. A customer route with an assignment trigger can never fire — which is why the UI doesn’t offer that combination.
- Check the dispatch log. A
failedrow carries the provider’s own message. Askippedrow says which rule applied. No row at all means no route matched — go back to step 1. - Is the connector verified? Connectors → Resend → Verify.
AI report notifications aren’t arriving for one customer
There’s a separate switch: Customers → (customer) → AI Report. It controls whether AI-written findings may reach that customer at all, and it’s opt-in — a customer who has never been enabled is disabled. When it’s off, AI notifications to that customer’s routes are suppressed and logged asskipped. Internal routes are unaffected, so you can keep running investigations while keeping results in-house.
The email arrived but the formatting is wrong
- Asterisks showing as literal
*text*— the template’s format istext. Change it tomarkdownand email will render it. Chat channels treat markdown natively either way. - Tables showing as rows of
|characters — same cause. Markdown or HTML format renders them properly on email; chat channels can’t render tables at all. - HTML tags visible in the message — an
htmltemplate sent to a chat channel. Only email renders HTML.
The template previewed fine but the real message is the default
Almost always acontext.… variable that exists on the sample event and not on the real one. The preview uses a fully-populated sample; real events are sparser. Guard every context.… reference with {% if %}.
Overlap with Notification Workflows
CoPilot has an older per-customer feature — Customers → (customer) → Notification Workflows — that fires a single Shuffle workflow when an alert is created. That still works and is untouched. But it fires on the same event as an alert is created route, so a customer with both configured gets two notifications per alert. Both are opt-in, so this only happens if you set up both. The route form warns you when it applies. If you see that warning, disable whichever you don’t want.Reference
Trigger → scope
Channel capabilities
Environment variables
SHUFFLER_API_KEY.
