diff --git a/.build_number b/.build_number index c630262..a17e58e 100644 --- a/.build_number +++ b/.build_number @@ -1 +1 @@ -0303261611 +0303261629 diff --git a/backend/app/build_info.py b/backend/app/build_info.py index 0118f36..887a7e6 100644 --- a/backend/app/build_info.py +++ b/backend/app/build_info.py @@ -1,2 +1,2 @@ -BUILD_NUMBER = "0303261611" -CHANGELOG = '2026-03-03|Fix shared request access and Jellyfin-ready pipeline status\n2026-03-03|Process 1 build 0303261507\n2026-03-03|Improve SQLite batching and diagnostics visibility\n2026-03-03|Add login page visibility controls\n2026-03-03|Hotfix: expand landing-page search to all requests\n2026-03-02|Hotfix: add logged-out password reset flow\n2026-03-02|Process 1 build 0203261953\n2026-03-02|Process 1 build 0203261610\n2026-03-02|Process 1 build 0203261608\n2026-03-02|Add dedicated profile invites page and fix mobile admin layout\n2026-03-01|Persist Seerr media failure suppression and reduce sync error noise\n2026-03-01|Add repository line ending policy\n2026-03-01|Finalize diagnostics, logging controls, and email test support\n2026-03-01|Add invite email templates and delivery workflow\n2026-02-28|Finalize dev-1.3 upgrades and Seerr updates\n2026-02-27|admin docs and layout refresh, build 2702261314\n2026-02-27|Build 2702261153: fix jellyfin sync user visibility\n2026-02-26|Build 2602262241: live request page updates\n2026-02-26|Build 2602262204\n2026-02-26|Build 2602262159: restore jellyfin-first user source\n2026-02-26|Build 2602262049: split magent settings and harden local login\n2026-02-26|Build 2602262030: add magent settings and hardening\n2026-02-26|Build 2602261731: fix user resync after nuclear wipe\n2026-02-26|Build 2602261717: master invite policy and self-service invite controls\n2026-02-26|Build 2602261636: self-service invites and count fixes\n2026-02-26|Build 2602261605: invite trace and cross-system user lifecycle\n2026-02-26|Build 2602261536: refine invite layouts and tighten UI\n2026-02-26|Build 2602261523: live updates, invite cleanup and nuclear resync\n2026-02-26|Build 2602261442: tidy users and invite layouts\n2026-02-26|Build 2602261409: unify invite management controls\n2026-02-26|Build 2602260214: invites profiles and expiry admin controls\n2026-02-26|Build 2602260022: enterprise UI refresh and users bulk auto-search\n2026-02-25|Build 2502262321: fix auto-search quality and per-user toggle\n2026-02-02|Build 0202261541: allow FQDN service URLs\n2026-01-30|Build 3001262148: single container\n2026-01-29|Build 2901262244: format changelog\n2026-01-29|Build 2901262240: cache users\n2026-01-29|Tidy full changelog\n2026-01-29|Update full changelog\n2026-01-29|Bake build number and changelog\n2026-01-29|Hardcode build number in backend\n2026-01-29|release: 2901262102\n2026-01-29|release: 2901262044\n2026-01-29|release: 2901262036\n2026-01-27|Hydrate missing artwork from Jellyseerr (build 271261539)\n2026-01-27|Fallback to TMDB when artwork cache fails (build 271261524)\n2026-01-27|Add service test buttons (build 271261335)\n2026-01-27|Bump build number (process 2) 271261322\n2026-01-27|Add cache load spinner (build 271261238)\n2026-01-27|Fix snapshot title fallback (build 271261228)\n2026-01-27|Fix request titles in snapshots (build 271261219)\n2026-01-27|Bump build number to 271261202\n2026-01-27|Clarify request sync settings (build 271261159)\n2026-01-27|Fix backend cache stats import (build 271261149)\n2026-01-27|Improve cache stats performance (build 271261145)\n2026-01-27|Add cache control artwork stats\n2026-01-26|Fix sync progress bar animation\n2026-01-26|Fix cache title hydration\n2026-01-25|Build 2501262041\n2026-01-25|Harden request cache titles and cache-only reads\n2026-01-25|Serve bundled branding assets by default\n2026-01-25|Seed branding logo from bundled assets\n2026-01-25|Tidy request sync controls\n2026-01-25|Add Jellyfin login cache and admin-only stats\n2026-01-25|Add user stats and activity tracking\n2026-01-25|Move account actions into avatar menu\n2026-01-25|Improve mobile header layout\n2026-01-25|Automate build number tagging and sync\n2026-01-25|Add site banner, build number, and changelog\n2026-01-24|Improve request handling and qBittorrent categories\n2026-01-24|Map Prowlarr releases to Arr indexers for manual grab\n2026-01-24|Clarify how-it-works steps and fixes\n2026-01-24|Document fix buttons in how-it-works\n2026-01-24|Route grabs through Sonarr/Radarr only\n2026-01-23|Use backend branding assets for logo and favicon\n2026-01-23|Copy public assets into frontend image\n2026-01-23|Fix backend Dockerfile paths for root context\n2026-01-23|Add Docker Hub compose override\n2026-01-23|Remove password fields from users page\n2026-01-23|Use bundled branding assets\n2026-01-23|Add default branding assets when missing\n2026-01-23|Show available status on landing when in Jellyfin\n2026-01-23|Fix cache titles and move feedback link\n2026-01-23|Add feedback form and webhook\n2026-01-23|Hide header actions when signed out\n2026-01-23|Fallback manual grab to qBittorrent\n2026-01-23|Split search actions and improve download options\n2026-01-23|Fix cache titles via Jellyseerr media lookup\n2026-01-22|Update README with Docker-first guide\n2026-01-22|Update README\n2026-01-22|Ignore build artifacts\n2026-01-22|Initial commit' +BUILD_NUMBER = "0303261629" +CHANGELOG = '2026-03-03|Add SMTP receipt logging for Exchange relay tracing\n2026-03-03|Fix shared request access and Jellyfin-ready pipeline status\n2026-03-03|Process 1 build 0303261507\n2026-03-03|Improve SQLite batching and diagnostics visibility\n2026-03-03|Add login page visibility controls\n2026-03-03|Hotfix: expand landing-page search to all requests\n2026-03-02|Hotfix: add logged-out password reset flow\n2026-03-02|Process 1 build 0203261953\n2026-03-02|Process 1 build 0203261610\n2026-03-02|Process 1 build 0203261608\n2026-03-02|Add dedicated profile invites page and fix mobile admin layout\n2026-03-01|Persist Seerr media failure suppression and reduce sync error noise\n2026-03-01|Add repository line ending policy\n2026-03-01|Finalize diagnostics, logging controls, and email test support\n2026-03-01|Add invite email templates and delivery workflow\n2026-02-28|Finalize dev-1.3 upgrades and Seerr updates\n2026-02-27|admin docs and layout refresh, build 2702261314\n2026-02-27|Build 2702261153: fix jellyfin sync user visibility\n2026-02-26|Build 2602262241: live request page updates\n2026-02-26|Build 2602262204\n2026-02-26|Build 2602262159: restore jellyfin-first user source\n2026-02-26|Build 2602262049: split magent settings and harden local login\n2026-02-26|Build 2602262030: add magent settings and hardening\n2026-02-26|Build 2602261731: fix user resync after nuclear wipe\n2026-02-26|Build 2602261717: master invite policy and self-service invite controls\n2026-02-26|Build 2602261636: self-service invites and count fixes\n2026-02-26|Build 2602261605: invite trace and cross-system user lifecycle\n2026-02-26|Build 2602261536: refine invite layouts and tighten UI\n2026-02-26|Build 2602261523: live updates, invite cleanup and nuclear resync\n2026-02-26|Build 2602261442: tidy users and invite layouts\n2026-02-26|Build 2602261409: unify invite management controls\n2026-02-26|Build 2602260214: invites profiles and expiry admin controls\n2026-02-26|Build 2602260022: enterprise UI refresh and users bulk auto-search\n2026-02-25|Build 2502262321: fix auto-search quality and per-user toggle\n2026-02-02|Build 0202261541: allow FQDN service URLs\n2026-01-30|Build 3001262148: single container\n2026-01-29|Build 2901262244: format changelog\n2026-01-29|Build 2901262240: cache users\n2026-01-29|Tidy full changelog\n2026-01-29|Update full changelog\n2026-01-29|Bake build number and changelog\n2026-01-29|Hardcode build number in backend\n2026-01-29|release: 2901262102\n2026-01-29|release: 2901262044\n2026-01-29|release: 2901262036\n2026-01-27|Hydrate missing artwork from Jellyseerr (build 271261539)\n2026-01-27|Fallback to TMDB when artwork cache fails (build 271261524)\n2026-01-27|Add service test buttons (build 271261335)\n2026-01-27|Bump build number (process 2) 271261322\n2026-01-27|Add cache load spinner (build 271261238)\n2026-01-27|Fix snapshot title fallback (build 271261228)\n2026-01-27|Fix request titles in snapshots (build 271261219)\n2026-01-27|Bump build number to 271261202\n2026-01-27|Clarify request sync settings (build 271261159)\n2026-01-27|Fix backend cache stats import (build 271261149)\n2026-01-27|Improve cache stats performance (build 271261145)\n2026-01-27|Add cache control artwork stats\n2026-01-26|Fix sync progress bar animation\n2026-01-26|Fix cache title hydration\n2026-01-25|Build 2501262041\n2026-01-25|Harden request cache titles and cache-only reads\n2026-01-25|Serve bundled branding assets by default\n2026-01-25|Seed branding logo from bundled assets\n2026-01-25|Tidy request sync controls\n2026-01-25|Add Jellyfin login cache and admin-only stats\n2026-01-25|Add user stats and activity tracking\n2026-01-25|Move account actions into avatar menu\n2026-01-25|Improve mobile header layout\n2026-01-25|Automate build number tagging and sync\n2026-01-25|Add site banner, build number, and changelog\n2026-01-24|Improve request handling and qBittorrent categories\n2026-01-24|Map Prowlarr releases to Arr indexers for manual grab\n2026-01-24|Clarify how-it-works steps and fixes\n2026-01-24|Document fix buttons in how-it-works\n2026-01-24|Route grabs through Sonarr/Radarr only\n2026-01-23|Use backend branding assets for logo and favicon\n2026-01-23|Copy public assets into frontend image\n2026-01-23|Fix backend Dockerfile paths for root context\n2026-01-23|Add Docker Hub compose override\n2026-01-23|Remove password fields from users page\n2026-01-23|Use bundled branding assets\n2026-01-23|Add default branding assets when missing\n2026-01-23|Show available status on landing when in Jellyfin\n2026-01-23|Fix cache titles and move feedback link\n2026-01-23|Add feedback form and webhook\n2026-01-23|Hide header actions when signed out\n2026-01-23|Fallback manual grab to qBittorrent\n2026-01-23|Split search actions and improve download options\n2026-01-23|Fix cache titles via Jellyseerr media lookup\n2026-01-22|Update README with Docker-first guide\n2026-01-22|Update README\n2026-01-22|Ignore build artifacts\n2026-01-22|Initial commit' diff --git a/backend/app/services/invite_email.py b/backend/app/services/invite_email.py index f02e8dd..ff9b81e 100644 --- a/backend/app/services/invite_email.py +++ b/backend/app/services/invite_email.py @@ -64,6 +64,78 @@ TEMPLATE_PLACEHOLDERS = [ "username", ] +EMAIL_TAGLINE = "Find and fix media requests fast." + +EMAIL_TONE_STYLES: Dict[str, Dict[str, str]] = { + "brand": { + "chip_bg": "rgba(255, 107, 43, 0.16)", + "chip_border": "rgba(255, 107, 43, 0.38)", + "chip_text": "#ffd2bf", + "accent_a": "#ff6b2b", + "accent_b": "#1c6bff", + }, + "success": { + "chip_bg": "rgba(34, 197, 94, 0.16)", + "chip_border": "rgba(34, 197, 94, 0.38)", + "chip_text": "#c7f9d7", + "accent_a": "#22c55e", + "accent_b": "#1c6bff", + }, + "warning": { + "chip_bg": "rgba(251, 146, 60, 0.16)", + "chip_border": "rgba(251, 146, 60, 0.38)", + "chip_text": "#ffe0ba", + "accent_a": "#fb923c", + "accent_b": "#ff6b2b", + }, + "danger": { + "chip_bg": "rgba(248, 113, 113, 0.16)", + "chip_border": "rgba(248, 113, 113, 0.38)", + "chip_text": "#ffd0d0", + "accent_a": "#ef4444", + "accent_b": "#ff6b2b", + }, +} + +TEMPLATE_PRESENTATION: Dict[str, Dict[str, str]] = { + "invited": { + "tone": "brand", + "title": "You have been invited", + "subtitle": "A new account invitation is ready for you.", + "primary_label": "Accept invite", + "primary_url_key": "invite_link", + "secondary_label": "How it works", + "secondary_url_key": "how_it_works_url", + }, + "welcome": { + "tone": "success", + "title": "Welcome to Magent", + "subtitle": "Your account is ready and synced.", + "primary_label": "Open Magent", + "primary_url_key": "app_url", + "secondary_label": "How it works", + "secondary_url_key": "how_it_works_url", + }, + "warning": { + "tone": "warning", + "title": "Account warning", + "subtitle": "Please review the note below.", + "primary_label": "Open Magent", + "primary_url_key": "app_url", + "secondary_label": "How it works", + "secondary_url_key": "how_it_works_url", + }, + "banned": { + "tone": "danger", + "title": "Account status changed", + "subtitle": "Your account has been restricted or removed.", + "primary_label": "How it works", + "primary_url_key": "how_it_works_url", + "secondary_label": "", + "secondary_url_key": "", + }, +} + DEFAULT_TEMPLATES: Dict[str, Dict[str, str]] = { "invited": { "subject": "{{app_name}} invite for {{recipient_email}}", @@ -81,18 +153,34 @@ DEFAULT_TEMPLATES: Dict[str, Dict[str, str]] = { "Build: {{build_number}}\n" ), "body_html": ( - "
You have been invited to {{app_name}}.
" - "Invite code: {{invite_code}}
"
- "Invited by: {{inviter_username}}
"
- "Invite label: {{invite_label}}
"
- "Expires: {{invite_expires_at}}
"
- "Remaining uses: {{invite_remaining_uses}}
{{invite_description}}
" - "{{message}}
" - "Accept invite and create account
" - "" - "" + "| "
+ " Invite code "
+ "{{invite_code}} "
+ " | "
+ ""
+ " Invited by "
+ "{{inviter_username}} "
+ " | "
+ "
| "
+ " Invite label "
+ "{{invite_label}} "
+ " | "
+ ""
+ " Access window "
+ "{{invite_expires_at}} "
+ "Remaining uses: {{invite_remaining_uses}} "
+ " | "
+ "
Your {{app_name}} account is ready, {{username}}.
" - "Role: {{role}}
" - "Open {{app_name}}
"
- "Read how it works
{{message}}
" + "| "
+ " Username "
+ "{{username}} "
+ " | "
+ ""
+ " Role "
+ "{{role}} "
+ " | "
+ "
Hello {{username}},
" - "This is a warning regarding your {{app_name}} account.
" - "Reason: {{reason}}
" - "{{message}}
" - "If you need help, contact the admin.
" + "Hello {{username}},
" - "Your {{app_name}} account has been banned or removed.
" - "Reason: {{reason}}
" - "{{message}}
" + "| "
+ " Reason "
+ "{{reason}} "
+ " | "
+ "
"
+ "
|
This is a test email from {html.escape(env_settings.app_name)}.
" - f"Build: {html.escape(BUILD_NUMBER)}
"
- f"Application URL: {html.escape(application_url)}
| "
+ " Build "
+ f"{html.escape(BUILD_NUMBER)} "
+ " | "
+ ""
+ " Application URL "
+ f"{html.escape(application_url)} "
+ " | "
+ "
A password reset was requested for {html.escape(username)}.
" - f"This link will reset the password used for {html.escape(provider_label)}.
" - f"" - f"Expires: {html.escape(expires_at)}
" - "If you did not request this reset, you can ignore this email.
" + body_html = _wrap_email_html( + app_name=env_settings.app_name, + app_url=app_url, + build_number=BUILD_NUMBER, + title="Reset your password", + subtitle=f"This will update the credentials used for {provider_label}.", + tone="brand", + body_html=( + f"| "
+ " Account "
+ f"{html.escape(username)} "
+ " | "
+ ""
+ " Expires "
+ f"{html.escape(expires_at)} "
+ " | "
+ "