Skip to content

Commit

Permalink
[LLSC-20] Implement Skeleton Email Service (#6)
Browse files Browse the repository at this point in the history
## Notion ticket link
<!-- Please replace with your ticket's URL -->
[Setup Basic Email
System](https://www.notion.so/uwblueprintexecs/Setup-Basic-Email-System-11110f3fb1dc80a48e88ce63228b926c?pvs=4)


<!-- Give a quick summary of the implementation details, provide design
justifications if necessary -->
## Implementation description
Setup a quick skeleton for email service with interfaces and classes
defined. Will be implementing actual functionality in next pr. Created a
test endpoint to ensure that implementation of skeleton classes is done
properly just need to add actual logic.


<!-- What should the reviewer do to verify your changes? Describe
expected results and include screenshots when appropriate -->
## Steps to test
1. Can run backend properly 
2. Hit the new test email API. Can check fast API docs for
params/requirements

<!-- Draw attention to the substantial parts of your PR or anything
you'd like a second opinion on -->
## What should reviewers focus on?
* Interface definitions other functionalities that we may ned


## Checklist
- [x] My PR name is descriptive and in imperative tense
- [x] My commit messages are descriptive and in imperative tense. My
commits are atomic and trivial commits are squashed or fixup'd into
non-trivial commits
- [ ] I have run the appropriate linter(s)
- [x] I have requested a review from the PL, as well as other devs who
have background knowledge on this PR or who will be building on top of
this PR
  • Loading branch information
Mayank808 authored Nov 10, 2024
1 parent 78a092a commit 37e30a4
Show file tree
Hide file tree
Showing 15 changed files with 172 additions and 25 deletions.
Empty file.
File renamed without changes.
72 changes: 72 additions & 0 deletions backend/app/interfaces/email_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from abc import ABC, abstractmethod


class IEmailService(ABC):
"""
Interface for the Email Service, defining the core email operations such as
sending templated and custom emails.
"""

@abstractmethod
def send_email(self, to: str, subject: str, body: str) -> dict:
"""
Sends an email with the given parameters.
:param to: Recipient's email address
:type to: str
:param subject: Subject of the email
:type subject: str
:param body: HTML body content of the email
:type body: str
:return: Provider-specific metadata (like message ID, thread ID, label IDs)
:rtype: dict
:raises Exception: if email was not sent successfully
"""
pass

@abstractmethod
def send_welcome_email(self, recipient: str, user_name: str) -> dict:
"""
Sends a welcome email to the specified user.
:param recipient: Email address of the user
:type recipient: str
:param user_name: Name of the user
:type user_name: str
:return: Provider-specific metadata for the sent email
:rtype: dict
:raises Exception: if email was not sent successfully
"""
pass

@abstractmethod
def send_password_reset_email(self, recipient: str, reset_link: str) -> dict:
"""
Sends a password reset email with the provided reset link.
:param recipient: Email address of the user requesting the reset
:type recipient: str
:param reset_link: Password reset link
:type reset_link: str
:return: Provider-specific metadata for the sent email
:rtype: dict
:raises Exception: if email was not sent successfully
"""
pass

@abstractmethod
def send_notification_email(self, recipient: str, notification_text: str) -> dict:
"""
Sends a notification email to the user with the provided notification text.
Examples of use case include matches completed and ready to view, new messages,
meeting time scheduled, etc.
:param recipient: Email address of the user
:type recipient: str
:param notification_text: The notification content
:type notification_text: str
:return: Provider-specific metadata for the sent email
:rtype: dict
:raises Exception: if email was not sent successfully
"""
pass
30 changes: 30 additions & 0 deletions backend/app/interfaces/email_service_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from abc import ABC, abstractmethod


class IEmailServiceProvider(ABC):
"""
Interface for Email Providers that interact with external
email services (e.g., Amazon SES).
"""

@abstractmethod
def send_email(
self, recipient: str, subject: str, body_html: str, body_text: str
) -> dict:
"""
Sends an email using the provider's service.
:param recipient: Email address of the recipient
:type recipient: str
:param subject: Subject of the email
:type subject: str
:param body_html: HTML body content of the email
:type body_html: str
:param body_text: Plain text content of the email
:type body_text: str
:return: Provider-specific metadata related to the sent email
(like message ID, status, etc.)
:rtype: dict
:raises Exception: if the email fails to send
"""
pass
File renamed without changes.
File renamed without changes.
28 changes: 28 additions & 0 deletions backend/app/routes/email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from typing import Annotated

from fastapi import APIRouter, Depends

from app.interfaces.email_service import IEmailService
from app.services.email.email_service import EmailService
from app.services.email.email_service_provider import AmazonSESEmailProvider

router = APIRouter(
prefix="/email",
tags=["email"],
)


def get_email_service() -> IEmailService:
email_provider = AmazonSESEmailProvider(aws_access_key="", aws_secret_key="")
return EmailService(email_provider)


# TODO (Mayank, Nov 30th) - Remove test emails once email service is fully implemented
@router.post("/send-test-email/")
async def send_welcome_email(
recipient: str,
user_name: str,
email_service: Annotated[IEmailService, Depends(get_email_service)],
):
email_service.send_welcome_email(recipient, user_name)
return {"message": f"Welcome email sent to {user_name}!"}
4 changes: 4 additions & 0 deletions backend/app/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from dotenv import load_dotenv
from fastapi import FastAPI

from app.routes import email

from . import models

load_dotenv()
Expand All @@ -24,6 +26,8 @@ async def lifespan(_: FastAPI):
# running-alembic-migrations-on-fastapi-startup
app = FastAPI(lifespan=lifespan)

app.include_router(email.router)


@app.get("/")
def read_root():
Expand Down
Empty file.
20 changes: 20 additions & 0 deletions backend/app/services/email/email_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from app.interfaces.email_service import IEmailService
from app.interfaces.email_service_provider import IEmailServiceProvider


# TODO (Mayank, Nov 30th) - Implement the email service methods and use User object
class EmailService(IEmailService):
def __init__(self, provider: IEmailServiceProvider):
self.provider = provider

def send_email(self, to: str, subject: str, body: str) -> dict:
pass

def send_welcome_email(self, recipient: str, user_name: str) -> dict:
pass

def send_password_reset_email(self, recipient: str, reset_link: str) -> dict:
pass

def send_notification_email(self, recipient: str, notification_text: str) -> dict:
pass
12 changes: 12 additions & 0 deletions backend/app/services/email/email_service_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from app.interfaces.email_service_provider import IEmailServiceProvider


class AmazonSESEmailProvider(IEmailServiceProvider):
def __init__(self, aws_access_key: str, aws_secret_key: str):
pass

# TODO (Mayank, Nov 30th) - Create an email object to pass into this method
def send_email(
self, recipient: str, subject: str, body_html: str, body_text: str
) -> dict:
pass
6 changes: 6 additions & 0 deletions backend/app/services/email/email_templates/test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<html>
<body>
<h1>Welcome, {user_name}!</h1>
<p>We are glad to have you with us.</p>
</body>
</html>
25 changes: 0 additions & 25 deletions backend/app/services/interfaces/email_service.py

This file was deleted.

0 comments on commit 37e30a4

Please sign in to comment.