Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.12.9
3.13.8
4 changes: 3 additions & 1 deletion app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ class Settings(BaseSettings):
frozen=True,
)
# APP
RUN_ENV: Literal["local", "develop", "staging", "production"] = "local"
RUN_ENV: Literal["local", "develop", "staging", "production", "test"] = (
"local"
)
PROCESS_TYPE: Literal["api", "worker", "beat"]
API_V1_STR: str = "/api/v1"
SECRET_KEY: str = secrets.token_urlsafe(32)
Expand Down
52 changes: 52 additions & 0 deletions app/emails/services/email_templates_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from __future__ import annotations

from enum import Enum
from typing import Any, ClassVar, Literal, TypedDict, Unpack, overload

from jinja2 import Environment, FileSystemLoader


class EmailTemplate(Enum):
WELCOME = "welcome.j2"
OTHER = "_base.j2"


class _WelcomeKwargs(TypedDict):
name: str


class _OtherKwargs(TypedDict):
pass


class EmailTemplatesService:
_instance: ClassVar[EmailTemplatesService | None] = None
_environment: ClassVar[Environment]

def __new__(cls) -> EmailTemplatesService:
if cls._instance:
return cls._instance

cls._environment = Environment(
loader=FileSystemLoader("app/emails/templates"),
)

cls._instance = super().__new__(cls)
return cls._instance

@overload
def render(
self,
template: Literal[EmailTemplate.WELCOME],
**kw: Unpack[_WelcomeKwargs],
) -> str: ...
@overload
def render(
self,
template: Literal[EmailTemplate.OTHER],
**kw: Unpack[_OtherKwargs],
) -> str: ...

def render(self, template: EmailTemplate, **kw: Any) -> str:
jinja_template = self._environment.get_template(template.value)
return jinja_template.render(**kw)
67 changes: 26 additions & 41 deletions app/emails/services/emails_service.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,36 @@
from enum import Enum
from string import Template

from app.core.config import settings
from app.users.schemas.user_schema import UserInDB

from app.emails._global_state import get_client
from app.emails.clients.base import BaseEmailClient
from app.emails.schema.email import Email, EmailContext
from app.emails._global_state import get_client


class Paths(Enum):
NEW_USER = "app/emails/templates/welcome_email.html"
from app.emails.services.email_templates_service import (
EmailTemplatesService,
EmailTemplate,
)


class EmailService:
def __init__(self, email_client: BaseEmailClient | None = None):
self.email_client = email_client or get_client()

def _get_email(
self,
recipient_email: str,
template: str,
subject: str,
html_message_input: dict | None = None,
) -> Email:
with open(template, "r") as file:
html_template_string = file.read()

html_message_input = html_message_input or {}
html = Template(html_template_string).substitute(**html_message_input)

return Email(
to_emails=[recipient_email],
subject=subject,
html=html,
)
self.template_service = EmailTemplatesService()

def send_new_user_email(
self,
user: UserInDB,
) -> None:
email = self._get_email(
user.email,
Paths.NEW_USER.value,
"Welcome",
)

email.context = EmailContext(
max_retries=settings.SEND_WELCOME_EMAIL_MAX_RETRIES,
backoff_in_seconds=settings.SEND_WELCOME_EMAIL_RETRY_BACKOFF_VALUE,
error_message=f"Sending new user email to user {user.id} failed",
email = Email(
to_emails=[user.email],
subject="Welcome",
html=self.template_service.render(
EmailTemplate.WELCOME,
name=user.email,
),
context=EmailContext(
max_retries=settings.SEND_WELCOME_EMAIL_MAX_RETRIES,
backoff_in_seconds=settings.SEND_WELCOME_EMAIL_RETRY_BACKOFF_VALUE,
error_message=f"Sending new user email to user {user.id} failed",
),
)

self.email_client.send_email(email)
Expand All @@ -57,10 +39,13 @@ def send_user_remind_email(
self,
user: UserInDB,
) -> None:
email = self._get_email(
user.email,
Paths.NEW_USER.value,
"Welcome",
email = Email(
to_emails=[user.email],
subject="Welcome",
html=self.template_service.render(
EmailTemplate.WELCOME,
name=user.email,
),
)

self.email_client.send_email(email)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Email Template</title>
<title>
{% block title %}{% endblock title %}
</title>
<style>
@media screen and (max-width: 599px) {
.mobile-hidden {
Expand Down Expand Up @@ -31,6 +33,6 @@
justify-content: center;
"
>
<h1>Welcome</h1>
{% block body %}{% endblock body %}
</body>
</html>
9 changes: 9 additions & 0 deletions app/emails/templates/welcome.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% extends "_base.j2" %}

{% block title %}
Welcome
{% endblock title %}

{% block body %}
<h1>Welcome {{ name }}</h1>
{% endblock body %}
Loading