Skip to content

ENG-3301: Add correspondence template and reply-to utility (Story 3)#8123

Open
JadeCara wants to merge 8 commits into
ENG-3301/messaging-provider-refactor-story2from
ENG-3301/messaging-provider-refactor-story3
Open

ENG-3301: Add correspondence template and reply-to utility (Story 3)#8123
JadeCara wants to merge 8 commits into
ENG-3301/messaging-provider-refactor-story2from
ENG-3301/messaging-provider-refactor-story3

Conversation

@JadeCara
Copy link
Copy Markdown
Contributor

@JadeCara JadeCara commented May 6, 2026

Ticket ENG-3301

Description Of Changes

Adds the correspondence email template and reply-to address utilities for data subject correspondence threading. These utilities have no callers yet — they will be wired by ENG-3299 (CorrespondenceService). This is the final PR in the ENG-3301 messaging provider refactor series.

Code Changes

  • New src/fides/api/email_templates/templates/correspondence.html — HTML template with subject and body variables
  • Registered CORRESPONDENCE template name and MessagingActionType.CORRESPONDENCE mapping in get_email_template()
  • New src/fides/api/service/messaging/messaging_providers/reply_to_utils.py:
    • generate_reply_to_token() — 128-bit cryptographic token (32 hex chars)
    • format_reply_to_address() — plus-addressing or dedicated subdomain format
  • New tests for template rendering and reply-to utilities

Steps to Confirm

1. Server starts cleanly

docker logs fides 2>&1 | grep -i traceback

Confirm no import errors from the new template registration or reply-to utility module.

2. Template loads and renders

Open a Python shell inside the fides container and verify the template is registered and produces valid HTML:

docker exec fides python3 -c "
from fides.api.email_templates.get_email_template import get_email_template
from fides.api.schemas.messaging.messaging import MessagingActionType

template = get_email_template(MessagingActionType.CORRESPONDENCE)
rendered = template.render(subject='Test Subject', body='<p>Hello</p>')
assert 'Test Subject' in rendered
assert '<p>Hello</p>' in rendered
print('Template renders correctly')
print(rendered)
"

Verify the output is valid HTML with the subject in <title> and the body in <main>.

3. Reply-to token generation

Verify token format and uniqueness:

docker exec fides python3 -c "
from fides.api.service.messaging.messaging_providers.reply_to_utils import (
    generate_reply_to_token, format_reply_to_address
)

token = generate_reply_to_token()
assert len(token) == 32, f'Expected 32 chars, got {len(token)}'
assert all(c in '0123456789abcdef' for c in token), 'Not valid hex'
print(f'Token: {token}')

# Plus addressing (default)
addr = format_reply_to_address(token, 'example.com')
assert addr == f'reply+{token}@replies.example.com'
print(f'Plus addressing: {addr}')

# Dedicated subdomain
addr2 = format_reply_to_address(token, 'example.com', use_plus_addressing=False)
assert addr2 == f'{token}@replies.example.com'
print(f'Subdomain: {addr2}')

print('All assertions passed')
"

4. No callers yet (automated coverage)

These utilities are infrastructure for ENG-3299. No production code calls them — verify with:

grep -rn "generate_reply_to_token\|format_reply_to_address" src/ --include="*.py" | grep -v reply_to_utils.py | grep -v __pycache__

Should return no results (only test files import them).

Pre-Merge Checklist

  • Issue requirements met
  • All CI pipelines succeeded
  • CHANGELOG.md updated
    • Updates unreleased work already in Changelog, no new entry necessary
  • UX feedback:
    • No UX review needed
  • Followup issues:
    • Followup issues created
  • Database migrations:
    • No migrations
  • Documentation:
    • No documentation updates required

Stacked on #8122 — merge that first. #8120 and #8118 are already merged.

🤖 Generated with Claude Code

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
fides-plus-nightly Ignored Ignored Preview May 11, 2026 10:59pm
fides-privacy-center Ignored Ignored May 11, 2026 10:59pm

Request Review

@JadeCara JadeCara force-pushed the ENG-3301/messaging-provider-refactor-story2 branch from 916b01c to 64bee10 Compare May 6, 2026 18:15
JadeCara added a commit that referenced this pull request May 6, 2026
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@JadeCara JadeCara force-pushed the ENG-3301/messaging-provider-refactor-story3 branch from 5411f9c to 1c12125 Compare May 6, 2026 18:17
@JadeCara JadeCara force-pushed the ENG-3301/messaging-provider-refactor-story2 branch from 64bee10 to f07ce63 Compare May 6, 2026 18:59
JadeCara and others added 2 commits May 6, 2026 13:07
Add correspondence.html email template registered with
MessagingActionType.CORRESPONDENCE. Add reply-to utilities:
generate_reply_to_token() (128-bit cryptographic token) and
format_reply_to_address() (plus-addressing or subdomain format).

