mirror of
https://github.com/grillazz/fastapi-sqlalchemy-asyncpg.git
synced 2025-08-26 16:40:40 +03:00
Merge pull request #192 from grillazz/171-simple-and-fast-smtp-client
add endpoint to send email with smtp service
This commit is contained in:
commit
76a816d4a7
@ -1,16 +1,102 @@
|
||||
import logging
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, status, Request
|
||||
from fastapi import APIRouter, status, Request, Depends, Query
|
||||
from pydantic import EmailStr
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
|
||||
from app.services.smtp import SMTPEmailService
|
||||
|
||||
from app.utils.logging import AppLogger
|
||||
|
||||
logger = AppLogger().get_logger()
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/redis", status_code=status.HTTP_200_OK)
|
||||
async def redis_check(request: Request):
|
||||
_redis = await request.app.redis
|
||||
_info = None
|
||||
"""
|
||||
Endpoint to check Redis health and retrieve server information.
|
||||
|
||||
This endpoint connects to the Redis client configured in the application
|
||||
and attempts to fetch server information using the `info()` method.
|
||||
If an error occurs during the Redis operation, it logs the error.
|
||||
|
||||
Args:
|
||||
request (Request): The incoming HTTP request.
|
||||
|
||||
Returns:
|
||||
dict or None: Returns Redis server information as a dictionary if successful,
|
||||
otherwise returns `None` in case of an error.
|
||||
"""
|
||||
redis_client = await request.app.redis
|
||||
redis_info = None
|
||||
try:
|
||||
_info = await _redis.info()
|
||||
redis_info = await redis_client.info()
|
||||
except Exception as e:
|
||||
logging.error(f"Redis error: {e}")
|
||||
return _info
|
||||
return redis_info
|
||||
|
||||
|
||||
@router.post("/email", status_code=status.HTTP_200_OK)
|
||||
async def smtp_check(
|
||||
request: Request,
|
||||
smtp: Annotated[SMTPEmailService, Depends()],
|
||||
sender: Annotated[EmailStr, Query(description="Email address of the sender")],
|
||||
recipients: Annotated[
|
||||
list[EmailStr], Query(description="List of recipient email addresses")
|
||||
],
|
||||
subject: Annotated[str, Query(description="Subject line of the email")],
|
||||
body_text: Annotated[str, Query(description="Body text of the email")] = "",
|
||||
):
|
||||
"""
|
||||
Endpoint to send an email via an SMTP service.
|
||||
|
||||
This endpoint facilitates sending an email using the configured SMTP service. It performs
|
||||
the operation in a separate thread using `run_in_threadpool`, which is suitable for blocking I/O
|
||||
operations, such as sending emails. By offloading the sending process to a thread pool, it prevents
|
||||
the asynchronous event loop from being blocked, ensuring that other tasks in the application
|
||||
remain responsive.
|
||||
|
||||
Args:
|
||||
request (Request): The incoming HTTP request, providing context such as the base URL.
|
||||
smtp (SMTPEmailService): The SMTP email service dependency injected to send emails.
|
||||
sender (EmailStr): The sender's email address.
|
||||
recipients (list[EmailStr]): A list of recipient email addresses.
|
||||
subject (str): The subject line of the email.
|
||||
body_text (str, optional): The plain-text body of the email. Defaults to an empty string.
|
||||
|
||||
Returns:
|
||||
dict: A JSON object indicating success with a message, e.g., {"message": "Email sent"}.
|
||||
|
||||
Logs:
|
||||
Logs relevant email metadata: request base URL, sender, recipients, and subject.
|
||||
|
||||
Why `run_in_threadpool`:
|
||||
Sending an email often involves interacting with external SMTP servers, which can be
|
||||
a slow, blocking operation. Using `run_in_threadpool` is beneficial because:
|
||||
1. Blocking I/O operations like SMTP requests do not interrupt the main event loop,
|
||||
preventing other tasks (e.g., handling HTTP requests) from slowing down.
|
||||
2. The email-sending logic is offloaded to a separate, managed thread pool, improving
|
||||
application performance and scalability.
|
||||
"""
|
||||
|
||||
email_data = {
|
||||
"base_url": request.base_url,
|
||||
"sender": sender,
|
||||
"recipients": recipients,
|
||||
"subject": subject,
|
||||
}
|
||||
|
||||
logger.info("Sending email with data: %s", email_data)
|
||||
|
||||
await run_in_threadpool(
|
||||
smtp.send_email,
|
||||
sender=sender,
|
||||
recipients=recipients,
|
||||
subject=subject,
|
||||
body_text=body_text,
|
||||
body_html=None,
|
||||
)
|
||||
return {"message": "Email sent"}
|
||||
|
@ -56,9 +56,11 @@ class SMTPEmailService(metaclass=SingletonMetaNoArgs):
|
||||
and logs in using the provided credentials.
|
||||
"""
|
||||
self.server = smtplib.SMTP(self.server_host, self.server_port)
|
||||
self.server.starttls() # Upgrade the connection to secure TLS
|
||||
self.server.starttls() # Upgrade the connection to secure TLS
|
||||
self.server.login(self.username, self.password)
|
||||
logger.info("SMTPEmailService initialized successfully and connected to SMTP server.")
|
||||
logger.info(
|
||||
"SMTPEmailService initialized successfully and connected to SMTP server."
|
||||
)
|
||||
|
||||
def _prepare_email(
|
||||
self,
|
||||
@ -141,7 +143,7 @@ class SMTPEmailService(metaclass=SingletonMetaNoArgs):
|
||||
Args:
|
||||
recipients (list[EmailStr]): A list of recipient email addresses.
|
||||
subject (str): The subject line of the email.
|
||||
template (str): The name of the template file in the templates directory.
|
||||
template (str): The name of the template file in the templates' directory.
|
||||
context (dict): A dictionary of values to render the template with.
|
||||
sender (EmailStr): The email address of the sender.
|
||||
|
||||
@ -151,9 +153,13 @@ class SMTPEmailService(metaclass=SingletonMetaNoArgs):
|
||||
"""
|
||||
try:
|
||||
template_str = self.templates.get_template(template)
|
||||
body_html = template_str.render(context) # Render the HTML using context variables
|
||||
body_html = template_str.render(
|
||||
context
|
||||
) # Render the HTML using context variables
|
||||
self.send_email(sender, recipients, subject, body_html=body_html)
|
||||
logger.info(f"Template email sent successfully to {recipients} using template {template}.")
|
||||
logger.info(
|
||||
f"Template email sent successfully to {recipients} using template {template}."
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Failed to send template email", exc_info=e)
|
||||
raise
|
||||
raise
|
||||
|
Loading…
x
Reference in New Issue
Block a user