improvement(emails): align transactional emails with the platform neutral design system (#5309)

* improvement(emails): align transactional emails with the platform neutral design system

Re-skin all ~25 transactional email templates to match the platform/newsletter:
- Color tokens in _styles/base.ts now mirror globals.css light mode (#fefefe canvas,
  #1a1a1a text, #525252/#5c5c5c/#707070 greys, #dedede border) instead of the old
  lavender + green (#33C482) scheme; accent defaults to neutral #1a1a1a but still
  honors a whitelabel primaryColor.
- Primary CTA now matches the platform's primary Chip (inverse #1a1a1a fill, white
  text, rounded-lg, text-sm, normal weight); links carry an underline since the
  neutral color no longer signals a link.
- Card uses 8px radius + hairline border; info/code boxes use a subtle token fill.
- Logo is the dark, text-only 'sim' wordmark (green icon removed).
- Footer: LinkedIn replaces Discord.
- Every template routes through the centralized colors tokens — zero hardcoded hex
  (payment-failed error box tokenized to platform error surface values).

* fix(emails): keep card boundary robust + don't reshape custom logos

- Canvas goes to #f8f8f8 (off Andres's newsletter) so the white card reads via
  contrast in clients that flatten inline borders, not the border alone (Greptile P2).
- Logo pins exact 70x34 only for the bundled wordmark; whitelabel brand.logoUrl now
  constrains by height with auto width so custom logos keep their aspect (Greptile P1).

* improvement(emails): use Season Sans to match the newsletter and platform

Register the Season Sans web font (the woff2 shipped with the newsletter, #5302)
via react-email's <Font> in the layout head, and lead the email font stack with
'Season Sans' (system stack still the fallback for clients that ignore @font-face).
This commit is contained in:
Waleed
2026-06-30 19:57:58 -07:00
committed by GitHub
parent 0990a17f82
commit 2c38e63c17
7 changed files with 68 additions and 44 deletions
+41 -32
View File
@@ -10,30 +10,36 @@ function buildColors() {
const brand = getBrandConfig()
const isWhitelabeled = brand.isWhitelabeled
const accentColor =
isWhitelabeled && brand.theme?.primaryColor ? brand.theme.primaryColor : '#33C482'
isWhitelabeled && brand.theme?.primaryColor ? brand.theme.primaryColor : '#1a1a1a'
return {
/** Main canvas background */
bgOuter: '#F7F9FC',
/** Card/container background - pure white */
/** Main canvas background — a hair off-white so the white card reads via contrast, not the border alone */
bgOuter: '#f8f8f8',
/** Card/container background — platform `--surface-2` */
bgCard: '#ffffff',
/** Primary text color */
textPrimary: '#2d2d2d',
/** Secondary text color */
textSecondary: '#404040',
/** Tertiary text color */
/** Primary text — platform `--text-primary` */
textPrimary: '#1a1a1a',
/** Secondary text — platform `--text-secondary` */
textSecondary: '#525252',
/** Tertiary text — platform `--text-tertiary` */
textTertiary: '#5c5c5c',
/** Muted text (footer) */
textMuted: '#737373',
/** Brand primary color */
/** Muted text (footer) — platform `--text-muted` */
textMuted: '#707070',
/** Brand primary — neutral by default, brand color when whitelabeled */
brandPrimary:
isWhitelabeled && brand.theme?.primaryColor ? brand.theme.primaryColor : '#33C482',
/** Brand accent - used for buttons and links */
isWhitelabeled && brand.theme?.primaryColor ? brand.theme.primaryColor : '#1a1a1a',
/** Accent for buttons and links — neutral by default, brand color when whitelabeled */
brandTertiary: accentColor,
/** Border/divider color */
divider: '#ededed',
/** Footer background */
footerBg: '#F7F9FC',
/** Border/divider — platform `--border` */
divider: '#dedede',
/** Subtle fill for info/code boxes on the white card */
surfaceSubtle: '#f7f7f7',
/** Error surface fill — platform `--terminal-status-error-bg` */
errorBg: '#fef2f2',
/** Error surface border — platform `--error-muted` */
errorBorder: '#fecaca',
/** Footer background — matches the canvas */
footerBg: '#f8f8f8',
}
}
@@ -41,7 +47,8 @@ export const colors = buildColors()
/** Typography settings */
export const typography = {
fontFamily: "-apple-system, 'SF Pro Display', 'SF Pro Text', 'Helvetica', sans-serif",
fontFamily:
"'Season Sans', -apple-system, 'SF Pro Display', 'SF Pro Text', 'Helvetica', sans-serif",
fontSize: {
body: '16px',
small: '14px',
@@ -79,12 +86,13 @@ export const baseStyles = {
margin: '0 auto',
},
/** Main card container with rounded corners */
/** Main card container — white surface, chip-radius, hairline border on the near-white canvas */
container: {
maxWidth: `${spacing.containerWidth}px`,
margin: '0 auto',
backgroundColor: colors.bgCard,
borderRadius: '16px',
border: `1px solid ${colors.divider}`,
borderRadius: '8px',
overflow: 'hidden',
},
@@ -120,26 +128,27 @@ export const baseStyles = {
display: 'inline',
},
/** Primary CTA button - matches app tertiary button style */
/** Primary CTA button - matches the platform's primary Chip (inverse fill, rounded-lg, h-30, text-sm) */
button: {
display: 'inline-block',
backgroundColor: colors.brandTertiary,
color: '#ffffff',
fontWeight: 500,
fontWeight: 400,
fontSize: '14px',
padding: '6px 12px',
borderRadius: '5px',
lineHeight: '30px',
padding: '0 12px',
borderRadius: '8px',
textDecoration: 'none',
textAlign: 'center' as const,
margin: '4px 0',
fontFamily: typography.fontFamily,
},
/** Link text style */
/** Link text style - neutral color, so it carries an underline to read as a link */
link: {
color: colors.brandTertiary,
fontWeight: 'bold' as const,
textDecoration: 'none',
fontWeight: 400,
textDecoration: 'underline',
},
/** Horizontal divider */
@@ -169,8 +178,8 @@ export const baseStyles = {
codeContainer: {
margin: '12px 0',
padding: '12px 16px',
backgroundColor: '#f8f9fa',
borderRadius: '6px',
backgroundColor: colors.surfaceSubtle,
borderRadius: '8px',
border: `1px solid ${colors.divider}`,
textAlign: 'center' as const,
},
@@ -198,9 +207,9 @@ export const baseStyles = {
/** Highlighted info box (e.g., "What you get with Pro") */
infoBox: {
backgroundColor: colors.bgOuter,
backgroundColor: colors.surfaceSubtle,
padding: '16px 18px',
borderRadius: '6px',
borderRadius: '8px',
margin: '16px 0',
},
@@ -34,7 +34,7 @@ export function CreditsExhaustedEmail({
<Section
style={{
backgroundColor: '#f8faf9',
backgroundColor: colors.surfaceSubtle,
border: `1px solid ${colors.brandTertiary}20`,
borderRadius: '8px',
padding: '16px 20px',
@@ -39,7 +39,7 @@ export function FreeTierUpgradeEmail({
{/* Pro Features */}
<Section
style={{
backgroundColor: '#f8faf9',
backgroundColor: colors.surfaceSubtle,
border: `1px solid ${colors.brandTertiary}20`,
borderRadius: '8px',
padding: '16px 20px',
@@ -48,8 +48,8 @@ export function PaymentFailedEmail({
<Section
style={{
backgroundColor: '#fff5f5',
border: '1px solid #fed7d7',
backgroundColor: colors.errorBg,
border: `1px solid ${colors.errorBorder}`,
borderRadius: '6px',
padding: '16px 18px',
margin: '16px 0',
@@ -81,12 +81,15 @@ export function EmailFooter({
</Link>
</td>
<td align='left' style={{ padding: '0 8px' }}>
<Link href={`${baseUrl}/discord`} rel='noopener noreferrer'>
<Link
href='https://www.linkedin.com/company/simdotai'
rel='noopener noreferrer'
>
<Img
src={`${baseUrl}/static/discord-icon.png`}
src={`${baseUrl}/static/linkedin-icon.png`}
width='20'
height='20'
alt='Discord'
alt='LinkedIn'
/>
</Link>
</td>
@@ -1,4 +1,4 @@
import { Body, Container, Head, Html, Img, Preview, Section } from '@react-email/components'
import { Body, Container, Font, Head, Html, Img, Preview, Section } from '@react-email/components'
import { baseStyles } from '@/components/emails/_styles'
import { EmailFooter } from '@/components/emails/components/email-footer'
import { getBaseUrl } from '@/lib/core/utils/urls'
@@ -30,10 +30,22 @@ export function EmailLayout({
}: EmailLayoutProps) {
const brand = getBrandConfig()
const baseUrl = getBaseUrl()
const hasCustomLogo = Boolean(brand.logoUrl)
return (
<Html>
<Head />
<Head>
<Font
fontFamily='Season Sans'
fallbackFontFamily={['Helvetica', 'sans-serif']}
webFont={{
url: `${baseUrl}/brand/fonts/SeasonSansUprightsVF.woff2`,
format: 'woff2',
}}
fontWeight='300 800'
fontStyle='normal'
/>
</Head>
<Preview>{preview}</Preview>
<Body style={baseStyles.main}>
{/* Main card container */}
@@ -42,10 +54,10 @@ export function EmailLayout({
<Section style={baseStyles.header}>
<Img
src={brand.logoUrl || `${baseUrl}/brand/color/email/wordmark.png`}
width='107'
height='33'
height='34'
{...(hasCustomLogo ? {} : { width: '70' })}
alt={brand.name}
style={{ display: 'block' }}
style={hasCustomLogo ? { display: 'block', width: 'auto' } : { display: 'block' }}
/>
</Section>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 15 KiB