These utilities have no callers yet — they will be wired in by
ENG-3299 (CorrespondenceService).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@JadeCara JadeCara force-pushed the ENG-3301/messaging-provider-refactor-story3 branch from 1c12125 to cc22e16 Compare May 6, 2026 19:11
@codecov
Copy link
Copy Markdown

codecov Bot commented May 11, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.43%. Comparing base (bb052c2) to head (32a0258).

Additional details and impacted files
@@                               Coverage Diff                               @@
##           ENG-3301/messaging-provider-refactor-story2    #8123      +/-   ##
===============================================================================
+ Coverage                                        83.70%   85.43%   +1.72%     
===============================================================================
  Files                                              650      650              
  Lines                                            42412    42369      -43     
  Branches                                          4980     4973       -7     
===============================================================================
+ Hits                                             35503    36199     +696     
+ Misses                                            5652     5062     -590     
+ Partials                                          1257     1108     -149     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@JadeCara
Copy link
Copy Markdown
Contributor Author

/code-review

Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

Code Review: PR #8123 — Correspondence Template and Reply-To Utility

Scope: Backend (Python). The PR adds a Jinja2 HTML email template, registers MessagingActionType.CORRESPONDENCE in get_email_template(), and introduces reply_to_utils.py with two utility functions plus tests.


Critical (Must Fix)

1. {{ body | safe }} — undefined security contract (correspondence.html:9)

The | safe filter is the same pattern used in other templates for operator-authored static content, which is fine. The risk here is that body for a correspondence email is described as a message sent to a data subject, and the content's origin is not yet defined by this PR. If any future caller passes non-operator content (data-subject text, a database value, an external API response), it will be rendered verbatim as HTML — a stored XSS vector.

The security contract for body needs to be documented at the point of infrastructure creation, not deferred to ENG-3299. See the inline comment for specifics.

2. format_reply_to_address() — no input validation, email header injection risk (reply_to_utils.py:16-23)

Both domain and token are interpolated directly into a string used as a Reply-To: header value, with no validation. A caller passing a domain containing \n, \r, or @ can produce a malformed or header-injectable address. The function signature accepts any str; only the tests are safe (they use controlled inputs). Input validation with re.fullmatch is required before this ships. See the inline comment for a suggested implementation.


Suggestions

3. MessagingActionType.CORRESPONDENCE not in validate_body_params_match_action_type

All other MessagingActionType values with templates have a corresponding entry in the validator map in messaging.py. CORRESPONDENCE is absent (presumably because the body params class doesn't exist yet), which means a FidesopsMessage with action_type=CORRESPONDENCE and any body_params silently passes validation. When ENG-3299 defines CorrespondenceBodyParams, the map entry should be added at the same time — not deferred — to avoid a false-confidence window.

4. format_reply_to_address hardcodes the replies. subdomain

The replies. prefix is hardcoded. If different deployments need a different subdomain, this requires a code change. Consider accepting an optional subdomain: str = "replies" parameter, consistent with the flexibility that use_plus_addressing already implies.

5. Test coverage gaps in test_reply_to_utils.py

  • No test for invalid domain or token values (ties back to Critical finding #2).
  • The test_tokens_are_unique test is a no-op probabilistic check on a 128-bit space. See inline for a more useful alternative.

6. test_template_loads assertion always passes

get_email_template raises on failure; it never returns None. The assert template is not None check is meaningless. See inline for a fix.

7. Verify tests/service/messaging/__init__.py exists

The PR diff does not include this file. Depending on the project's conftest.py setup, pytest may fail to collect the new test files if it is missing. Worth confirming before merge (it may already exist from an earlier PR in this series).


Nice to Have

8. correspondence.html — no | default(...) fallbacks for missing variables

Other templates in the codebase use | default(...) on template variables. If subject or body are not passed, the template silently renders empty strings. Adding {{ subject | default('') }} is low cost and consistent.


Summary

Two critical findings need to be resolved before merge: the | safe filter needs a documented and enforced security contract, and format_reply_to_address must validate its inputs against email header injection. The crypto token generation itself (secrets.token_hex(16)) is correct and appropriate.

🔬 Codegraph: unavailable


💡 Write /code-review in a comment to re-run this review.

Comment thread src/fides/api/email_templates/templates/correspondence.html Outdated
Comment thread tests/service/messaging/test_correspondence_template.py Outdated
Comment thread tests/service/messaging/test_reply_to_utils.py
- Replace | safe with sanitize_html filter (nh3.clean) in correspondence template
- Add input validation to format_reply_to_address (header injection prevention)
- Remove redundant test_template_loads test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@JadeCara JadeCara marked this pull request as ready for review May 11, 2026 20:12
@JadeCara JadeCara requested a review from a team as a code owner May 11, 2026 20:12
@JadeCara JadeCara requested review from erosselli and nreyes-dev and removed request for a team and nreyes-dev May 11, 2026 20:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant