Report templating with Jinja2 and MJML for SignalRoom. Use when creating new reports, modifying templates, debugging report rendering, or adding new notification channels.
reports/
āāā registry.py # Report definitions (name, templates, query)
āāā renderer.py # Jinja2 + MJML rendering
āāā runner.py # Execute reports (query ā render ā send)
āāā templates/ # .j2 (Slack/SMS) and .mjml (Email)
āāā queries/ # SQL files for report data
Create src/signalroom/reports/queries/{report_name}.sql:
-- Parameters available: :date, :start_date, :end_date
SELECT
:date AS report_date,
SUM(conversions) AS total_conversions,
SUM(revenue) AS total_revenue
FROM everflow.daily_stats
WHERE date = :date
Slack (templates/{report_name}.slack.j2):
*{{ title }}* ā {{ report_date }}
:chart_with_upwards_trend: *Performance Summary*
⢠Conversions: {{ "{:,}".format(total_conversions) }}
⢠Revenue: ${{ "{:,.2f}".format(total_revenue) }}
Email (templates/{report_name}.email.mjml):
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>
<h1>{{ title }}</h1>
<p>Conversions: {{ total_conversions }}</p>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
SMS (templates/{report_name}.sms.j2):
{{ title }}: {{ total_conversions }} conv, ${{ total_revenue }} rev
Add to src/signalroom/reports/registry.py:
REPORTS = {
"my_report": Report(
name="my_report",
title="My Report Title",
query_file="my_report.sql",
templates={
"slack": "my_report.slack.j2",
"email": "my_report.email.mjml",
"sms": "my_report.sms.j2",
},
),
}
python -c "
from signalroom.reports import run_report
print(run_report('daily_ccw', channel='slack'))
"
python -c "
from signalroom.reports import run_report
print(run_report('daily_ccw', params={'date': '2025-12-18'}))
"
python -c "
from signalroom.reports import run_report
run_report('daily_ccw', channel='slack', send=True)
"
python scripts/trigger_workflow.py --report daily_ccw -w
| Report | Channels | Description |
|---|---|---|
daily_ccw |
slack, email, sms | Daily CCW performance summary |
test_sync |
slack | Simple Everflow + Redtrack totals |
alert |
slack, email, sms | Error/warning/info alerts |
*bold* _italic_ ~strikethrough~
:emoji_name:
⢠bullet point
```code```
MJML compiles to responsive HTML. Use components:
<mj-section> ā row container<mj-column> ā column within section<mj-text> ā text content<mj-button> ā CTA button<mj-image> ā imagesKeep under 160 characters. No formatting.
{{ "{:,}".format(value) }} # 1,234,567
{{ "{:.2f}".format(value) }} # 1234.56
{{ "${:,.2f}".format(value) }} # $1,234.56
{{ "{:.1%}".format(value) }} # 12.3%
{% if value > 0 %}
:arrow_up: Up {{ value }}%
{% else %}
:arrow_down: Down {{ value|abs }}%
{% endif %}
{% for row in affiliates %}
⢠{{ row.name }}: {{ row.conversions }} conv
{% endfor %}
{{ report_date.strftime('%B %d, %Y') }} # December 19, 2025
{{ report_date.strftime('%m/%d') }} # 12/19
For quick alerts without full reports:
from signalroom.reports import render_alert
message = render_alert(
title="Pipeline Failed",
message="Everflow sync failed with timeout",
level="error" # error, warning, info
)
Check path in registry matches actual file in templates/
Run query directly:
python -c "
from signalroom.reports.runner import execute_query
print(execute_query('daily_ccw', {'date': '2025-12-18'}))
"
Test MJML syntax: https://mjml.io/try-it-live