Coverage for app / backend / src / couchers / email / __init__.py: 100%
27 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-03 06:18 +0000
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-03 06:18 +0000
1import logging
2from pathlib import Path
3from typing import Any
5import yaml
6from jinja2 import Environment, FileSystemLoader
7from sqlalchemy.orm.session import Session
9from couchers.config import config
10from couchers.jobs.enqueue import queue_job
11from couchers.metrics import emails_counter
12from couchers.proto.internal import jobs_pb2
14logger = logging.getLogger(__name__)
16loader = FileSystemLoader(Path(__file__).parent / ".." / ".." / ".." / "templates")
17env = Environment(loader=loader, trim_blocks=True)
20def _queue_email(
21 session: Session,
22 sender_name: str,
23 sender_email: str,
24 recipient: str,
25 subject: str,
26 plain: str,
27 html: str | None,
28 list_unsubscribe_header: str | None,
29 source_data: str | None,
30) -> None:
31 # Import here to avoid circular dependency
32 from couchers.jobs.handlers import send_email
34 payload = jobs_pb2.SendEmailPayload(
35 sender_name=sender_name,
36 sender_email=sender_email,
37 recipient=recipient,
38 subject=subject,
39 plain=plain,
40 html=html,
41 list_unsubscribe_header=list_unsubscribe_header,
42 source_data=source_data,
43 )
44 queue_job(
45 session,
46 job=send_email,
47 payload=payload,
48 priority=5,
49 )
52def queue_email(
53 session: Session,
54 sender_name: str,
55 sender_email: str,
56 recipient: str,
57 subject: str,
58 plain: str,
59 html: str | None,
60 list_unsubscribe_header: str | None = None,
61 source_data: str | None = None,
62) -> None:
63 """
64 This indirection is so that this can be easily mocked. Not sure how to do it better :(
65 """
66 _queue_email(
67 session=session,
68 sender_name=sender_name,
69 sender_email=sender_email,
70 recipient=recipient,
71 subject=subject,
72 plain=plain,
73 html=html,
74 list_unsubscribe_header=list_unsubscribe_header,
75 source_data=source_data,
76 )
79def enqueue_system_email(session: Session, recipient: str, template_name: str, template_args: dict[str, Any]) -> None:
80 source, _, _ = loader.get_source(env, f"system/{template_name}.md")
81 _, frontmatter_source, text_source = source.split("---", 2)
83 rendered_frontmatter = env.from_string(frontmatter_source).render(**template_args, plain=True, html=False)
84 frontmatter = yaml.load(rendered_frontmatter, Loader=yaml.FullLoader)
86 plain = env.from_string(text_source.strip()).render(
87 {**template_args, "frontmatter": frontmatter}, plain=True, html=False
88 )
90 queue_email(
91 session,
92 sender_name=config["NOTIFICATION_EMAIL_SENDER"],
93 sender_email=config["NOTIFICATION_EMAIL_ADDRESS"],
94 recipient=recipient,
95 subject=config["NOTIFICATION_PREFIX"] + frontmatter["subject"],
96 plain=plain,
97 html=None,
98 source_data=template_name,
99 )
101 emails_counter.inc()