Skip to content

Commit 5d28a2e

Browse files
committed
fix(emails): publish the resized wordmark at a new URL
#6648 replaced wordmark.png in place while changing its intrinsic aspect and the pinned width/height. Mail clients and Gmail's image proxy cache by URL, so the two collide in both directions: a cached 272x164 file forced into the new 42x20 box renders 21.7% squashed, and every email already delivered still carries width=68 height=41, which stretches the new 168x80 file 20.3% the other way. Restore wordmark.png byte-for-byte so delivered mail renders as it always did, and serve the resized mark from wordmark-v2.png. The src now comes from EMAIL_WORDMARK_SRC, and a test pins every retired raster's dimensions so the next resize adds a file instead of replacing one.
1 parent 2da8015 commit 5d28a2e

5 files changed

Lines changed: 47 additions & 17 deletions

File tree

apps/sim/components/emails/components/email-layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Body, Container, Font, Head, Html, Img, Preview, Section } from '@react-email/components'
22
import { baseStyles } from '@/components/emails/_styles'
33
import { EmailFooter } from '@/components/emails/components/email-footer'
4-
import { EMAIL_WORDMARK_SIZE } from '@/lib/branding/wordmark'
4+
import { EMAIL_WORDMARK_SIZE, EMAIL_WORDMARK_SRC } from '@/lib/branding/wordmark'
55
import { getBaseUrl } from '@/lib/core/utils/urls'
66
import { getBrandConfig } from '@/ee/whitelabeling'
77

@@ -75,7 +75,7 @@ export function EmailLayout({
7575
<Container style={baseStyles.container}>
7676
<Section style={baseStyles.header}>
7777
<Img
78-
src={brand.logoUrl || `${baseUrl}/brand/color/email/wordmark.png`}
78+
src={brand.logoUrl || `${baseUrl}${EMAIL_WORDMARK_SRC}`}
7979
alt={brand.name}
8080
{...(hasCustomLogo ? CUSTOM_LOGO_SIZE : WORDMARK_SIZE)}
8181
style={hasCustomLogo ? { display: 'block', width: 'auto' } : { display: 'block' }}

apps/sim/lib/branding/wordmark.test.ts

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,29 @@
44
import { readFileSync } from 'node:fs'
55
import path from 'node:path'
66
import { describe, expect, it } from 'vitest'
7-
import { EMAIL_WORDMARK_SIZE, WORDMARK_PATHS, WORDMARK_VIEW_BOX } from '@/lib/branding/wordmark'
7+
import {
8+
EMAIL_WORDMARK_SIZE,
9+
EMAIL_WORDMARK_SRC,
10+
WORDMARK_PATHS,
11+
WORDMARK_VIEW_BOX,
12+
} from '@/lib/branding/wordmark'
813

914
/** Email clients do no responsive image selection, so the asset carries retina detail itself. */
1015
const MIN_RETINA_SCALE = 2
1116

12-
const WORDMARK_PNG = path.join(
13-
import.meta.dirname,
14-
'..',
15-
'..',
16-
'public',
17-
'brand',
18-
'color',
19-
'email',
20-
'wordmark.png'
21-
)
17+
const PUBLIC_DIR = path.join(import.meta.dirname, '..', '..', 'public')
18+
19+
/** The file the header actually points at, resolved through the same constant it uses. */
20+
const WORDMARK_PNG = path.join(PUBLIC_DIR, EMAIL_WORDMARK_SRC)
21+
22+
/**
23+
* Rasters retired from {@link EMAIL_WORDMARK_SRC}, with the box the mail that
24+
* still references them was sent with. Those messages refetch these URLs
25+
* forever, so the files must stay put and keep their original dimensions.
26+
*/
27+
const RETIRED_WORDMARKS = [
28+
{ src: '/brand/color/email/wordmark.png', width: 272, height: 164 },
29+
] as const
2230

2331
/** Reads width/height out of a PNG's IHDR, which always follows the 8-byte signature. */
2432
function readPngSize(file: string): { width: number; height: number } {
@@ -50,4 +58,12 @@ describe('email wordmark asset', () => {
5058
expect(WORDMARK_PATHS).toHaveLength(4)
5159
for (const d of WORDMARK_PATHS) expect(d.startsWith('M')).toBe(true)
5260
})
61+
62+
it.each(RETIRED_WORDMARKS)(
63+
'keeps $src intact for the mail already sent against it',
64+
({ src, width, height }) => {
65+
expect(src).not.toBe(EMAIL_WORDMARK_SRC)
66+
expect(readPngSize(path.join(PUBLIC_DIR, src))).toEqual({ width, height })
67+
}
68+
)
5369
})

apps/sim/lib/branding/wordmark.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,23 @@ export const WORDMARK_PATHS: readonly string[] = [
2525
* mark stands 20px tall; the width follows the view box. Both dimensions are
2626
* pinned because email clients do no responsive image selection.
2727
*
28-
* The header renders `public/brand/color/email/wordmark.png` rather than these
29-
* paths, because email clients strip inline SVG. That file is these outlines
30-
* filled with the email palette's `textBody`, rasterized at 4x so the mark
31-
* stays crisp on retina and this box can be retuned without re-exporting it.
28+
* The header renders {@link EMAIL_WORDMARK_SRC} rather than these paths, because
29+
* email clients strip inline SVG. That file is these outlines filled with the
30+
* email palette's `textBody`, rasterized at 4x so the mark stays crisp on retina
31+
* and this box can be retuned without re-exporting it.
3232
*/
3333
export const EMAIL_WORDMARK_SIZE = { width: 42, height: 20 } as const
34+
35+
/**
36+
* Public path of the email wordmark raster.
37+
*
38+
* **Never replace this file in place — publish a new filename instead.** A sent
39+
* email is immutable: it carries the `width`/`height` of the day it was sent and
40+
* refetches this URL forever. Mail clients and Gmail's image proxy cache by URL,
41+
* so swapping the bytes behind one both squashes the mark in every email already
42+
* delivered (old attributes, new intrinsic size) and serves stale bytes into the
43+
* new box until every cache expires. Changing the raster's dimensions or artwork
44+
* means bumping the suffix here, leaving the previous file in place for the mail
45+
* that still points at it.
46+
*/
47+
export const EMAIL_WORDMARK_SRC = '/brand/color/email/wordmark-v2.png'
2.48 KB
Loading
7.56 KB
Loading

0 commit comments

Comments
 (0)