Skip to content

Commit

Permalink
Update E-Mail Delivery System to Queueing
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnGrubba committed Nov 10, 2024
1 parent c46b769 commit b6aa6af
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 16 deletions.
5 changes: 2 additions & 3 deletions src/api/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
SessionConfig,
r,
SecurityConfig,
send_email,
queue_email,
all_ids,
regenerate_ids,
SignupConfig,
Expand Down Expand Up @@ -75,8 +75,7 @@ async def forgot_password(
}
),
)
background_tasks.add_task(
send_email,
queue_email(
"ChangePassword",
user["email"],
code=unique_id,
Expand Down
5 changes: 2 additions & 3 deletions src/api/signup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Response, BackgroundTasks, HTTPException, Request
from api.model import UserSignupRequest, LoginResponse, ConfirmEmailCodeRequest
from tools import send_email
from tools import queue_email
import json
from tools import SignupConfig, SessionConfig
from tools import all_ids, regenerate_ids, r
Expand Down Expand Up @@ -66,8 +66,7 @@ async def signup(
)

# Generate and send confirmation email
background_tasks.add_task(
send_email,
queue_email(
"ConfirmEmail",
signup_form.email,
code=unique_id,
Expand Down
8 changes: 3 additions & 5 deletions src/crud/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from tools import (
users_collection,
SignupConfig,
send_email,
queue_email,
InternalConfig,
insecure_cols,
AccountFeaturesConfig,
Expand Down Expand Up @@ -155,15 +155,13 @@ def update_public_user(
# Get a unique ID for confirmation email
unique_id = all_ids.pop()
# Generate and send confirmation email
background_tasks.add_task(
send_email,
queue_email(
"ConfirmEmail",
data["email"],
**existing_user,
code=unique_id,
time=SignupConfig.conf_code_expiry,
)

r.setex(
"emailchange:" + str(existing_user["_id"]),
SignupConfig.conf_code_expiry * 60,
Expand Down Expand Up @@ -335,7 +333,7 @@ def create_user(
# User Created (Create Session Token and send Welcome Email)
session_token = sessions.create_login_session(user_db.inserted_id, request)
if SignupConfig.enable_welcome_email:
background_tasks.add_task(send_email, "WelcomeEmail", data["email"], **data)
queue_email("WelcomeEmail", data["email"], **data)
return session_token


Expand Down
4 changes: 2 additions & 2 deletions src/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
EmailConfig,
SignupConfig,
)
from .mail import send_email, broadcast_emails
from .mail import broadcast_emails, queue_email
from .confirmation_codes import all_ids, regenerate_ids

__all__ = [
Expand All @@ -30,7 +30,7 @@
"AccountFeaturesConfig",
"insecure_cols",
"SecurityConfig",
"send_email",
"queue_email",
"broadcast_emails",
"all_ids",
"regenerate_ids",
Expand Down
37 changes: 34 additions & 3 deletions src/tools/mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,31 @@
from threading import Lock
from tools import users_collection, InternalConfig
import importlib
import re
import threading
import queue

smtp = smtplib.SMTP_SSL(EmailConfig.smtp_host, EmailConfig.smtp_port)
smtp = smtplib.SMTP_SSL(EmailConfig.smtp_host, EmailConfig.smtp_port, timeout=1)
email_queue = queue.Queue()


def queue_email(template_name: str, to: str, **kwargs):
email_queue.put_nowait(lambda: send_email(template_name, to, **kwargs))


def fill_placeholders(template: str, data: dict) -> str:
pattern = re.compile(r"\{\{\s*(\w+)\s*\}\}")
result = pattern.sub(
lambda match: str(data.get(match.group(1), match.group(0))), template
)
return result


def load_template(template_name: str, **kwargs) -> str:
with open(f"/src/app/config/email/{template_name}.html", "r") as file:
template = file.read()
if template.find("<title>") == -1:
raise ValueError("Template does not contain a title tag")
subject = template[template.find("<title>") + 7 : template.find("</title>")]
# Try to find template_name + ".py" and execute the function "preprocess"
try:
Expand All @@ -29,7 +47,7 @@ def load_template(template_name: str, **kwargs) -> str:
logger.error(
f"Failed to execute preprocess function in {template_name}.py: {e}"
)
formatted_template = template.format(**kwargs)
formatted_template = fill_placeholders(template, kwargs)
return formatted_template, subject


Expand All @@ -48,7 +66,6 @@ def send_email(template_name: str, to: str, **kwargs):
# Send email
smtp.login(EmailConfig.login_usr, EmailConfig.login_pwd)
smtp.send_message(msg)
smtp.quit()

logger.info(f"Email sent to {to} with subject: {subject}")

Expand All @@ -72,3 +89,17 @@ def broadcast_emails(
raise e
finally:
email_task_lock.release_lock()


def email_worker():
logger.info("\u001b[32m- E-Mail Worker started\u001b[0m")
while True:
email_task = email_queue.get()
try:
email_task()
except Exception as e:
logger.error(f"Failed to send email: {e}")
email_queue.task_done()


threading.Thread(target=email_worker).start()

0 comments on commit b6aa6af

Please sign in to comment.