Skip to content

Commit f541772

Browse files
committed
improvement(emails): align font stack, logo and social-icon sizing with the platform
1 parent 01aa311 commit f541772

3 files changed

Lines changed: 88 additions & 65 deletions

File tree

apps/sim/components/emails/_styles/base.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,15 @@ export const colors = buildColors()
5858
* client default and the only deviation, and it is still a real platform token.
5959
*/
6060
export const typography = {
61+
/**
62+
* Mirrors the platform face and its fallback chain
63+
* (`apps/sim/app/_styles/fonts/season/season.ts`). This matters more than the
64+
* `<Font>` webfont in `EmailLayout` — Gmail and Outlook strip `@font-face`
65+
* entirely, so for most recipients the fallback chain IS the rendered font.
66+
* It previously listed Apple system faces the platform never uses.
67+
*/
6168
fontFamily:
62-
"'Season Sans', -apple-system, 'SF Pro Display', 'SF Pro Text', 'Helvetica', sans-serif",
69+
"'Season Sans', system-ui, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif",
6370
fontSize: {
6471
/** `text-caption` */
6572
caption: '12px',
@@ -103,8 +110,6 @@ export const spacing = {
103110
gutter: 40,
104111
sectionGap: 20,
105112
paragraphGap: 12,
106-
/** Logo width in pixels */
107-
logoWidth: 90,
108113
}
109114

110115
export const baseStyles = {

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

Lines changed: 57 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,32 @@ import { isHosted } from '@/lib/core/config/env-flags'
44
import { getBaseUrl } from '@/lib/core/utils/urls'
55
import { getBrandConfig } from '@/ee/whitelabeling'
66

7+
/**
8+
* Social mark display size. Every `static/*-icon.png` is 40×40, so 20px is a
9+
* clean 2x source — email clients do no responsive image selection, so the
10+
* asset must be authored at 2x and pinned here. This is deliberately NOT the
11+
* platform's 14px UI-icon size: these are brand marks in an image, not
12+
* `--text-icon` glyphs, and 14px renders them illegibly.
13+
*/
14+
const SOCIAL_ICON_SIZE = 20
15+
16+
/** Gap between social marks. Halved on each side, so the row reads evenly. */
17+
const SOCIAL_ICON_GAP = 8
18+
19+
/**
20+
* `display: block` removes the 2–3px gap Outlook adds under inline images;
21+
* `border: 0` prevents the blue link border older Outlook draws around a linked
22+
* image.
23+
*/
24+
const socialIconStyle = { display: 'block' as const, border: 0 }
25+
26+
const SOCIAL_LINKS = [
27+
{ path: 'x', label: 'X', icon: 'x-icon.png' },
28+
{ path: 'linkedin', label: 'LinkedIn', icon: 'linkedin-icon.png' },
29+
{ path: 'github', label: 'GitHub', icon: 'github-icon.png' },
30+
{ path: 'slack', label: 'Slack', icon: 'slack-icon.png' },
31+
] as const
32+
733
interface EmailFooterProps {
834
baseUrl?: string
935
messageId?: string
@@ -32,16 +58,6 @@ export function EmailFooter({
3258

3359
const footerLinkStyle = baseStyles.footerLink
3460

35-
/**
36-
* Social icons are linked images. `display: block` removes the 2–3px gap
37-
* Outlook adds under inline images, and `border: 0` prevents the blue link
38-
* border older Outlook versions draw around linked images.
39-
*/
40-
const socialIconStyle = {
41-
display: 'block' as const,
42-
border: 0,
43-
}
44-
4561
return (
4662
<Section
4763
style={{
@@ -75,50 +91,26 @@ export function EmailFooter({
7591
<table cellPadding={0} cellSpacing={0} style={{ border: 0 }}>
7692
<tbody>
7793
<tr>
78-
<td align='left' style={{ padding: '0 8px 0 0' }}>
79-
<Link href={`${baseUrl}/x`} rel='noopener noreferrer'>
80-
<Img
81-
src={`${baseUrl}/static/x-icon.png`}
82-
width='20'
83-
height='20'
84-
alt='X'
85-
style={socialIconStyle}
86-
/>
87-
</Link>
88-
</td>
89-
<td align='left' style={{ padding: '0 8px' }}>
90-
<Link href={`${baseUrl}/linkedin`} rel='noopener noreferrer'>
91-
<Img
92-
src={`${baseUrl}/static/linkedin-icon.png`}
93-
width='20'
94-
height='20'
95-
alt='LinkedIn'
96-
style={socialIconStyle}
97-
/>
98-
</Link>
99-
</td>
100-
<td align='left' style={{ padding: '0 8px' }}>
101-
<Link href={`${baseUrl}/github`} rel='noopener noreferrer'>
102-
<Img
103-
src={`${baseUrl}/static/github-icon.png`}
104-
width='20'
105-
height='20'
106-
alt='GitHub'
107-
style={socialIconStyle}
108-
/>
109-
</Link>
110-
</td>
111-
<td align='left' style={{ padding: '0 8px' }}>
112-
<Link href={`${baseUrl}/slack`} rel='noopener noreferrer'>
113-
<Img
114-
src={`${baseUrl}/static/slack-icon.png`}
115-
width='20'
116-
height='20'
117-
alt='Slack'
118-
style={socialIconStyle}
119-
/>
120-
</Link>
121-
</td>
94+
{SOCIAL_LINKS.map(({ path, label, icon }, index) => (
95+
<td
96+
key={path}
97+
align='left'
98+
style={{
99+
paddingLeft: index === 0 ? 0 : SOCIAL_ICON_GAP,
100+
paddingRight: SOCIAL_ICON_GAP,
101+
}}
102+
>
103+
<Link href={`${baseUrl}/${path}`} rel='noopener noreferrer'>
104+
<Img
105+
src={`${baseUrl}/static/${icon}`}
106+
width={SOCIAL_ICON_SIZE}
107+
height={SOCIAL_ICON_SIZE}
108+
alt={label}
109+
style={socialIconStyle}
110+
/>
111+
</Link>
112+
</td>
113+
))}
122114
</tr>
123115
</tbody>
124116
</table>
@@ -167,6 +159,11 @@ export function EmailFooter({
167159
</td>
168160
<td style={baseStyles.footerText}>
169161
Questions?{' '}
162+
{/*
163+
A raw anchor, not `<Link>`: react-email's Link hardcodes
164+
target="_blank", which on a mailto: opens a blank tab beside
165+
the compose window in most webmail clients.
166+
*/}
170167
<a href={`mailto:${brand.supportEmail}`} style={footerLinkStyle}>
171168
{brand.supportEmail}
172169
</a>
@@ -210,24 +207,24 @@ export function EmailFooter({
210207
&nbsp;
211208
</td>
212209
<td style={baseStyles.footerText}>
213-
<a href={`${baseUrl}/privacy`} style={footerLinkStyle} rel='noopener noreferrer'>
210+
<Link href={`${baseUrl}/privacy`} style={footerLinkStyle} rel='noopener noreferrer'>
214211
Privacy Policy
215-
</a>{' '}
212+
</Link>{' '}
216213
{' '}
217-
<a href={`${baseUrl}/terms`} style={footerLinkStyle} rel='noopener noreferrer'>
214+
<Link href={`${baseUrl}/terms`} style={footerLinkStyle} rel='noopener noreferrer'>
218215
Terms of Service
219-
</a>
216+
</Link>
220217
{showUnsubscribe && (
221218
<>
222219
{' '}
223220
{' '}
224-
<a
221+
<Link
225222
href={`${baseUrl}/unsubscribe?token={{UNSUBSCRIBE_TOKEN}}&email={{UNSUBSCRIBE_EMAIL}}`}
226223
style={footerLinkStyle}
227224
rel='noopener noreferrer'
228225
>
229226
Unsubscribe
230-
</a>
227+
</Link>
231228
</>
232229
)}
233230
</td>

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ import { EmailFooter } from '@/components/emails/components/email-footer'
44
import { getBaseUrl } from '@/lib/core/utils/urls'
55
import { getBrandConfig } from '@/ee/whitelabeling'
66

7+
/**
8+
* Wordmark display size. `wordmark.png` is 272×164, so these are exactly 1/4 of
9+
* its intrinsic size — the asset is a 4x source and must stay that way, since
10+
* email clients do no responsive image selection. Changing one of these without
11+
* the other distorts the mark.
12+
*/
13+
const WORDMARK_HEIGHT = 41
14+
const WORDMARK_WIDTH = 68
15+
16+
/** Whitelabeled logos are arbitrary aspect ratios, so only height is pinned. */
17+
const CUSTOM_LOGO_HEIGHT = 34
18+
719
interface EmailLayoutProps {
820
/** Preview text shown in email client list view */
921
preview: string
@@ -35,9 +47,16 @@ export function EmailLayout({
3547
return (
3648
<Html>
3749
<Head>
50+
{/*
51+
`fallbackFontFamily` only accepts react-email's fixed union, so it
52+
cannot express the platform's full chain (`system-ui`, `Segoe UI`,
53+
`Roboto`, …) — this is the closest allowed subset. The complete chain
54+
lives on `typography.fontFamily`, which is applied inline to every
55+
element and is what clients that strip `@font-face` actually use.
56+
*/}
3857
<Font
3958
fontFamily='Season Sans'
40-
fallbackFontFamily={['Helvetica', 'sans-serif']}
59+
fallbackFontFamily={['Helvetica', 'Arial', 'sans-serif']}
4160
webFont={{
4261
url: `${baseUrl}/brand/fonts/SeasonSansUprightsVF.woff2`,
4362
format: 'woff2',
@@ -55,7 +74,9 @@ export function EmailLayout({
5574
<Img
5675
src={brand.logoUrl || `${baseUrl}/brand/color/email/wordmark.png`}
5776
alt={brand.name}
58-
{...(hasCustomLogo ? { height: '34' } : { height: '41', width: '68' })}
77+
{...(hasCustomLogo
78+
? { height: `${CUSTOM_LOGO_HEIGHT}` }
79+
: { height: `${WORDMARK_HEIGHT}`, width: `${WORDMARK_WIDTH}` })}
5980
style={hasCustomLogo ? { display: 'block', width: 'auto' } : { display: 'block' }}
6081
/>
6182
</Section>

0 commit comments

Comments
 (0)