Coverage for src/lexigram/admin/services/notifications/sender.py: 0%
16 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 23:18 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 23:18 +0800
1"""Email sending utilities for admin notifications."""
3from __future__ import annotations
5from typing import TYPE_CHECKING
7from lexigram.contracts.mailer import EmailMessage
9if TYPE_CHECKING:
10 from lexigram.admin.services.notifications.models import NotificationRecipient
11 from lexigram.contracts.mailer.protocols import MailerProtocol
14class EmailSender:
15 """Handles sending of notification emails via a MailerProtocol backend."""
17 def __init__(
18 self,
19 mailer: MailerProtocol | None = None,
20 from_email: str = "admin@example.com",
21 from_name: str = "Admin System",
22 ):
23 """Initialize email sender.
25 Args:
26 mailer: MailerProtocol backend for email delivery; raises
27 RuntimeError in send_email() when None.
28 from_email: Default from email address.
29 from_name: Default from name.
30 """
31 self.mailer = mailer
32 self.from_email = from_email
33 self.from_name = from_name
35 async def send_email(
36 self,
37 recipient: NotificationRecipient,
38 subject: str,
39 body: str,
40 html_body: str | None = None,
41 ) -> None:
42 """Send email to recipient.
44 Args:
45 recipient: Email recipient.
46 subject: Email subject.
47 body: Plain text body.
48 html_body: Optional HTML body.
50 Raises:
51 RuntimeError: If email sending fails.
52 """
53 if self.mailer:
54 message = EmailMessage(
55 to=[recipient.email],
56 subject=subject,
57 body=body,
58 html_body=html_body,
59 from_email=self.from_email,
60 from_name=self.from_name,
61 )
62 result = await self.mailer.send(message)
63 if result.is_err():
64 raise RuntimeError(str(result.unwrap_err()))
65 else:
66 raise RuntimeError(
67 "No mailer backend is configured. Register a MailerProtocol "
68 "(e.g. lexigram-notification MailerModule with driver "
69 "'smtp'/'sendgrid', or 'console' for development logging) so "
70 f"emails like '{subject}' can be delivered to {recipient.email}."
71 )
74__all__ = ["EmailSender"]