Skip to content

feat(auth): integrate Resend transactional mailer for password resets - #252

Open
resincode wants to merge 1 commit into
suiflex:mainfrom
resincode:feat/auth-resend-mailer
Open

resincode wants to merge 1 commit into
suiflex:mainfrom
resincode:feat/auth-resend-mailer

Conversation

@resincode

Copy link
Copy Markdown
Collaborator

Summary

  • Introduces an async MailerService using Resend REST API (https://api.resend.com/emails) via httpx.
  • Adds environment settings:
    • SUITEST_RESEND_API_KEY (optional: if omitted, mailer gracefully no-ops and keeps existing behavior).
    • SUITEST_MAIL_FROM (defaults to Suitest <sui@keton.id>).
  • Wires send_password_reset_email into UserManager.on_after_forgot_password in apps/api/src/suitest_api/auth/manager.py.
  • Updates .env.deploy.example, compose.deploy.yml, docs/ARCHITECTURE.md, and docs/ROADMAP.md.
  • Adds thorough unit tests with respx in apps/api/tests/test_mailer.py adhering to strict TDD and typing guidelines.

Verification

  • uv run ruff check apps/api passed.
  • uv run ruff format --check apps/api passed.
  • uv run mypy on affected modules passed.
  • uv run pytest apps/api/tests/test_mailer.py apps/api/tests/test_m1e_passwords.py (7 passed).
  • Successfully built container image locally (Dockerfile.api).

@resincode
resincode requested a review from a team October 8, 2026 01:56
@suiflex-bot suiflex-bot Bot added commit: feat Contains a feat commit · suiflex-bot maintainer Opened by a member of the suiflex/maintainers team · suiflex-bot area: api Changes under apps/api (FastAPI backend) · suiflex-bot area: docs Changes to Markdown documentation or docs-site/ · suiflex-bot cla: signed Contributor has signed the CLA · suiflex-bot labels Oct 8, 2026

@mulhamna mulhamna left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the PR! The core logic and test coverage look good, but there are a few important issues that need to be addressed before merging:

  1. Personal Information (PII) in tests: Real personal email address (rejaisme@gmail.com) is committed in unit tests instead of RFC 2606 example domains.
  2. Architecture diagram alignment: Box drawing border in docs/ARCHITECTURE.md got broken and misaligned.
  3. Default mail_from domain: Hardcoding keton.id as default will cause 403 errors on self-hosted setups unless overridden.
  4. Client lifecycle & timeout: Creating a new httpx.AsyncClient per call and blocking the request handler with a 10s timeout.
  5. HTML escaping: reset_url should be escaped before rendering in email template.

Please see the inline comments for details.

)

sent = await send_password_reset_email(
to_email="rejaisme@gmail.com",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please avoid using real personal email addresses in test cases. Use a standard RFC 2606 example address like user@example.com or alice@example.com instead of rejaisme@gmail.com. Also consider using https://suitest.local or example.com for the reset URL domain in tests.

Comment thread docs/ARCHITECTURE.md
▼ ▼
┌──────────────────────────────┐
│ apps/api (FastAPI) │
| `apps/api` (FastAPI) │

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The ASCII box-drawing border is misaligned here:

  1. Line 24 replaced the box-drawing character │ with an ASCII pipe | and added backticks inside the diagram.
  2. Line 28 (│ • Transactional Mailer (Resend)│) exceeds the 30-character box width, pushing the right border out of alignment.

Please adjust the width or shorten the label so the box-drawing border remains straight and consistent.


# Transactional Mailer (Resend API)
resend_api_key: str = Field(default="", repr=False)
mail_from: str = Field(default="Suitest <sui@keton.id>")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Defaulting to sui@keton.id will cause API errors (403 Forbidden) for self-hosted instances when users provide their own SUITEST_RESEND_API_KEY without verifying keton.id on Resend.

Consider using a generic placeholder like Suitest <noreply@suitest.local> or Resend's sandbox domain Suitest <onboarding@resend.dev> as the fallback default, and document in .env.deploy.example that users must configure their verified sender domain.


try:
async with httpx.AsyncClient(timeout=10.0) as client:
response = await client.post(RESEND_API_URL, json=payload, headers=headers)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Instantiating a new httpx.AsyncClient on every email send bypasses connection pooling. Additionally, with a 10-second timeout directly inside the synchronous password-reset request lifecycle, any upstream Resend latency or outage will stall the user's HTTP request.

Consider reducing the timeout (e.g. 5s) or offloading the delivery to a background task / reusable client.

<div class="card">
<h2>Reset your password</h2>
<p>We received a request to reset your password for your Suitest account. Click the button below to choose a new password:</p>
<p><a href="{reset_url}" class="button">Reset Password</a></p>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

reset_url is directly interpolated into the HTML template without escaping. Please wrap it with html.escape(reset_url) before rendering to follow defensive HTML templating practices.

@resincode

Copy link
Copy Markdown
Collaborator Author

Ah thanks for the catch,, lemme fix that

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: api Changes under apps/api (FastAPI backend) · suiflex-bot area: docs Changes to Markdown documentation or docs-site/ · suiflex-bot cla: signed Contributor has signed the CLA · suiflex-bot commit: feat Contains a feat commit · suiflex-bot maintainer Opened by a member of the suiflex/maintainers team · suiflex-bot

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants