notification-platform

por sentry

Guía para agregar notificaciones, renderizadores personalizados o nuevos proveedores a NotificationPlatform de Sentry. Úsalo cuando se te pida "agregar notificación", "nueva notificación",…

npx skills add https://github.com/getsentry/sentry --skill notification-platform

NotificationPlatform Guide

Sentry's NotificationPlatform is a provider-based system for sending notifications across Email, Slack, Discord, and MS Teams. You define data + template, register it, and the platform handles rendering and delivery per provider.

Glossary

ConceptRoleLocation
NotificationDataProtocol. Frozen dataclass carrying the payload for a single notification. Must declare a source class variable.types.py
NotificationTemplateAbstract class. Converts NotificationData into a NotificationRenderedTemplate. Registered per NotificationSource.types.py
NotificationRenderedTemplateDataclass. Provider-agnostic output: subject (str or text blocks), body (sections containing text blocks), actions, chart, footer (str or text blocks), optional email paths.types.py
NotificationProviderProtocol. Knows how to validate a target, pick a renderer, and send the final renderable (Email, Slack, etc.).provider.py
NotificationRendererProtocol. Converts a NotificationRenderedTemplate into a provider-specific renderable (HTML email, Slack blocks, etc.).renderer.py
NotificationTargetProtocol. Identifies the recipient: email address, channel ID, or DM user ID. Two concrete classes: GenericNotificationTarget (email) and IntegrationNotificationTarget (Slack/Discord/MSTeams).target.py
NotificationServiceEntry point. Orchestrates lookup, rendering, and delivery. Provides has_access(), notify_target(), notify_async(), notify_sync().service.py

All paths below are relative to src/sentry/notifications/platform/.

Step 1: Determine Your Operation

I want to...Go to
Add a new notification (most common)Steps 2-5
Add a custom renderer for an existing providerStep 6
Add an entirely new providerStep 7

After any operation, continue to Step 8 (Test) and Step 9 (Verify).

Step 2: Define the Notification Source

Every notification needs a unique NotificationSource enum value and must be mapped to a NotificationCategory. A NotificationSource should represent the domain or feature that a given notification belongs to.

For examples, load src/sentry/notifications/platform/types.py.

File: types.py

  1. Add the enum value under the appropriate category comment:
class NotificationSource(StrEnum):
    # MY_CATEGORY
    MY_NEW_SOURCE = "my-new-source"
  1. Add it to NOTIFICATION_SOURCE_MAP under the matching category key:
NOTIFICATION_SOURCE_MAP[NotificationCategory.MY_CATEGORY].append(
    NotificationSource.MY_NEW_SOURCE
)

If no existing NotificationCategory fits, add a new one to the NotificationCategory enum first, then create its entry in NOTIFICATION_SOURCE_MAP.

All NotificationCategory options are defined in the src/sentry/notifications/platform/types.py file.

Step 3: Create the Notification Data

The data class is a frozen dataclass implementing the NotificationData protocol. It carries everything the template needs to render.

File: templates/<your_notification>.py (new file)

from dataclasses import dataclass
from sentry.notifications.platform.types import NotificationData, NotificationSource

@dataclass(frozen=True)
class MyNotificationData(NotificationData):
    source = NotificationSource.MY_NEW_SOURCE  # class variable, not a field
    title: str
    detail_url: str

Rules:

  • source is a class variable (no type annotation), not a dataclass field
  • Use frozen=True for serialization safety
  • Only include fields needed by the template's render() method
  • Avoid Django model instances; use primitive types or simple dataclasses for async serialization

For full examples (DataExportSuccess, DataExportFailure), load references/data-and-templates.md.

Step 4: Create the Notification Template

The template converts your data into a provider-agnostic NotificationRenderedTemplate.

Same file as Step 3: templates/<your_notification>.py

from sentry.notifications.platform.registry import template_registry
from sentry.notifications.platform.types import (
    NotificationCategory,
    NotificationRenderedAction,
    NotificationRenderedTemplate,
    NotificationTemplate,
    ParagraphSection,
    PlainTextBlock,
)

@template_registry.register(MyNotificationData.source)
class MyNotificationTemplate(NotificationTemplate[MyNotificationData]):
    category = NotificationCategory.MY_CATEGORY
    example_data = MyNotificationData(
        title="Example title",
        detail_url="https://example.com",
    )

    def render(self, data: MyNotificationData) -> NotificationRenderedTemplate:
        return NotificationRenderedTemplate(
            subject=data.title,
            body=[
                ParagraphSection(blocks=[PlainTextBlock(text="Something happened.")])
            ],
            actions=[
                NotificationRenderedAction(label="View Details", link=data.detail_url)
            ],
        )

Available types:

A notification is composed of sections and text blocks. Text blocks can appear in sections (for the body), and also directly in the subject and footer fields (as list[NotificationTextBlock] instead of plain str).

Sections (portions of a message, used in body):

SectionDescription
ParagraphSectionA block of text with a line break before.
CodeSectionA code block with a line break before.
BlockQuoteSectionA quoted block of text, rendered as a blockquote.

Text blocks (parts of text — used inside sections, and also in subject and footer):

BlockDescription
PlainTextBlockUnformatted text.
BoldTextBlockBold text.
ItalicTextBlockItalic text.
CodeTextBlockInline code.
LinkTextBlockA hyperlink with text and url fields.

Refer to src/sentry/notifications/platform/types.py for the full definitions.

Register the import in templates/__init__.py:

from .my_notification import MyNotificationTemplate

This import is required so the @template_registry.register decorator executes at startup (via sentry/notifications/apps.py).

For the full rendered template field reference and more examples, load references/data-and-templates.md.

Step 5: Register Rollout and Send

Rollout registration

The platform uses a tiered rollout system. Each notification source must be added to the appropriate rollout option before it will be delivered.

Rollout options are configured externally in sentry-options-automator (not this repo). The option keys are:

Rollout stageOption key
Internal testingnotifications.platform-rollout.internal-testing
Sentry orgsnotifications.platform-rollout.is-sentry
Early adopternotifications.platform-rollout.early-adopter
General accessnotifications.platform-rollout.general-access

Each option is a Dict mapping source string to rollout rate (0.0-1.0). Example:

{"my-new-source": 1.0}

These options are registered in src/sentry/options/defaults.py (already done for the four stages above).

Sending pattern

from sentry.notifications.platform.service import NotificationService
from sentry.notifications.platform.target import GenericNotificationTarget
from sentry.notifications.platform.types import (
    NotificationProviderKey,
    NotificationTargetResourceType,
)

data = MyNotificationData(title="Export ready", detail_url="https://...")

# Guard with rollout check
if NotificationService.has_access(organization, data.source):
    service = NotificationService(data=data)
    target = GenericNotificationTarget(
        provider_key=NotificationProviderKey.EMAIL,
        resource_type=NotificationTargetResourceType.EMAIL,
        resource_id=user.email,
    )
    service.notify_async(targets=[target])

For target types, async/sync decisions, and strategy patterns, load references/targets-and-sending.md.

Step 6: Add a Custom Renderer

Custom renderers bypass the default template-to-renderable conversion for a specific provider + category combination. Use when the default section/block-based rendering is too limiting (e.g., interactive Slack buttons, rich card layouts).

When to use:

  • The notification needs provider-specific interactive elements (buttons with action IDs, rich text blocks)
  • The rendered output structure differs significantly from subject + body + actions
  • You need to render different data types differently within the same provider

How it works: Override get_renderer() on the provider to return your custom renderer class for the relevant category:

# In the provider class
@classmethod
def get_renderer(
    cls, *, data: NotificationData, category: NotificationCategory
) -> type[NotificationRenderer[MyRenderable]]:
    if category == NotificationCategory.MY_CATEGORY:
        return MyCustomRenderer
    return cls.default_renderer

File placement: {provider}/renderers/{name}.py (e.g., slack/renderers/seer.py)

For architecture details and the full Seer Slack renderer example, load references/custom-renderers.md.

Step 7: Add a New Provider

Adding a new provider requires implementing the NotificationProvider protocol, a default NotificationRenderer, and registering both. This should only be done when onboarding a new integration provider.

High-level steps:

  1. Create {provider_name}/provider.py with provider + default renderer classes
  2. Register with @provider_registry.register(NotificationProviderKey.MY_PROVIDER)
  3. Add NotificationProviderKey.MY_PROVIDER to the NotificationProviderKey enum in types.py
  4. Import the provider in sentry/notifications/apps.py
  5. Gate availability behind a feature flag in is_available()

