-
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #405 from OpenMined/tauquir/email-apis
Add server API endpoints for sending emails
- Loading branch information
Showing
5 changed files
with
99 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
EMAIL_SERVICE_API_URL = "https://api.resend.com/emails" | ||
FROM_EMAIl = "OpenMined SyftBox <noreply@syftbox.openmined.org>" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from typing import Union | ||
|
||
from pydantic import BaseModel, EmailStr, Field, NameEmail | ||
|
||
from .constants import FROM_EMAIl | ||
|
||
|
||
class SendEmailRequest(BaseModel): | ||
to: Union[EmailStr, NameEmail] | ||
subject: str | ||
html: str | ||
|
||
def json_for_request(self): | ||
return { | ||
"from": FROM_EMAIl, | ||
"to": [self.to], | ||
"subject": self.subject, | ||
"html": self.html, | ||
} | ||
|
||
|
||
class BatchSendEmailRequest(BaseModel): | ||
emails: list[SendEmailRequest] = Field(max_length=100) | ||
|
||
def json_for_request(self): | ||
return [email.json_for_request() for email in self.emails] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import httpx | ||
from fastapi import APIRouter, Depends | ||
from loguru import logger | ||
|
||
from syftbox.server.emails.models import BatchSendEmailRequest, SendEmailRequest | ||
from syftbox.server.settings import ServerSettings, get_server_settings | ||
|
||
from .constants import EMAIL_SERVICE_API_URL | ||
|
||
router = APIRouter(prefix="/emails", tags=["email"]) | ||
|
||
# TODO add some safety mechanisms to the below endpoints (rate limiting, authorization, etc) | ||
|
||
|
||
@router.post("/") | ||
async def send_email( | ||
email_request: SendEmailRequest, | ||
server_settings: ServerSettings = Depends(get_server_settings), | ||
) -> bool: | ||
if not server_settings.email_service_api_key: | ||
raise httpx.HTTPStatusError("Email service API key is not set", request=None, response=None) | ||
async with httpx.AsyncClient() as client: | ||
response = await client.post( | ||
EMAIL_SERVICE_API_URL, | ||
headers={ | ||
"Authorization": f"Bearer {server_settings.email_service_api_key}", | ||
"Content-Type": "application/json", | ||
}, | ||
json=email_request.json_for_request(), | ||
) | ||
if response.status_code == 200: | ||
sent_to = email_request.to if isinstance(email_request.to, str) else ", ".join(email_request.to) | ||
logger.info(f"Email sent successfully to {sent_to}") | ||
return True | ||
else: | ||
logger.error(f"Failed to send email: {response.text}") | ||
return False | ||
|
||
|
||
@router.post("/batch") | ||
async def send_batch_email( | ||
email_requests: BatchSendEmailRequest, | ||
server_settings: ServerSettings = Depends(get_server_settings), | ||
) -> bool: | ||
"""Trigger up to 100 batch emails at once. | ||
Instead of sending one email per HTTP request, we provide a batching endpoint that | ||
permits you to send up to 100 emails in a single API call. | ||
""" | ||
if not server_settings.email_service_api_key: | ||
raise httpx.HTTPStatusError("Email service API key is not set", request=None, response=None) | ||
async with httpx.AsyncClient() as client: | ||
response = await client.post( | ||
f"{EMAIL_SERVICE_API_URL}/batch", | ||
headers={ | ||
"Authorization": f"Bearer {server_settings.email_service_api_key}", | ||
"Content-Type": "application/json", | ||
}, | ||
json=email_requests.json_for_request(), | ||
) | ||
if response.status_code == 200: | ||
logger.info(f"{len(email_requests)} emails sent successfully") | ||
return True | ||
else: | ||
logger.error(f"Failed to send email: {response.text}") | ||
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters