Flesh out the email implementation

This commit is contained in:
Campbell Alden 2026-08-01 01:07:58 +09:00
parent c1a61730d5
commit cee708f4d8
4 changed files with 29 additions and 10 deletions

View file

@ -1,3 +1,4 @@
from src.utils.secret import SecretBox
from src.config.parse import assert_key_of_type from src.config.parse import assert_key_of_type
from typing import Any from typing import Any
from dataclasses import dataclass from dataclasses import dataclass
@ -8,11 +9,11 @@ EmailAddress = str
@dataclass @dataclass
class Email: class Email:
bird_api_key: str bird_api_key: SecretBox[str]
sender: EmailAddress sender: EmailAddress
@classmethod @classmethod
def from_dict(cls, config: dict[str, Any]) -> 'Email': def from_dict(cls, config: dict[str, Any]) -> 'Email':
assert_key_of_type(config, 'bird_api_key', str) assert_key_of_type(config, 'bird_api_key', str)
assert_key_of_type(config, 'sender', str) assert_key_of_type(config, 'sender', str)
return Email(bird_api_key=config['bird_api_key'], sender=config['sender']) return Email(bird_api_key=SecretBox(config['bird_api_key']), sender=config['sender'])

View file

@ -1,5 +1,5 @@
from dataclasses import dataclass, field import requests
from email.utils import formatdate, make_msgid from dataclasses import dataclass
import abc import abc
from ..config.email import Email as EmailConfig, EmailAddress from ..config.email import Email as EmailConfig, EmailAddress
@ -17,10 +17,6 @@ class EmailDTO:
text: str text: str
# HTML alternate content. (Optional: Textual content is required) # HTML alternate content. (Optional: Textual content is required)
html: str | None = None 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: class EmailService:
@ -30,12 +26,20 @@ class EmailService:
class BirdEmailServiceImpl(EmailService): class BirdEmailServiceImpl(EmailService):
BIRD_API_ENDPOINT = 'https://eu1.platform.bird.com/v1/email/messages'
def __init__(self, config: EmailConfig): def __init__(self, config: EmailConfig):
self._config = config self._config = config
def send_email(self, email: EmailDTO): def send_email(self, email: EmailDTO):
# TODO: Implement Bird Specific Email sending and Error handling payload = {'from': email.sender, 'to': email.to, 'subject': email.subject, 'text': email.text}
pass
if email.html:
payload['html'] = email.html
headers = {'Authorization': f'Bearer {self._config.bird_api_key.expose_secret()}'}
response = requests.post(BirdEmailServiceImpl.BIRD_API_ENDPOINT, json=payload, headers=headers)
return response.ok
def get_service(config: EmailConfig) -> EmailService: def get_service(config: EmailConfig) -> EmailService:

0
src/utils/__init__.py Normal file
View file

14
src/utils/secret.py Normal file
View file

@ -0,0 +1,14 @@
class SecretBox[T]:
"""A helper abstraction for making sure secrets aren't leaked accidentally"""
def __init__(self, item: T):
self._item = item
def expose_secret(self) -> T:
return self._item
def __str__(self):
return '<SECRET>'
def __repr__(self):
return f'SecretBox<{type(self._item)}>'