notifications
Skill0 starsChanged 32 days agoMITView on GitHub
Designing and implementing a multi-channel notification system: routes notifications through email, push, SMS, and in-app channels based on user preferences, renders templates with user data, tracks delivery status, deduplicates within time windows to prevent spam, respects opt-outs, and sends through the background job queue so the request path is never blocked. Use when you want to send notifications, implement email notifications, add push notifications, build an in-app notification system, manage notification preferences, track delivery status, prevent notification spam, or route alerts through multiple channels.
9 other projects use a version of this.
Adopted by
Most recently taken up by
- soilmass/motion-design-agenttheir own version, first seen Nov 8, 2026
- Happy-Technologies-LLC/happy-platform-skillstheir own version, first seen Nov 1, 2026
- hyperlink-academy/leaflettheir own version, first seen Oct 8, 2026
- latitude-dev/latitude-llmtheir own version, first seen Oct 8, 2026
- rlajous/claude-code-commandstheir own version, first seen Aug 28, 2026
- vellum-ai/vellum-assistanttheir own version, first seen Jul 8, 2026
Used with
Projects with it also use review.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
SentrySkillreview
Fixture review skill.
Also in 3 projects that use this
Other versions9 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 9
- latitude-dev/latitude-llm★ 4.7ksame name
- vellum-ai/vellum-assistant★ 1.5ksame name
- hyperlink-academy/leaflet★ 527same name
- Happy-Technologies-LLC/happy-platform-skills★ 41same name
- rlajous/claude-code-commands★ 30same name
- soilmass/motion-design-agent★ 7same name
- moasq/ios-dev-agent★ 4same name
- montagao/skills★ 2same name
- hkarpinen/agent-skills★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository68 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .claude/CLAUDE.mdCLAUDE.md
- accessibilitySkill
- api-designSkill
- api-versioningSkill
- authSkill
- background-jobsSkill
- cachingSkill
- capacity-planningSkill
- changelogSkill
- chaos-engineeringSkill
- ci-cdSkill
- circuit-breakerSkill
- claude-best-practicesSkill
- code-commentsSkill
- code-hygieneSkill
- code-reviewSkill
- code-structureSkill
- commit-styleSkill
- concurrency-and-lockingSkill
- database-transactionsSkill
- data-consistencySkill
- debug-infra-startupSkill
- debug-sessionSkill
- demo-video-creatorSkill
- demo-video-script-creatorSkill
- dependency-injectionSkill
- dependency-updateSkill
- disk-cleanupSkill
- distributed-tracingSkill
- dockerSkill
- env-configSkill
- env-setupSkill
- error-handlingSkill
- event-driven-architectureSkill
- feature-flagsSkill
- file-storageSkill
- graceful-shutdownSkill
- idempotencySkill
- input-validationSkill
- jars-engineering-blogsmithSkill
- jars-workshop-builderSkill
- licensingSkill
- loggingSkill
- memory-and-allocationsSkill
- migrate-dbSkill
- monitoringSkill
- multi-tenancySkill
- openapiSkill
- paginationSkill
- perf-profileSkill
- pr-descriptionSkill
- query-optimizationSkill
- rate-limitingSkill
- refactorSkill
- releaseSkill
- runbook-writerSkill
- searchSkill
- secrets-auditSkill
- simplifySkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
2 rules. 0 are common, 2 are distinctive, and 0 are very specific to this project.
0
Common
Most of the 8 edited versions we read keep these.
2
Distinctive
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 342
Never call a notification channel directly from business logic — always via the service.
- Distinctive: line 343
Always send notifications via the background queue — never block the request path.
“Common” is measured: kept by at least 6 in 10 of the 8 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
notifications
Decouple notification delivery from business logic. Business logic fires an event — the notification system decides channel, template, and timing.
1. The architecture
Business logic
│ fires NotificationRequest
▼
NotificationService ← checks preferences, deduplicates, rate-limits
│
├── EmailChannel ← renders template, sends via provider
├── PushChannel ← sends to FCM / APNs
├── SMSChannel ← sends via Twilio / SNS
└── InAppChannel ← writes to notifications tableBusiness logic never calls a channel directly. It calls NotificationService.send().
2. Notification request — typed, not stringly typed
# lib/notifications.py
"""
lib/notifications.py
---------------------
Notification request types and the central dispatch service.
Depends on: services/channels/, repositories/notification_repository.py
Used by: services/* (fire and forget via background queue)
"""
from dataclasses import dataclass
from enum import Enum
from typing import Any
class Channel(str, Enum):
EMAIL = "email"
PUSH = "push"
SMS = "sms"
IN_APP = "in_app"
class NotificationType(str, Enum):
ORDER_CONFIRMED = "order.confirmed"
PAYMENT_FAILED = "payment.failed"
PASSWORD_RESET = "password.reset"
WEEKLY_DIGEST = "weekly.digest"
SECURITY_ALERT = "security.alert"
@dataclass(frozen=True)
class NotificationRequest:
notification_type: NotificationType
user_id: int
tenant_id: str
data: dict[str, Any] # template variables
channels: list[Channel] # preferred channels — service may override
idempotency_key: str # prevents duplicates on retry3. Notification service — preferences, dedup, dispatch
# services/notification_service.py
"""
services/notification_service.py
----------------------------------
Orchestrates notification delivery: checks user preferences, deduplicates
within a time window, and dispatches to the right channels.
Depends on:
- repositories/notification_repository.py
- services/channels/email_channel.py
- services/channels/push_channel.py
- services/channels/in_app_channel.py
- lib/notifications.py
Used by: workers/notification_worker.py
"""
from lib.notifications import Channel, NotificationRequest, NotificationType
from repositories.notification_repository import NotificationRepository
from services.channels.email_channel import EmailChannel
from services.channels.push_channel import PushChannel
from services.channels.in_app_channel import InAppChannel
from lib.logger import get_logger
logger = get_logger(__name__)
# Dedup window per notification type — prevents spam on retries
DEDUP_WINDOW_SECONDS: dict[NotificationType, int] = {
NotificationType.ORDER_CONFIRMED: 300, # 5 min — safe to re-send after
NotificationType.PAYMENT_FAILED: 3600, # 1 hour
NotificationType.WEEKLY_DIGEST: 86400, # 1 day
NotificationType.SECURITY_ALERT: 60, # 1 min — urgent, allow frequent
NotificationType.PASSWORD_RESET: 300,
}
class NotificationService:
def __init__(self) -> None:
self._repo = NotificationRepository()
self._channels = {
Channel.EMAIL: EmailChannel(),
Channel.PUSH: PushChannel(),
Channel.IN_APP: InAppChannel(),
}
async def send(self, req: NotificationRequest) -> None:
# 1. Idempotency — already sent?
if await self._repo.is_duplicate(req.idempotency_key):
logger.info(
"Duplicate notification skipped. key=%s type=%s user_id=%s",
req.idempotency_key, req.notification_type, req.user_id,
)
return
# 2. Deduplication within time window
dedup_window = DEDUP_WINDOW_SECONDS.get(req.notification_type, 300)
if await self._repo.sent_recently(req.user_id, req.notification_type, dedup_window):
logger.info(
"Notification suppressed — sent recently. type=%s user_id=%s window_s=%d",
req.notification_type, req.user_id, dedup_window,
)
return
# 3. Load user preferences and opt-outs
prefs = await self._repo.get_preferences(req.user_id)
active_channels = [
ch for ch in req.channels
if prefs.is_enabled(req.notification_type, ch)
]
if not active_channels:
logger.info(
"No active channels for notification. type=%s user_id=%s",
req.notification_type, req.user_id,
)
return
# 4. Dispatch to each active channel
for channel in active_channels:
handler = self._channels.get(channel)
if not handler:
logger.warning("Unknown channel, skipping. channel=%s", channel)
continue
try:
await handler.send(req)
await self._repo.record_sent(req.idempotency_key, req.user_id,
req.notification_type, channel)
logger.info(
"Notification sent. type=%s user_id=%s channel=%s",
req.notification_type, req.user_id, channel,
)
except Exception as exc:
logger.error(
"Notification delivery failed. type=%s user_id=%s channel=%s error=%s",
req.notification_type, req.user_id, channel, exc,
)
# Continue — try remaining channels even if one fails4. Email channel — template rendering
# services/channels/email_channel.py
"""
services/channels/email_channel.py
------------------------------------
Renders email templates and sends via the configured provider.
Depends on: lib/email_provider.py, templates/email/
Used by: services/notification_service.py
"""
from jinja2 import Environment, FileSystemLoader
from lib.email_provider import email_provider
from lib.logger import get_logger
from lib.notifications import NotificationRequest, NotificationType
logger = get_logger(__name__)
_jinja = Environment(
loader=FileSystemLoader("templates/email"),
autoescape=True, # always escape — prevents XSS in email HTML
)
TEMPLATES: dict[NotificationType, tuple[str, str]] = {
NotificationType.ORDER_CONFIRMED: ("order_confirmed.html", "Your order is confirmed"),
NotificationType.PAYMENT_FAILED: ("payment_failed.html", "Action required: payment failed"),
NotificationType.WEEKLY_DIGEST: ("weekly_digest.html", "Your weekly summary"),
NotificationType.SECURITY_ALERT: ("security_alert.html", "Security alert on your account"),
NotificationType.PASSWORD_RESET: ("password_reset.html", "Reset your password"),
}
class EmailChannel:
async def send(self, req: NotificationRequest) -> None:
template_file, default_subject = TEMPLATES.get(
req.notification_type, (None, None)
)
if not template_file:
raise ValueError(f"No email template for {req.notification_type}")
html_body = _jinja.get_template(template_file).render(**req.data)
subject = req.data.get("subject", default_subject)
to_email = req.data["user_email"]
await email_provider.send(
to=to_email,
subject=subject,
html=html_body,
)
logger.info(
"Email sent. type=%s to=%s subject=%s",
req.notification_type, to_email, subject,
)5. In-app notifications — database table
-- migration: in-app notifications
CREATE TABLE notifications (
id BIGSERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
tenant_id TEXT NOT NULL,
notification_type TEXT NOT NULL,
title TEXT NOT NULL,
body TEXT NOT NULL,
data JSONB,
read_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_notifications_user_unread
ON notifications (user_id, created_at DESC)
WHERE read_at IS NULL;# services/channels/in_app_channel.py
from lib.notifications import NotificationRequest
IN_APP_TEMPLATES: dict = {
"order.confirmed": {"title": "Order confirmed", "body": "Your order #{order_id} is confirmed."},
"payment.failed": {"title": "Payment failed", "body": "Your payment for order #{order_id} failed."},
"security.alert": {"title": "Security alert", "body": "A new login was detected from {location}."},
}
class InAppChannel:
async def send(self, req: NotificationRequest) -> None:
tmpl = IN_APP_TEMPLATES.get(req.notification_type)
if not tmpl:
return
title = tmpl["title"]
body = tmpl["body"].format(**req.data)
await db.execute(
"""
INSERT INTO notifications (user_id, tenant_id, notification_type, title, body, data)
VALUES (:user_id, :tenant_id, :type, :title, :body, :data)
""",
{"user_id": req.user_id, "tenant_id": req.tenant_id,
"type": req.notification_type, "title": title,
"body": body, "data": req.data},
)6. User preferences schema
CREATE TABLE notification_preferences (
user_id INTEGER NOT NULL REFERENCES users(id),
notification_type TEXT NOT NULL,
channel TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
PRIMARY KEY (user_id, notification_type, channel)
);@dataclass
class UserPreferences:
settings: dict # {(notification_type, channel): bool}
def is_enabled(self, notification_type, channel) -> bool:
# Default: enabled unless explicitly opted out
return self.settings.get((notification_type, channel), True)7. Fire from business logic — always via queue
# In any service — fire and forget via the background queue
from lib.notifications import NotificationRequest, NotificationType, Channel
from lib.queue import enqueue
async def place_order(user_id: int, order_id: int) -> Order:
order = await order_repo.create(user_id, order_id)
# Enqueue notification — never await directly in business logic
await enqueue("notification", NotificationRequest(
notification_type = NotificationType.ORDER_CONFIRMED,
user_id = user_id,
tenant_id = get_tenant(),
data = {
"order_id": order.id,
"total": order.total_formatted,
"user_email": order.user_email,
"user_name": order.user_name,
},
channels = [Channel.EMAIL, Channel.IN_APP],
idempotency_key = f"order.confirmed:{order.id}",
))
return orderRules
- Never call a notification channel directly from business logic — always via the service.
- Always send notifications via the background queue — never block the request path.
- Deduplicate within a time window per notification type — what that window is depends on urgency.
- Respect opt-outs before sending — check preferences before dispatching to any channel.
- Template rendering must use autoescape — email HTML with unescaped user data is an XSS vector.
- Derive the idempotency key from the business event, not a random UUID — so retries produce the same notification, not a duplicate.
- Log every send with type, user_id, and channel — notifications are the one thing users will complain about when they go missing or arrive twice.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