For the full provider scaffold and protocol requirements, load references/provider-template.md.

Step 8: Test

Test directory: tests/sentry/notifications/platform/

Template test

class TestMyNotificationTemplate:
    def test_render(self):
        data = MyNotificationData(title="Test", detail_url="https://example.com")
        template = MyNotificationTemplate()
        rendered = template.render(data)

        assert rendered.subject == "Test"
        assert len(rendered.body) == 1
        assert len(rendered.actions) == 1
        assert rendered.actions[0].link == "https://example.com"

    def test_render_example(self):
        template = MyNotificationTemplate()
        rendered = template.render_example()
        assert rendered.subject  # Verify example_data produces valid output

Service integration test

from unittest.mock import patch
from sentry.notifications.platform.service import NotificationService

class TestMyNotificationService:
    @patch("sentry.notifications.platform.email.provider.EmailNotificationProvider.send")
    def test_notify_target(self, mock_send):
        data = MyNotificationData(title="Test", detail_url="https://example.com")
        service = NotificationService(data=data)
        target = GenericNotificationTarget(
            provider_key=NotificationProviderKey.EMAIL,
            resource_type=NotificationTargetResourceType.EMAIL,
            resource_id="user@example.com",
        )
        service.notify_target(target=target)
        assert mock_send.called

Custom renderer test

If you added a custom renderer, test that the provider dispatches to it:

def test_get_renderer_returns_custom():
    data = MySpecialData(source=NotificationSource.MY_SOURCE, ...)
    renderer = MyProvider.get_renderer(data=data, category=NotificationCategory.MY_CATEGORY)
    assert renderer is MyCustomRenderer

Step 9: Verify

Pre-flight checklist before submitting:

  • NotificationSource enum value added to types.py
  • Source added to NOTIFICATION_SOURCE_MAP under correct category
  • Data class is @dataclass(frozen=True) with source as class variable
  • Template registered with @template_registry.register(DataClass.source)
  • Template imported in templates/__init__.py
  • example_data on template produces valid output via render_example()
  • Rollout option value configured (or ticket filed for sentry-options-automator)
  • Sending code guarded with NotificationService.has_access()
  • Tests pass: pytest -svv --reuse-db tests/sentry/notifications/platform/
  • Pre-commit passes on all modified files

Más skills de sentry

generate-frontend-forms
sentry
Guía para crear formularios usando el nuevo sistema de formularios de Sentry. Úsalo al implementar formularios, campos de formulario, validación o funcionalidad de guardado automático.
official
architecture-review
sentry
Revisión de salud del código a nivel de equipo. Encuentra módulos monolíticos, fallos silenciosos, brechas de seguridad de tipos, vacíos en la cobertura de pruebas y problemas de compatibilidad con LLM.
official
linear-type-labeler
sentry
Clasifica los issues de Linear y aplica una etiqueta de Tipo de la taxonomía de etiquetas del espacio de trabajo de Sentry basándose en el contenido del título y la descripción de cada issue.
official
sentry-flutter-sdk
sentry
Configuración completa del SDK de Sentry para Flutter y Dart. Úsalo cuando te pidan "agregar Sentry a Flutter", "instalar sentry_flutter", "configurar Sentry en Dart" o configurar errores…
official
sentry-svelte-sdk
sentry
Configuración completa del SDK de Sentry para Svelte y SvelteKit. Úsalo cuando te pidan "añadir Sentry a Svelte", "añadir Sentry a SvelteKit", "instalar @sentry/sveltekit" o configurar…
official
vercel-react-best-practices
sentry
Directrices de optimización de rendimiento para React y Next.js de Vercel Engineering. Esta habilidad debe usarse al escribir, revisar o refactorizar React/Next.js…
official
sentry-tanstack-start-sdk
sentry
Configuración completa del SDK de Sentry para TanStack Start React. Úsalo cuando te pidan "agregar Sentry a TanStack Start", "instalar @sentry/tanstackstart-react" o configurar errores…
official
bump-size-limit
sentry
Aumenta los límites de tamaño en .size-limit.js cuando la verificación de CI de size-limit falla. Úsalo cuando el usuario mencione fallos en los límites de tamaño, comprobaciones de tamaño de paquete fallidas, tamaño de CI…
official