app.bettersight.io/backend/services/brief_service.py

60 lines
2.4 KiB
Python

import os
import logging
from jinja2 import Environment, FileSystemLoader, select_autoescape
from repositories.brief_repository import brief_repository
logger = logging.getLogger(__name__)
RESEND_API_KEY = os.getenv('RESEND_API_KEY')
TEMPLATES_DIR = os.path.join(os.path.dirname(__file__), '..', 'templates', 'emails')
# Plain Jinja2 (not flask.render_template) so this service has no
# dependency on an active Flask request/app context — it must also run
# from the n8n-triggered Flask route AND, in principle, from an RQ
# worker process. Autoescaping is mandatory: brief content includes
# scraped third-party trip names which must never be interpreted as
# HTML in the rendered email.
_jinja_env = Environment(
loader=FileSystemLoader(TEMPLATES_DIR),
autoescape=select_autoescape(['html'])
)
def send_weekly_brief(tenant_id):
"""
Renders the weekly intelligence brief as branded HTML and sends it
via Resend to the tenant admin email.
Data flow:
tenant_id → brief_repository.get_weekly_data() (price_history,
new products, battlecard updates for the past 7 days) →
brief.html Jinja2 template rendered → Resend API →
digest_logs record created (sent or failed)
"""
data = brief_repository.get_weekly_data(tenant_id)
admin_email = data.get('admin_email')
if not admin_email:
logger.error(f'No admin_email on file for tenant {tenant_id} — skipping weekly brief')
return
template = _jinja_env.get_template('brief.html')
html = template.render(**data)
try:
# Imported lazily so this module is importable without the
# `resend` package configured in test environments.
import resend
resend.api_key = RESEND_API_KEY
resend.Emails.send({
'from': 'Bettersight <brief@bettersight.io>',
'to': admin_email,
'subject': f"Bettersight Weekly Brief — Week of {data['week_of']}",
'html': html,
})
brief_repository.log_digest(tenant_id, 'weekly', recipient_email=admin_email, status='sent')
except Exception as e:
# A failed email send is a side-effect failure — it must not
# raise into the n8n workflow that triggered this run.
logger.error(f'Weekly brief send failed for tenant {tenant_id}: {str(e)}')
brief_repository.log_digest(tenant_id, 'weekly', recipient_email=admin_email, status='failed')