From b6217b68ec1bfaf00d3462737438c3c3935fe1d1 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Thu, 16 Jul 2026 15:38:17 +0200 Subject: [PATCH] Self-host email assets instead of relying on raw.githubusercontent.com MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the externally-hosted codebar logo in email templates with the Rails asset pipeline. The logo is now copied from the codebar/assets repo into app/assets/images/email/, a dedicated folder for email-specific assets with a README warning against modifying existing files (digest fingerprints would break previously-sent emails). Also fix the email CSS link in _header.html.haml — it was hardcoded as /assets/email.css which would 404 in production (digest mismatch). Now uses asset_path helper for correct fingerprint path. Set config.action_mailer.asset_host in production (via ASSET_HOST env var for staging override) and development for absolute asset URLs. --- app/assets/images/email/README.md | 21 ++++++++++++++++++ app/assets/images/email/website-logo.png | Bin 0 -> 5931 bytes .../shared_mailers/_body_header.html.haml | 3 +-- app/views/shared_mailers/_header.html.haml | 2 +- config/environments/development.rb | 3 +++ config/environments/production.rb | 3 +++ 6 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 app/assets/images/email/README.md create mode 100644 app/assets/images/email/website-logo.png diff --git a/app/assets/images/email/README.md b/app/assets/images/email/README.md new file mode 100644 index 000000000..c93a28f3b --- /dev/null +++ b/app/assets/images/email/README.md @@ -0,0 +1,21 @@ +# Email Assets + +Images in this folder are used in transactional emails sent by the planner +application. They are referenced via the Rails asset pipeline with digest +fingerprinting. + +## ⚠️ Do not modify or delete existing files + +The digest fingerprint is derived from the file content. Changing an image +here changes its URL. Any previously-sent email that references the old URL +will show a broken image in the recipient's inbox. + +**Additive changes only.** Need a new image for a new email? Add a new file. +Need a new logo? Add it as a new filename (e.g. `logo-2026.png`) and update +the mailer view to reference the new file. Never mutate an existing file. + +## Email stylesheet + +The email CSS lives at `app/assets/stylesheets/email.css` — not in this +folder — but it's referenced by mailer layouts via the pipeline path +`email.css`. diff --git a/app/assets/images/email/website-logo.png b/app/assets/images/email/website-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ba919ed8640a10319bf15cbdc682cdd8586760d7 GIT binary patch literal 5931 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 U#~=Kh8#nP3 "Content-Type", content: "text/html; charset=UTF-8" } %title=title - %link{rel: "stylesheet", type: "text/css", href: "/assets/email.css"} + %link{rel: "stylesheet", type: "text/css", href: asset_path("email.css")} diff --git a/config/environments/development.rb b/config/environments/development.rb index 5da35e3b7..3f030ff3d 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -43,6 +43,9 @@ # Set localhost to be used by links generated in mailer templates. config.action_mailer.default_url_options = { host: "localhost", port: 3000 } + # Host for absolute asset URLs in emails (for local email preview). + config.action_mailer.asset_host = "http://localhost:3000" + # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log diff --git a/config/environments/production.rb b/config/environments/production.rb index 920dd1e47..6b16c5d3d 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -68,6 +68,9 @@ # Set host to be used by links generated in mailer templates. config.action_mailer.default_url_options = { host: 'codebar.io' } + # Host for absolute asset URLs in emails. Override for staging via ASSET_HOST env var. + config.action_mailer.asset_host = ENV.fetch('ASSET_HOST', 'https://codebar.io') + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. config.action_mailer.smtp_settings = { port: '587',