How to send real-time in-app notifications from PostHog backend code. Use when integrating notifications into a new feature, wiring up a notification source (alerts, comments, approvals, pipelines, issues), or choosing the right target type and priority for a notification.
Scanned 9/1/2026
Install to Claude Code
npx -y skills add PostHog/posthog --skill sending-notifications --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Sending Notifications?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/posthog-sending-notifications-posthog)More formats (shields.io, HTML) on the badges page.
---
name: sending-notifications
description: 'How to send real-time in-app notifications from PostHog backend code. Use when integrating notifications into a new feature, wiring up a notification source (alerts, comments, approvals, pipelines, issues), or choosing the right target type and priority for a notification.'
---
# Sending real-time notifications
## When to use this
You're adding notification support to a PostHog feature — for example, notifying a user when they're mentioned in a comment, when an alert fires, or when an approval is requested.
## The facade API
All notification creation goes through a single function. Import from the facade, not from internal modules:
```python
from products.notifications.backend.facade.api import (
create_notification,
NotificationData,
NotificationType,
Priority,
TargetType,
)
```
Build a `NotificationData` and call `create_notification`:
```python
event = create_notification(
NotificationData(
team_id=team.id,
notification_type=NotificationType.ALERT_FIRING,
priority=Priority.CRITICAL,
title="Event ingestion latency > 30s",
body="Events are queuing up. Ingestion pipeline is degraded.",
target_type=TargetType.USER,
target_id=str(user.id),
resource_type="dashboard",
resource_id="42",
source_url="/dashboard/42",
)
)
```
Returns a `NotificationEvent` on success, or `None` if the feature flag is disabled, no recipients were resolved, or the team doesn't exist. Safe to call in any context.
## NotificationData fields
**Required:**
| Field | Type | Description |
| ------------------- | ------------------ | -------------------------------------------------------------------------- |
| `team_id` | `int` | Team context — used to look up the organization and check the feature flag |
| `notification_type` | `NotificationType` | Determines the icon in the UI |
| `title` | `str` | Notification headline (~100 chars recommended) |
| `body` | `str` | Longer description shown on expand. Can be empty string |
| `target_type` | `TargetType` | Who receives this: `user`, `team`, `organization`, or `role` |
| `target_id` | `str` | ID of the target (user ID, team ID, org UUID, or role UUID as string) |
**Optional:**
| Field | Type | Default | Description |
| --------------- | ---------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `resource_type` | `NotificationResourceType \| None` | `None` | Access-controlled types (e.g. `"dashboard"`) auto-filter recipients without viewer access |
| `resource_id` | `str` | `""` | ID of the resource for linking |
| `source_url` | `str` | `""` | Relative URL path (e.g. `/dashboard/42`), shown as link icon in UI |
| `priority` | `Priority` | `NORMAL` | `normal` = popover only; `critical` = popover + persistent toast |
| `archivable` | `bool` | `False` | Opt in to a per-recipient "archive" (dismiss) action that moves the notification to the recipient's Archived tab. When `False`, recipients can only mark it read/unread (the default pattern) |
| `resolver` | `RecipientsResolver \| None` | `None` | Custom recipient resolver. Default handles user/team/org/role targeting |
## Choosing parameters
### Notification type
| Type | When to use |
| -------------------- | ------------------------------------------------ |
| `comment_mention` | User was @mentioned in a comment or discussion |
| `alert_firing` | A monitoring alert threshold was breached |
| `approval_requested` | A change requires the user's approval |
| `approval_resolved` | An approval the user requested has been resolved |
| `pipeline_failure` | A data pipeline or batch export failed |
| `issue_assigned` | An error tracking issue was assigned to the user |
### Priority
**Be very careful with `critical`.** It triggers a persistent toast popup that overlays the user's screen and must be manually dismissed. This is intentionally intrusive — reserve it for genuine emergencies like outages, security alerts, or SLA breaches. Overusing `critical` will train users to ignore notifications entirely. When in doubt, use `normal`.
### Target type
| Target | `target_id` value | Recipients |
| -------------- | ----------------- | -------------------------------------- |
| `user` | User ID | Just that user |
| `team` | Team ID | All members of the team's organization |
| `organization` | Organization ID | All organization members |
| `role` | Role ID | All users with that RBAC role |
### Resource type and access control
When `resource_type` matches an access-controlled resource (dashboard, feature_flag, experiment, etc.), recipients without `viewer` access are automatically excluded. For notification-only types (`pipeline`, `approval`, `comment`), no AC filtering is applied.
## Delivery pipeline
```text
Django (create_notification)
→ Postgres (NotificationEvent row)
→ Kafka (notification_events topic, on transaction commit)
→ Go livestream service (Kafka consumer)
→ Redis SPUBLISH (sharded pub/sub, keyed by org ID)
→ SSE (/notifications endpoint)
→ Browser (popover + optional toast)
```
Kafka publish happens on `transaction.on_commit` — won't fire if the transaction rolls back.
## Adding a new notification type
1. Add enum value in `products/notifications/backend/facade/enums.py`
2. Add icon mapping in `frontend/src/lib/components/NotificationsMenu/notificationToasts.tsx` (`NOTIFICATION_TYPE_ICONS`) — the single icon source, read by `getNotificationIcon`, which only `NotificationRow` calls; the side panel gets the icon by rendering that row
3. Add a label + description entry in `frontend/src/lib/components/NotificationsMenu/NotificationRow.tsx` (`REALTIME_NOTIFICATION_TYPE_META`) — drives the per-type notification preferences UI
4. Run `python manage.py makemigrations notifications`
## Testing
Mock the feature flag in tests:
```python
from unittest.mock import patch
with patch("posthoganalytics.feature_enabled", side_effect=lambda flag, *a, **kw: flag == "real-time-notifications"):
event = create_notification(data)
```
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!