Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""NX AI - FastAPI, Python, Postgres, tsvector"""
"""Python - FastAPI, Postgres, tsvector"""

# Current Version
__version__ = "2.0.5"
__version__ = "2.0.6"
35 changes: 31 additions & 4 deletions app/api/resend/resend.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
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")
Comment on lines 2 to 10
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Expand All @@ -21,6 +19,35 @@ def root() -> dict:
return {"meta": meta}
meta = make_meta("success", "Resend endpoint")
data = [
{"action,": f"send email"},
{"action": "send email"},
]
return {"meta": meta, "data": data}


# POST endpoint to send email
from fastapi import status
from pydantic import BaseModel, EmailStr

class EmailRequest(BaseModel):
to: EmailStr
subject: str
html: str
sender: EmailStr

@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
)
Comment on lines +37 to +48

This comment was marked as resolved.

if "error" in result:
meta = make_meta("error", result["error"])
return {"meta": meta}
meta = make_meta("success", "Email sent successfully.")
Comment on lines +37 to +52
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

return {"meta": meta, "data": result}
26 changes: 26 additions & 0 deletions app/api/resend/utils/send_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Utility to send email using Resend API
import httpx
import os

RESEND_API_URL = "https://api.resend.com/emails"

def send_email_resend(to: str, subject: str, html: str, sender: str) -> dict:
resend_api_key = os.getenv("RESEND_API_KEY")
if not resend_api_key:
return {"error": "Missing RESEND_API_KEY"}
headers = {
"Authorization": f"Bearer {resend_api_key}",
"Content-Type": "application/json"
}
payload = {
"from": sender,
"to": [to],
"subject": subject,
"html": html
}
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)}
Comment on lines +21 to +26
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in commit e1e12bd. Changes to send_email.py:

  • Added a module-level _client = httpx.Client(timeout=10) for connection pooling (replaces one-off httpx.post())
  • Replaced broad except Exception as e with explicit httpx.HTTPStatusError as exc (returns sanitized {"error": f"Upstream error: {exc.response.status_code}"}) and httpx.RequestError (returns {"error": "Failed to reach the email service"})

2 changes: 1 addition & 1 deletion app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

app = FastAPI(
title="Python",
description="FastAPI, Python, Postgres, tsvector",
description="FastAPI, Postgres, tsvector",
version=__version__,
)

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ python-dotenv>=1.0.0
psycopg2-binary>=2.9.0
python-multipart>=0.0.20
Faker>=25.2.0
pydantic[email]>=2.0.0
Loading