feat(messages_v2): add Messages V2 for emailing program offerers/hosts - #1000
Draft
japsu wants to merge 4 commits into
Draft
feat(messages_v2): add Messages V2 for emailing program offerers/hosts#1000japsu wants to merge 4 commits into
japsu wants to merge 4 commits into
Conversation
Adds a new messages_v2 Django app and Program V2 admin UI for composing and sending Markdown messages to program offerers/hosts, selected via OR-of-AND involvement dimension filters. Messages have a draft -> active (sent) -> optionally expired lifecycle; edits to an already-sent message are not retroactive. New involvements matching an active message's filters are sent to automatically. Recipients can view messages they've received on their profile. Backend: - Message (UUID7 PK, created_at derived from it), MessageReplyTo, MessageBody (dedup store), MessageRecipient models - Security-critical rendering pipeline: placeholders are tokenized, substituted after Markdown rendering and nh3 sanitization, and HTML-escaped, so recipient-controlled values can never inject Markdown or HTML - Celery send task + auto-send hook on Involvement.refresh_dependents() - GraphQL queries/mutations under program_v2 CBAC, event log entries - Two integration tests covering compose->send (incl. injection payloads) and auto-send + non-retroactive edit Frontend: - New program-messages admin routes (list, new, edit) sharing a MessageComposeCard; recipient filters edited via a modal whose selection state survives the modal's mount/unmount cycle (RecipientFilterField) - New MarkdownText SchemaForm field type backed by @uiw/react-md-editor, toolbar restricted to formatting the backend actually allows - Reply-to address management on the program preferences page - Profile "received messages" view - Full en/fi/sv translations Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Address findings from a security/correctness/performance review of Messages V2: - Only record a MessageRecipient once the email actually sends (fail_silently off + logged), so a transient failure no longer permanently marks a person as delivered and blocks retries. - Refuse to send a message with empty recipient filters (an empty AND-group would otherwise match every involvement in the universe); drafts may still be saved empty. - Validate reply-to email addresses in the create/update mutations, which bypassed EmailField validation. - Skip the auto-send Celery dispatch unless the universe has an active message, avoiding a task per involvement change system-wide. - Resolve recipients with a single OR'd queryset instead of a Python id set. - Extract group_to_dimension_filters() shared by message.py and tasks.py. - Make MessageBody.digest unique so dedup get_or_create is race-safe. Frontend: - Real Swedish translation for the profile messages description. - Join recipient filter groups with language-neutral " / " instead of " OR ". - Localize the MarkdownEditor "insert heading" aria-label. - Parse the recipientFilters hidden field via a helper that fails cleanly on invalid JSON instead of raising an opaque 500. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a new messages_v2 Django app and Program V2 admin UI for composing
and sending Markdown messages to program offerers/hosts, selected via
OR-of-AND involvement dimension filters. Messages have a
draft -> active (sent) -> optionally expired lifecycle; edits to an
already-sent message are not retroactive. New involvements matching an
active message's filters are sent to automatically. Recipients can view
messages they've received on their profile.
Backend:
MessageBody (dedup store), MessageRecipient models
substituted after Markdown rendering and nh3 sanitization, and
HTML-escaped, so recipient-controlled values can never inject
Markdown or HTML
payloads) and auto-send + non-retroactive edit
Frontend:
MessageComposeCard; recipient filters edited via a modal whose
selection state survives the modal's mount/unmount cycle
(RecipientFilterField)
@uiw/react-md-editor, toolbar restricted to formatting the backend
actually allows
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com