Pull email sending concerns out into a standalone service

This commit is contained in:
Campbell Alden 2026-07-31 18:15:33 +09:00
parent 7d535a2c31
commit c1a61730d5
4 changed files with 46 additions and 57 deletions

View file

@ -5,7 +5,7 @@
"level": "info"
}
"email": {
"api_key": "asdf1234",
"bird_api_key": "asdf1234",
"sender": "me@example.com"
}
}

View file

@ -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'])

View file

@ -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)

42
src/services/email.py Normal file
View file

@ -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)