From c1a61730d5a52c294d9f5a8ab9d0f9c7fac5963d Mon Sep 17 00:00:00 2001 From: Campbell Alden Date: Fri, 31 Jul 2026 18:15:33 +0900 Subject: [PATCH] Pull email sending concerns out into a standalone service --- config.example.json | 2 +- src/config/email.py | 6 ++--- src/notifications/email.py | 53 -------------------------------------- src/services/email.py | 42 ++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 57 deletions(-) delete mode 100644 src/notifications/email.py create mode 100644 src/services/email.py diff --git a/config.example.json b/config.example.json index 35afa20..28f85f3 100644 --- a/config.example.json +++ b/config.example.json @@ -5,7 +5,7 @@ "level": "info" } "email": { - "api_key": "asdf1234", + "bird_api_key": "asdf1234", "sender": "me@example.com" } } diff --git a/src/config/email.py b/src/config/email.py index 423465c..e1d2409 100644 --- a/src/config/email.py +++ b/src/config/email.py @@ -8,11 +8,11 @@ EmailAddress = str @dataclass class Email: - api_key: str + bird_api_key: str sender: EmailAddress @classmethod def from_dict(cls, config: dict[str, Any]) -> 'Email': - assert_key_of_type(config, 'api_key', str) + assert_key_of_type(config, 'bird_api_key', str) assert_key_of_type(config, 'sender', str) - return Email(api_key=config['api_key'], sender=config['sender']) + return Email(bird_api_key=config['bird_api_key'], sender=config['sender']) diff --git a/src/notifications/email.py b/src/notifications/email.py deleted file mode 100644 index 799eb0b..0000000 --- a/src/notifications/email.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import Any -from email.utils import formatdate, make_msgid -import abc -from email.message import EmailMessage - -from ..config.email import Email as EmailConfig -from .service import NotificationService -from .data import Notification - - -class EmailService(NotificationService): - def __init__(self, config: EmailConfig): - self._config = config - - def notify(self, user: Any, notification: Notification): - email = self._prepare_email(user.email, notification) - self.send_email(email) - - @abc.abstractmethod - def send_email(self, email: EmailMessage): - pass - - def _render_template_text(self, notification: Notification) -> str: - # TODO - return '' - - def _render_template_html(self, notification: Notification) -> str: - # TODO - return '' - - def _prepare_email(self, receiver: str, notification: Notification) -> EmailMessage: - msg = EmailMessage() - msg['From'] = self._config.sender - msg['To'] = receiver - msg['Subject'] = notification.title - - msg['Date'] = formatdate(localtime=True) - msg['Message-ID'] = make_msgid() - - msg.set_content(self._render_template_text(notification)) - msg.add_alternative(self._render_template_html(notification), subtype='html') - - return msg - - -class BirdEmailServiceImpl(EmailService): - def send_email(self, email: EmailMessage): - # TODO: Implement Bird Specific Email sending and Error handling - pass - - -def get_service(config: EmailConfig) -> EmailService: - return BirdEmailServiceImpl(config) diff --git a/src/services/email.py b/src/services/email.py new file mode 100644 index 0000000..d6c4f5c --- /dev/null +++ b/src/services/email.py @@ -0,0 +1,42 @@ +from dataclasses import dataclass, field +from email.utils import formatdate, make_msgid +import abc + +from ..config.email import Email as EmailConfig, EmailAddress + + +@dataclass +class EmailDTO: + # Who the email is from + sender: EmailAddress + # Who the email is to + to: EmailAddress + # The subject of the email + subject: str + # The textual content of the email + text: str + # HTML alternate content. (Optional: Textual content is required) + html: str | None = None + # A unique ID for identifying this Email + message_id: str = field(default_factory=make_msgid) + # The date that the email was sent + date: str = field(default_factory=lambda: formatdate(localtime=True)) + + +class EmailService: + @abc.abstractmethod + def send_email(self, email: EmailDTO): + pass + + +class BirdEmailServiceImpl(EmailService): + def __init__(self, config: EmailConfig): + self._config = config + + def send_email(self, email: EmailDTO): + # TODO: Implement Bird Specific Email sending and Error handling + pass + + +def get_service(config: EmailConfig) -> EmailService: + return BirdEmailServiceImpl(config)