Add Resend email endpoint and utility#45
Conversation
Introduce a POST /resend endpoint and backing utility to send emails via the Resend API. Adds app/api/resend/utils/send_email.py (httpx-based sender), a Pydantic EmailRequest model and handler in app/api/resend/resend.py, and imports the new send helper. Also fixes a small typo in the root data response, updates app metadata/description and bumps __version__ to 2.0.6. Note: RESEND_API_KEY must be set in the environment. Adds pydantic[email] to requirements.
There was a problem hiding this comment.
Pull request overview
Adds a new Resend-backed email-sending capability to the API by introducing a /resend POST handler and an httpx utility function, plus small metadata/version updates.
Changes:
- Added
send_email_resend()utility to send emails via the Resend API. - Added
POST /resendwith a Pydantic request model usingEmailStr. - Updated app metadata/version and added
pydantic[email]dependency.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| requirements.txt | Adds pydantic[email] to support EmailStr validation. |
| app/main.py | Updates FastAPI app description metadata. |
| app/api/resend/utils/send_email.py | Introduces an httpx-based Resend email sender helper. |
| app/api/resend/resend.py | Adds /resend POST endpoint + request schema; fixes a small response key typo. |
| app/init.py | Updates package docstring and bumps __version__ to 2.0.6. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try: | ||
| response = httpx.post(RESEND_API_URL, headers=headers, json=payload, timeout=10) | ||
| response.raise_for_status() | ||
| return response.json() | ||
| except Exception as e: | ||
| return {"error": str(e)} |
There was a problem hiding this comment.
The helper makes a new one-off httpx.post() call every time and catches all exceptions, returning str(e) (which can be noisy and may leak internal details). Consider using a shared httpx.Client (connection pooling) and handling httpx.HTTPStatusError / httpx.RequestError explicitly with a sanitized, structured error (or raise and let the API layer map it to an HTTP status).
There was a problem hiding this comment.
Applied in commit e1e12bd. Changes to send_email.py:
- Added a module-level
_client = httpx.Client(timeout=10)for connection pooling (replaces one-offhttpx.post()) - Replaced broad
except Exception as ewith explicithttpx.HTTPStatusError as exc(returns sanitized{"error": f"Upstream error: {exc.response.status_code}"}) andhttpx.RequestError(returns{"error": "Failed to reach the email service"})
| from app import __version__ | ||
| import os | ||
| from app.utils.make_meta import make_meta | ||
|
|
||
| from fastapi import APIRouter, Query, Path, Body, HTTPException | ||
|
|
||
| from app.utils.db import get_db_connection | ||
|
|
||
| from .utils.send_email import send_email_resend | ||
|
|
||
| router = APIRouter() | ||
| base_url = os.getenv("BASE_URL", "http://localhost:8000") |
There was a problem hiding this comment.
There are several unused imports/variables here (__version__, Query, Path, Body, HTTPException, get_db_connection, and base_url). Removing unused items will reduce confusion and avoid lint/type-check noise as this module evolves.
| @router.post("/resend", status_code=status.HTTP_202_ACCEPTED) | ||
| def send_email(request: EmailRequest): | ||
| """POST /resend endpoint to send email via Resend API.""" | ||
| if not RESEND_API_KEY: | ||
| meta = make_meta("error", "RESEND_API_KEY missing. Please set it in your .env file.") | ||
| return {"meta": meta} | ||
| result = send_email_resend( | ||
| to=request.to, | ||
| subject=request.subject, | ||
| html=request.html, | ||
| sender=request.sender | ||
| ) | ||
| if "error" in result: | ||
| meta = make_meta("error", result["error"]) | ||
| return {"meta": meta} | ||
| meta = make_meta("success", "Email sent successfully.") |
There was a problem hiding this comment.
@router.post(..., status_code=202) returns 202 even when the API key is missing or when Resend returns an error (the handler returns a body instead of signaling an HTTP error). Use HTTPException (e.g., 500 for missing server config, 502 for upstream failure) and consider returning 200/201 on success unless the send is actually queued in the background.
| @router.post("/resend", status_code=status.HTTP_202_ACCEPTED) | ||
| def send_email(request: EmailRequest): | ||
| """POST /resend endpoint to send email via Resend API.""" | ||
| if not RESEND_API_KEY: | ||
| meta = make_meta("error", "RESEND_API_KEY missing. Please set it in your .env file.") | ||
| return {"meta": meta} | ||
| result = send_email_resend( | ||
| to=request.to, | ||
| subject=request.subject, | ||
| html=request.html, | ||
| sender=request.sender | ||
| ) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Introduce a POST /resend endpoint and backing utility to send emails via the Resend API. Adds app/api/resend/utils/send_email.py (httpx-based sender), a Pydantic EmailRequest model and handler in app/api/resend/resend.py, and imports the new send helper. Also fixes a small typo in the root data response, updates app metadata/description and bumps version to 2.0.6. Note: RESEND_API_KEY must be set in the environment. Adds pydantic[email] to requirements.