Skip to content

feat(email): Add React Email package with StandardLayout - #729

Merged
saddlepaddle merged 4 commits into
mainfrom
arrogant-sailfish-fa7e39
Jan 13, 2026
Merged

saddlepaddle merged 4 commits into
mainfrom
arrogant-sailfish-fa7e39

Conversation

@saddlepaddle

@saddlepaddle saddlepaddle commented Jan 13, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Add complete React Email package for transactional emails with StandardLayout, reusable components, and welcome template example.

What's Included

Package Structure

  • StandardLayout: Base layout with header, footer, and consistent styling
  • Components: Reusable Button and Logo components
  • Welcome Email: Example template demonstrating the pattern
  • Email Assets: Logos and social icons (PNG, hosted on marketing site)

Technical Implementation

  • React Email 5.0.7 (compatible with Bun)
  • Tailwind CSS via @react-email/tailwind
  • TypeScript config matches UI package (react-jsx transform, no React imports)
  • Environment variables managed with t3-env-core
  • Colors converted from app theme (OKLCH → hex for email compatibility)
  • Proper folder structure with co-located components

Assets

  • Header logo: Desktop cube icon (240x240)
  • Footer logo: Pixelated SUPERSET text (512x83)
  • Social icons: X, Instagram, LinkedIn (48x48, darkened to neutral-950)

Environment Setup

Loads NEXT_PUBLIC_MARKETING_URL from root .env:

  • Local dev: http://localhost:3002 (marketing app)
  • Production: https://superset.sh

Usage

import { WelcomeEmail } from "@superset/email/emails/welcome";
import { render } from "@react-email/render";

const html = render(<WelcomeEmail userName="Satya" />);
await resend.emails.send({
  from: "noreply@superset.sh",
  to: user.email,
  subject: "Welcome to Superset!",
  html,
});

Preview

Run bun dev in packages/email to preview templates at http://localhost:3000

Test Plan

  • Lint and format pass
  • TypeScript type checking passes
  • Dev server runs successfully
  • Images load correctly with environment variable
  • Welcome email renders properly

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a new email package with standardized templates, reusable UI components (layout, logo, button, footer) and a welcome onboarding email.
  • Styling
    • Consistent email styling and Tailwind-driven design tokens for brand-consistent layouts.
  • Documentation
    • New README with setup, template authoring, previewing, and testing guidance.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 13, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds a new @superset/email package containing reusable React email components (StandardLayout, Footer, Button, Logo), a Welcome email template, color tokens and env config, Tailwind setup, TypeScript configuration, package manifest, and documentation.

Changes

Cohort / File(s) Summary
Documentation
packages/email/README.md
New README describing package usage, environment variables, previewing, project structure, and templates.
Layout & Footer
packages/email/components/layout/StandardLayout/StandardLayout.tsx, packages/email/components/layout/StandardLayout/components/Footer/Footer.tsx, packages/email/components/layout/StandardLayout/components/Footer/index.ts, packages/email/components/layout/StandardLayout/index.ts, packages/email/src/components/layout/StandardLayout.tsx, packages/email/src/components/ui/Footer.tsx
Introduces StandardLayout and Footer components (preview prop, children, optional social icons, logo, legal links) for email templates.
UI Components (Button & Logo)
packages/email/components/ui/Button/Button.tsx, packages/email/components/ui/Button/index.ts, packages/email/src/components/ui/Button.tsx, packages/email/components/ui/Logo/Logo.tsx, packages/email/components/ui/Logo/index.ts, packages/email/src/components/ui/Logo.tsx
Adds email-safe Button with primary/secondary variants and Logo component sourcing image from NEXT_PUBLIC_MARKETING_URL.
Email Templates
packages/email/emails/welcome.tsx, packages/email/src/emails/welcome.tsx
New WelcomeEmail template (optional userName) using StandardLayout, heading, text, bullet list, CTA, and footer.
Library Utilities
packages/email/src/lib/colors.ts, packages/email/src/lib/env.ts
Exports frozen color tokens (colors and Color type) and env init reading NEXT_PUBLIC_MARKETING_URL via @t3-oss/env-core.
Config & Manifests
packages/email/package.json, packages/email/tsconfig.json, packages/email/tailwind.config.ts
New package manifest with exports/deps/scripts, TypeScript config extending base, and Tailwind config mapping color tokens and radii.
Barrel Exports
packages/email/src/components/index.ts
Re-exports StandardLayout, Button, Footer, and Logo for package API surface.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 ✨ A new email hop, neat and spry,
Layouts and buttons waving hi,
Logo and footer join the cheer,
Colors and config all appear,
Welcome emails ready — send with a sigh! 💌

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a React Email package with StandardLayout, which is the central feature introduced across multiple files in this PR.
Description check ✅ Passed The PR description includes all key template sections: a Summary, What's Included overview, Technical Implementation details, Environment Setup, Usage examples, Preview instructions, and a Test Plan checklist. All required information is present and well-structured.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jan 13, 2026 •

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch
  • ✅ Electric Fly.io app

Thank you for your contribution! 🎉

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🤖 Fix all issues with AI agents
In @packages/email/components/layout/StandardLayout/StandardLayout.tsx:
- Around line 1-61: Remove the dead StandardLayout implementation and its
export: delete the unused StandardLayout.tsx (the one under the
src/components/layout implementation) and remove its export entry from the
components index (the export of StandardLayout in
packages/email/src/components/index.ts); then run a repo-wide search for the
symbol StandardLayout to ensure no remaining imports reference this deleted file
and, if any are found, update them to import from the active implementation at
components/layout/StandardLayout/.

In @packages/email/emails/welcome.tsx:
- Around line 1-58: This file duplicates the canonical welcome email in
src/emails and breaks the package exports; delete
packages/email/emails/welcome.tsx entirely (remove the WelcomeEmail export and
default export in that file) and instead update
packages/email/src/emails/welcome.tsx with the preferred Tailwind implementation
if you want the new version; also refactor the repeated hardcoded color
"#515759" in the removed file by adding a single named constant (e.g., const
MUTED_COLOR = "#515759") at the top of the canonical WelcomeEmail module and
replace repeated className occurrences with that constant where inline strings
are constructed in the WelcomeEmail component and referenced by Button/Link
usage.

In @packages/email/package.json:
- Line 19: The "dev" script in package.json is fragile because the inline
grep/cut pipeline can fail silently if ../../.env is missing, the key is absent,
or the value contains '='; replace it with a robust approach: either load the
file with dotenv-cli (use dotenv -e ../../.env before running the email dev
command) or implement a safe fallback that checks for the file and variable,
redirects grep errors, and uses a default URL when missing; update the "dev"
script entry named "dev" to use one of these approaches so
NEXT_PUBLIC_MARKETING_URL is reliably set.
- Around line 12-16: There are duplicate root-level folders components/ and
emails/ that are dead copies of src/components and src/emails referenced by the
package exports ("./emails/*" -> "./src/emails/*.tsx" and "./components" ->
"./src/components/index.ts"); remove the root-level components/ and emails/
directories (keeping only src/components and src/emails), update or run a quick
repo-wide search to ensure no imports reference the removed root-level folders,
and run tests/build to verify nothing breaks.

In @packages/email/README.md:
- Around line 37-52: Add a language specifier to the fenced code block in
README.md by changing the opening ``` to ```text (i.e., replace the current
triple-backtick fence that begins the directory structure with ```text) and
ensure the closing triple-backtick remains, so the directory tree block is
rendered as plain text; locate the fenced block containing "packages/email/" and
update its opening fence accordingly.
- Line 213: Replace the bare URL in the README line that reads "Preview at
http://localhost:3000" with proper Markdown link syntax; update the text to use
a Markdown link like "Preview at [http://localhost:3000](http://localhost:3000)"
so the URL renders as a clickable link in the package README.
- Around line 35-54: The README's directory tree lists emails/ and components/
at the package root but package.json exports reference src/emails/*.tsx and
src/components/index.ts; update either the documentation or the package exports
so they match: either change README to show src/emails/ and src/components/ (and
note build output location), or modify package.json exports to point to
root-level emails/ and components/index.ts; ensure you reference the exact
export keys in package.json and the folders emails/, components/, src/emails/,
and src/components/index.ts when making the change so paths are consistent.

In @packages/email/src/components/layout/StandardLayout.tsx:
- Around line 10-11: The StandardLayout imports inconsistent Logo and Footer
implementations which causes divergence; pick the canonical, flexible
implementations (Logo/Logo.tsx that uses env.NEXT_PUBLIC_MARKETING_URL and
Footer/Footer.tsx that supports showSocial and social icons), update
StandardLayout to import those canonical components (Logo and Footer), remove or
deprecate the duplicate hardcoded Logo and non-showSocial Footer files (or
re-export the canonical ones under the old paths), and ensure the Footer
component's showSocial prop and Logo's env-driven URL are used throughout to
consolidate behavior and avoid duplicated implementations.

In @packages/email/src/components/ui/Button.tsx:
- Around line 1-44: There is a duplicate, orphaned Button implementation; remove
the unused components/ui/Button directory (the duplicate Button.tsx) and any
barrel export referencing it, leaving only the exported Button function that
uses ReactEmailButton, baseButtonStyle, primaryButtonStyle and
secondaryButtonStyle; also update any imports in the repo that pointed to the
deleted duplicate to reference the remaining Button implementation.

In @packages/email/src/components/ui/Footer.tsx:
- Around line 36-40: The Footer component currently hardcodes the year "2026"
inside the Text element (see Footer.tsx and the Text with style companyInfo);
replace the literal year with a dynamic value computed at render time (e.g., use
new Date().getFullYear()) so the displayed copyright year updates automatically
without manual changes.

In @packages/email/src/components/ui/Logo.tsx:
- Around line 1-18: This duplicates the Logo component; replace this
implementation with the consolidated version that uses the environment variable
pattern: update the Logo function to source the image URL from
env.NEXT_PUBLIC_MARKETING_URL (fallback optional), use the same width/height and
alt text as the canonical component (width 120, alt "Superset"), remove or unify
the local logoStyle so styling is consistent with the other Logo implementation,
and eliminate this duplicate file or re-export the canonical Logo component so
only one implementation is imported across the codebase.

In @packages/email/src/emails/welcome.tsx:
- Around line 77-80: The link color constant defined as link in welcome.tsx uses
the wrong hex (#966dd5); update the color to the design-system primary #323232
by changing the color property of the link object (the link constant) to
"#323232" so email links use the approved primary color and align with the
README-defined design system.
- Around line 8-11: The greeting in WelcomeEmail currently defaults userName to
"there", producing an awkward "Welcome to Superset, there!"; change the prop
default (remove the literal "there") and update the Heading render in
WelcomeEmail to only append ", {userName}" (or the name) when userName is truthy
so the heading becomes "Welcome to Superset!" when no name is provided and
"Welcome to Superset, <Name>!" when it is; adjust the WelcomeEmailProps/default
parameter and the Heading text interpolation accordingly (refer to WelcomeEmail,
userName, StandardLayout, and Heading).

In @packages/email/tailwind.config.ts:
- Around line 5-8: The Tailwind config's content array is missing the src folder
so classes in packages/email/src (e.g., components under src) won't be scanned;
update the content array in tailwind.config.ts to include the src glob (for
example add "./src/**/*.{js,jsx,ts,tsx}" and/or "./**/src/**/*.{js,jsx,ts,tsx}")
alongside the existing "./emails/**/*.{js,jsx,ts,tsx}" and
"./components/**/*.{js,jsx,ts,tsx}" entries so Tailwind picks up classes in src
components.
🧹 Nitpick comments (11)
packages/email/components/ui/Button/Button.tsx (1)

1-21: This is the duplicate Button (see previous comment).

This implementation uses Tailwind classes with semantic color tokens (bg-primary, text-foreground, border-border), which may be preferable over the inline-style version for consistency with the package's Tailwind configuration. If consolidating, consider keeping this approach.

One improvement for this file: per coding guidelines, consider using a lookup object instead of the ternary for variant styling.

Optional refactor using lookup object
+const variantStyles = {
+	primary:
+		"inline-block rounded-lg bg-primary text-white px-6 py-3 text-base font-semibold no-underline text-center",
+	secondary:
+		"inline-block rounded-lg bg-white border border-border text-foreground px-6 py-3 text-base font-semibold no-underline text-center",
+} as const;
+
 export function Button({ href, children, variant = "primary" }: ButtonProps) {
-	const className =
-		variant === "primary"
-			? "inline-block rounded-lg bg-primary text-white px-6 py-3 text-base font-semibold no-underline text-center"
-			: "inline-block rounded-lg bg-white border border-border text-foreground px-6 py-3 text-base font-semibold no-underline text-center";
+	const className = variantStyles[variant];

 	return (
 		<ReactEmailButton href={href} className={className}>
packages/email/src/lib/env.ts (1)

4-18: Consider adding URL validation and a default value for development.

The environment configuration uses z.string() which accepts any string. For a URL variable, z.string().url() would provide validation. Additionally, a .default() could help local development when the env var isn't set.

Note: The clientPrefix: "PUBLIC_" won't match variables prefixed with NEXT_PUBLIC_. If this is intentional (server-only usage), consider removing the unused clientPrefix and empty client block.

Suggested improvement
 export const env = createEnv({
 	server: {
-		NEXT_PUBLIC_MARKETING_URL: z.string(),
+		NEXT_PUBLIC_MARKETING_URL: z.string().url().default("http://localhost:3002"),
 	},
 
-	clientPrefix: "PUBLIC_",
-
-	client: {},
-
 	runtimeEnv: {
 		NEXT_PUBLIC_MARKETING_URL: process.env.NEXT_PUBLIC_MARKETING_URL,
 	},
 
 	emptyStringAsUndefined: true,
 });
packages/email/components/ui/Logo/Logo.tsx (1)

7-15: Consider adding an explicit height attribute for better email client compatibility.

Some email clients handle images better when both width and height are specified, preventing layout shifts during loading. Otherwise, the implementation looks good.

packages/email/tailwind.config.ts (1)

35-41: Consider extracting borderRadius values to named constants.

Based on coding guidelines, magic numbers should be extracted to named constants. This improves maintainability if these values need to change.

Example extraction
const BORDER_RADIUS = {
	sm: "6px",
	default: "8px",
	md: "9px",
	lg: "10px",
	xl: "12px",
} as const;

// Then in config:
borderRadius: {
	sm: BORDER_RADIUS.sm,
	DEFAULT: BORDER_RADIUS.default,
	// ...
},
packages/email/components/layout/StandardLayout/components/Footer/Footer.tsx (2)

2-2: Deep relative import path is fragile.

The path ../../../../../src/lib/env is prone to breaking if the file structure changes. Consider using a path alias (e.g., @email/lib/env) configured in tsconfig.json.


8-13: Extract static socialIcons object outside the component.

The socialIcons object is recreated on every render despite being static data. Moving it outside the function avoids unnecessary object allocations.

Suggested refactor
+const SOCIAL_ICONS = {
+	x: `${env.NEXT_PUBLIC_MARKETING_URL}/assets/emails/x.png`,
+	instagram: `${env.NEXT_PUBLIC_MARKETING_URL}/assets/emails/instagram.png`,
+	linkedin: `${env.NEXT_PUBLIC_MARKETING_URL}/assets/emails/linkedin.png`,
+} as const;
+
 export function Footer({ showSocial = true }: FooterProps) {
-	const socialIcons = {
-		x: `${env.NEXT_PUBLIC_MARKETING_URL}/assets/emails/x.png`,
-		instagram: `${env.NEXT_PUBLIC_MARKETING_URL}/assets/emails/instagram.png`,
-		linkedin: `${env.NEXT_PUBLIC_MARKETING_URL}/assets/emails/linkedin.png`,
-	};

Then reference SOCIAL_ICONS.x, etc. in the JSX.

packages/email/components/layout/StandardLayout/StandardLayout.tsx (1)

27-33: Consider extracting color tokens to a shared constant.

Per coding guidelines, hardcoded values should be extracted to named constants. These color tokens could be imported from a shared location for consistency across email components.

💡 Example extraction
// In a shared colors.ts or tokens.ts file
export const EMAIL_COLORS = {
  background: "#FFFFFF",
  foreground: "#212121",
  primary: "#323232",
  muted: "#888888",
  border: "#EBEBEB",
} as const;

Then import and use in the Tailwind config.

packages/email/src/components/index.ts (1)

1-4: Consolidate component implementations or document the intentional split.

This package maintains two parallel component trees: packages/email/components/ (used by internal email templates, with Tailwind and proper barrel exports per the React Email CLI requirement) and packages/email/src/components/ (flat structure exported via package.json). The email templates import from the root components/ directory, but the barrel export points to src/components/, creating divergent implementations.

Options:

  1. Remove src/components/ if it's no longer needed—the root components/ directory already follows the intended structure
  2. If src/components/ serves a purpose (e.g., simplified exports for internal use), document this clearly and ensure both versions remain synchronized
  3. Update the barrel export to consistently use one tree
packages/email/emails/welcome.tsx (2)

12-13: Fragile conditional relies on matching the default value.

The check userName !== "there" is coupled to the default parameter value. If the default changes, this logic silently breaks.

♻️ Suggested fix
-export function WelcomeEmail({ userName = "there" }: WelcomeEmailProps) {
+export function WelcomeEmail({ userName }: WelcomeEmailProps) {
 	return (
 		<StandardLayout preview="Welcome to Superset">
-			<Heading className="text-foreground text-[28px] font-semibold leading-tight m-0 mb-6">
-				Welcome to Superset{userName !== "there" ? `, ${userName}` : ""}
-			</Heading>
+			<Heading className="text-foreground text-[28px] font-semibold leading-tight m-0 mb-6">
+				Welcome to Superset{userName ? `, ${userName}` : ""}!
+			</Heading>

16-33: Extract repeated color value to a constant.

The color #515759 is hardcoded in five places (Lines 16, 21, 25, 28, 31). Per coding guidelines, extract magic values to named constants at module top for maintainability.

♻️ Suggested approach
const TEXT_SECONDARY = "text-[#515759]";

// Then use:
<Text className={`${TEXT_SECONDARY} text-base leading-snug m-0 mb-4`}>

Alternatively, if #515759 aligns with an existing theme token (like muted), use that instead for consistency with the design system.

packages/email/src/emails/welcome.tsx (1)

48-80: Consider co-locating styles or using shared constants.

The inline style objects are defined at the bottom of the file. For better maintainability across multiple email templates, consider:

  1. Moving styles closer to their usage, or
  2. Extracting shared styles (like paragraph, link) to a shared module in src/lib/

This would help maintain consistency as more email templates are added.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f1d1ce and 5ce4719.

⛔ Files ignored due to path filters (6)
  • apps/marketing/public/assets/emails/instagram.png is excluded by !**/*.png
  • apps/marketing/public/assets/emails/linkedin.png is excluded by !**/*.png
  • apps/marketing/public/assets/emails/logo-full.png is excluded by !**/*.png
  • apps/marketing/public/assets/emails/logo.png is excluded by !**/*.png
  • apps/marketing/public/assets/emails/x.png is excluded by !**/*.png
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • packages/email/README.md
  • packages/email/components/layout/StandardLayout/StandardLayout.tsx
  • packages/email/components/layout/StandardLayout/components/Footer/Footer.tsx
  • packages/email/components/layout/StandardLayout/components/Footer/index.ts
  • packages/email/components/layout/StandardLayout/index.ts
  • packages/email/components/ui/Button/Button.tsx
  • packages/email/components/ui/Button/index.ts
  • packages/email/components/ui/Logo/Logo.tsx
  • packages/email/components/ui/Logo/index.ts
  • packages/email/emails/welcome.tsx
  • packages/email/package.json
  • packages/email/src/components/index.ts
  • packages/email/src/components/layout/StandardLayout.tsx
  • packages/email/src/components/ui/Button.tsx
  • packages/email/src/components/ui/Footer.tsx
  • packages/email/src/components/ui/Logo.tsx
  • packages/email/src/emails/welcome.tsx
  • packages/email/src/lib/colors.ts
  • packages/email/src/lib/env.ts
  • packages/email/tailwind.config.ts
  • packages/email/tsconfig.json
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use object parameters for functions with 2+ parameters instead of positional arguments
Functions with 2+ parameters should accept a single params object with named properties for self-documentation and extensibility
Use prefixed console logging with context pattern: [domain/operation] message
Extract magic numbers and hardcoded values to named constants at module top
Use lookup objects/maps instead of repeated if (type === ...) conditionals
Avoid using any type - maintain type safety in TypeScript code
Never swallow errors silently - at minimum log them with context
Import from concrete files directly when possible - avoid barrel file abuse that creates circular dependencies
Avoid deep nesting (4+ levels) - use early returns, extract functions, and invert conditions
Use named properties in options objects instead of boolean parameters to avoid boolean blindness

Files:

  • packages/email/components/ui/Logo/index.ts
  • packages/email/src/components/ui/Button.tsx
  • packages/email/src/lib/env.ts
  • packages/email/components/layout/StandardLayout/components/Footer/Footer.tsx
  • packages/email/components/layout/StandardLayout/StandardLayout.tsx
  • packages/email/src/components/ui/Footer.tsx
  • packages/email/components/ui/Button/index.ts
  • packages/email/components/layout/StandardLayout/index.ts
  • packages/email/components/ui/Button/Button.tsx
  • packages/email/src/lib/colors.ts
  • packages/email/src/components/index.ts
  • packages/email/tailwind.config.ts
  • packages/email/src/components/ui/Logo.tsx
  • packages/email/src/components/layout/StandardLayout.tsx
  • packages/email/components/layout/StandardLayout/components/Footer/index.ts
  • packages/email/components/ui/Logo/Logo.tsx
  • packages/email/emails/welcome.tsx
  • packages/email/src/emails/welcome.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Biome for formatting and linting - run at root level with bun run lint:fix or biome check --write

Files:

  • packages/email/components/ui/Logo/index.ts
  • packages/email/src/components/ui/Button.tsx
  • packages/email/src/lib/env.ts
  • packages/email/components/layout/StandardLayout/components/Footer/Footer.tsx
  • packages/email/components/layout/StandardLayout/StandardLayout.tsx
  • packages/email/src/components/ui/Footer.tsx
  • packages/email/components/ui/Button/index.ts
  • packages/email/components/layout/StandardLayout/index.ts
  • packages/email/components/ui/Button/Button.tsx
  • packages/email/src/lib/colors.ts
  • packages/email/src/components/index.ts
  • packages/email/tailwind.config.ts
  • packages/email/src/components/ui/Logo.tsx
  • packages/email/src/components/layout/StandardLayout.tsx
  • packages/email/components/layout/StandardLayout/components/Footer/index.ts
  • packages/email/components/ui/Logo/Logo.tsx
  • packages/email/emails/welcome.tsx
  • packages/email/src/emails/welcome.tsx
**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

One component per file - do not create multi-component files

Files:

  • packages/email/src/components/ui/Button.tsx
  • packages/email/components/layout/StandardLayout/components/Footer/Footer.tsx
  • packages/email/components/layout/StandardLayout/StandardLayout.tsx
  • packages/email/src/components/ui/Footer.tsx
  • packages/email/components/ui/Button/Button.tsx
  • packages/email/src/components/ui/Logo.tsx
  • packages/email/src/components/layout/StandardLayout.tsx
  • packages/email/components/ui/Logo/Logo.tsx
  • packages/email/emails/welcome.tsx
  • packages/email/src/emails/welcome.tsx
🧠 Learnings (7)
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/*/src/components/{ui,ai-elements,react-flow}/*.tsx : Use kebab-case single files for shadcn/ui components (e.g., button.tsx, base-node.tsx) in src/components/ui/, src/components/ai-elements, and src/components/react-flow/

Applied to files:

  • packages/email/src/components/ui/Button.tsx
  • packages/email/components/ui/Button/Button.tsx
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/main/index.ts : Load environment variables from monorepo root .env in desktop app with override: true before any imports in src/main/index.ts and electron.vite.config.ts

Applied to files:

  • packages/email/src/lib/env.ts
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/*/src/components/**/[A-Z]*.tsx : Create component folder structure with one folder per component: ComponentName/ComponentName.tsx with barrel export index.ts

Applied to files:

  • packages/email/components/layout/StandardLayout/index.ts
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to **/*.{ts,tsx} : Extract magic numbers and hardcoded values to named constants at module top

Applied to files:

  • packages/email/src/lib/colors.ts
  • packages/email/tailwind.config.ts
📚 Learning: 2025-12-21T04:39:28.543Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-12-21T04:39:28.543Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : Use alias as defined in `tsconfig.json` when possible

Applied to files:

  • packages/email/tsconfig.json
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to **/*.test.{ts,tsx} : Co-locate tests with implementation files using .test.ts or .test.tsx suffix

Applied to files:

  • packages/email/tsconfig.json
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/*/src/components/**/*.{ts,tsx} : Co-locate component dependencies: utils, hooks, constants, config, tests, stories in the same folder as the component using them

Applied to files:

  • packages/email/components/layout/StandardLayout/components/Footer/index.ts
🧬 Code graph analysis (8)
packages/email/src/components/ui/Button.tsx (3)
packages/email/components/ui/Button/Button.tsx (1)
  • Button (10-21)
packages/email/components/ui/Button/index.ts (1)
  • Button (1-1)
packages/email/src/components/index.ts (1)
  • Button (2-2)
packages/email/components/layout/StandardLayout/components/Footer/Footer.tsx (2)
packages/email/src/components/ui/Footer.tsx (1)
  • Footer (4-43)
packages/email/src/lib/env.ts (1)
  • env (4-18)
packages/email/components/layout/StandardLayout/StandardLayout.tsx (4)
packages/email/src/components/layout/StandardLayout.tsx (1)
  • StandardLayout (18-42)
packages/email/components/ui/Logo/Logo.tsx (1)
  • Logo (7-15)
packages/email/components/layout/StandardLayout/components/Footer/Footer.tsx (1)
  • Footer (8-106)
packages/email/src/components/ui/Footer.tsx (1)
  • Footer (4-43)
packages/email/tailwind.config.ts (1)
packages/email/src/lib/colors.ts (1)
  • colors (6-22)
packages/email/src/components/ui/Logo.tsx (1)
packages/email/components/ui/Logo/Logo.tsx (1)
  • Logo (7-15)
packages/email/src/components/layout/StandardLayout.tsx (2)
packages/email/src/components/ui/Logo.tsx (1)
  • Logo (3-13)
packages/email/src/components/ui/Footer.tsx (1)
  • Footer (4-43)
packages/email/components/ui/Logo/Logo.tsx (4)
packages/email/components/ui/Logo/index.ts (1)
  • Logo (1-1)
packages/email/src/components/index.ts (1)
  • Logo (4-4)
packages/email/src/components/ui/Logo.tsx (1)
  • Logo (3-13)
packages/email/src/lib/env.ts (1)
  • env (4-18)
packages/email/emails/welcome.tsx (3)
packages/email/components/layout/StandardLayout/StandardLayout.tsx (1)
  • StandardLayout (19-61)
packages/email/components/ui/Button/Button.tsx (1)
  • Button (10-21)
packages/email/src/components/ui/Button.tsx (1)
  • Button (10-19)
🪛 markdownlint-cli2 (0.18.1)
packages/email/README.md

37-37: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


213-213: Bare URL used

(MD034, no-bare-urls)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Deploy Admin
  • GitHub Check: Deploy Marketing
  • GitHub Check: Deploy Web
  • GitHub Check: Deploy API
  • GitHub Check: Deploy Docs
  • GitHub Check: Build
🔇 Additional comments (11)
packages/email/components/ui/Button/index.ts (1)

1-1: LGTM!

Standard barrel export pattern for the Button component.

packages/email/tsconfig.json (1)

1-20: LGTM!

The TypeScript configuration is well-structured with appropriate settings for a React Email package. The react-jsx transform aligns with the PR's goal of not requiring React imports.

Note: The @/* path alias only covers ./src/*, so files in components/ and emails/ directories will need to use relative imports to reference each other or files in src/. This appears intentional given the package structure.

packages/email/src/components/ui/Footer.tsx (1)

1-43: Component structure looks good.

The Footer component is well-organized with clear sections (divider, logo, tagline, legal links, company info) and properly separated style definitions. The use of module-level style objects is appropriate for React Email components.

packages/email/components/ui/Logo/index.ts (1)

1-1: LGTM!

Clean re-export for the Logo component.

packages/email/src/lib/colors.ts (1)

1-24: LGTM!

Well-structured color tokens with good documentation. The as const assertion provides proper type narrowing, and the exported Color type enables type-safe color key usage.

packages/email/components/layout/StandardLayout/components/Footer/Footer.tsx (1)

1-106: Verify if Footer component duplication exists at the claimed location.

The review mentions a separate Footer component at packages/email/src/components/ui/Footer.tsx, but this claim requires verification against the actual codebase to confirm both files exist and whether they truly represent duplicate implementations or serve distinct purposes. Additionally, per the codebase guidelines, component dependencies should be co-located in the same folder as the component, which may clarify the intended separation if both footers exist.

packages/email/components/layout/StandardLayout/components/Footer/index.ts (1)

1-1: LGTM!

Clean barrel export following the co-located component structure pattern.

packages/email/components/layout/StandardLayout/index.ts (1)

1-1: LGTM!

Standard barrel export following the component folder structure pattern.

packages/email/src/components/layout/StandardLayout.tsx (1)

44-73: Style constants are well-structured.

The inline style objects follow the guideline of extracting magic values to named constants at module level. The as const assertion on Line 55 for overflow: "clip" is appropriate for TypeScript's type narrowing.

packages/email/package.json (1)

31-31: Verify Zod 4.x compatibility.

Zod 4 is a major version with breaking changes from Zod 3. Ensure all schema definitions in this package are compatible with Zod 4 API changes (e.g., unified error parameter, z.record() now requiring both key and value schemas).

packages/email/README.md (1)

1-216: LGTM – comprehensive documentation.

The README provides excellent guidance on image setup, environment configuration, component usage, and the design system. The example templates and integration code are helpful for onboarding developers.

Comment on lines +1 to +61
import {
Body,
Container,
Head,
Html,
Preview,
Section,
} from "@react-email/components";
import { Tailwind } from "@react-email/tailwind";
import type { ReactNode } from "react";
import { Logo } from "../../ui/Logo";
import { Footer } from "./components/Footer";

interface StandardLayoutProps {
preview: string;
children: ReactNode;
}

export function StandardLayout({ preview, children }: StandardLayoutProps) {
return (
<Html>
<Head />
<Tailwind
config={{
theme: {
extend: {
colors: {
background: "#FFFFFF",
foreground: "#212121",
primary: "#323232",
muted: "#888888",
border: "#EBEBEB",
},
},
},
}}
>
<Body className="bg-background font-sans">
<Preview>{preview}</Preview>
<Container className="mx-auto my-8 max-w-[600px] rounded-xl border border-border overflow-hidden">
<Section className="bg-background px-9 pt-6">
<Logo />
</Section>

<Section
className="h-px mx-9 my-7 border-t border-transparent"
style={{
background:
"radial-gradient(circle farthest-side, #dfe1e4, #edeff5)",
}}
/>

<Section className="bg-background px-9 pb-9">{children}</Section>

<Footer />
</Container>
</Body>
</Tailwind>
</Html>
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find both StandardLayout files mentioned in the comment
fd -t f "StandardLayout" -e ".tsx" -e ".ts" | head -20

Repository: superset-sh/superset

Length of output: 186


🏁 Script executed:

# Search for all imports of StandardLayout to see which version is used
rg "from.*StandardLayout" --type=ts --type=tsx -g '!*.d.ts' | head -30

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

# Check if packages/email/src/components/layout/StandardLayout.tsx exists
find . -path "*/packages/email/src/components/layout/StandardLayout*" -o -path "*/packages/email/components/layout/StandardLayout*"

Repository: superset-sh/superset

Length of output: 584


🏁 Script executed:

# Read the src version to compare with the one under review
cat -n packages/email/src/components/layout/StandardLayout.tsx

Repository: superset-sh/superset

Length of output: 1961


🏁 Script executed:

# Search for where each StandardLayout is imported from
rg "StandardLayout" --type typescript -A 2 -B 2 | grep -E "(from|import|StandardLayout)" | head -40

Repository: superset-sh/superset

Length of output: 98


🏁 Script executed:

# Check the Footer component imports from both versions
rg "Footer" packages/email/src/components/layout/StandardLayout.tsx -A 2 -B 2

Repository: superset-sh/superset

Length of output: 331


🏁 Script executed:

# Check the Footer component in the current file (under review)
rg "Footer" packages/email/components/layout/StandardLayout/StandardLayout.tsx -A 2 -B 2

Repository: superset-sh/superset

Length of output: 343


🏁 Script executed:

# Search for imports of StandardLayout from each location
rg "from.*StandardLayout" --type ts --type js -i

Repository: superset-sh/superset

Length of output: 372


🏁 Script executed:

# Check if there's an index.ts that re-exports from the components version
cat -n packages/email/components/layout/StandardLayout/index.ts

Repository: superset-sh/superset

Length of output: 121


🏁 Script executed:

# Check if the src version is used anywhere
rg "from.*src/components/layout/StandardLayout"

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

# Check what Footer component each one uses
cat -n packages/email/components/layout/StandardLayout/components/Footer/Footer.tsx | head -30

Repository: superset-sh/superset

Length of output: 1120


🏁 Script executed:

# Compare with the ui Footer
cat -n packages/email/src/components/ui/Footer.tsx | head -50

Repository: superset-sh/superset

Length of output: 1452


🏁 Script executed:

# Check if the src StandardLayout is imported anywhere
rg "src/components.*StandardLayout" --type ts --type js

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

# Check all files in src/components to see what's exported
cat -n packages/email/src/components/index.ts

Repository: superset-sh/superset

Length of output: 259


🏁 Script executed:

# Verify welcome.tsx uses the components version
cat -n packages/email/emails/welcome.tsx

Repository: superset-sh/superset

Length of output: 2152


🏁 Script executed:

# Check if there are other email templates using the src version
fd -e "tsx" -e "ts" packages/email/emails/ | head -20

Repository: superset-sh/superset

Length of output: 457


🏁 Script executed:

# Search all email templates to see which StandardLayout they import
rg "StandardLayout" packages/email/emails/ -B 2 -A 2

Repository: superset-sh/superset

Length of output: 1064


Remove dead code: packages/email/src/components/layout/StandardLayout.tsx is unused.

The StandardLayout.tsx in packages/email/src/components/layout/ is exported from packages/email/src/components/index.ts but is not imported anywhere in the codebase. Email templates import from packages/email/components/layout/StandardLayout/ (the active version under review), making the src version dead code. Remove the unused file to eliminate confusion and reduce maintenance surface.

🤖 Prompt for AI Agents
In @packages/email/components/layout/StandardLayout/StandardLayout.tsx around
lines 1 - 61, Remove the dead StandardLayout implementation and its export:
delete the unused StandardLayout.tsx (the one under the src/components/layout
implementation) and remove its export entry from the components index (the
export of StandardLayout in packages/email/src/components/index.ts); then run a
repo-wide search for the symbol StandardLayout to ensure no remaining imports
reference this deleted file and, if any are found, update them to import from
the active implementation at components/layout/StandardLayout/.

Comment on lines +1 to +58
import { Heading, Link, Text } from "@react-email/components";
import { StandardLayout } from "../components/layout/StandardLayout";
import { Button } from "../components/ui/Button";

interface WelcomeEmailProps {
userName?: string;
}

export function WelcomeEmail({ userName = "there" }: WelcomeEmailProps) {
return (
<StandardLayout preview="Welcome to Superset">
<Heading className="text-foreground text-[28px] font-semibold leading-tight m-0 mb-6">
Welcome to Superset{userName !== "there" ? `, ${userName}` : ""}
</Heading>

<Text className="text-[#515759] text-base leading-snug m-0 mb-4">
Thanks for signing up. Superset helps you automate your workflows with
AI-powered task management.
</Text>

<Text className="text-[#515759] text-base leading-snug m-0 mb-4">
Get started:
</Text>

<Text className="text-[#515759] text-base leading-7 m-0 mb-2">
• Create your first workspace
</Text>
<Text className="text-[#515759] text-base leading-7 m-0 mb-2">
• Connect your tools and integrations
</Text>
<Text className="text-[#515759] text-base leading-7 m-0 mb-6">
• Set up automated workflows
</Text>

<Button href="https://app.superset.sh/onboarding">Get Started</Button>

<Text className="text-muted text-sm leading-snug m-0 mt-6">
Questions? Check out our{" "}
<Link
href="https://superset.sh/docs"
className="text-primary no-underline"
>
docs
</Link>{" "}
or{" "}
<Link
href="https://superset.sh/support"
className="text-primary no-underline"
>
contact support
</Link>
.
</Text>
</StandardLayout>
);
}

export default WelcomeEmail;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify both welcome.tsx files exist and compare their differences
echo "=== Files in emails/ ==="
fd -t f "welcome.tsx" packages/email/

echo -e "\n=== Checking package.json exports ==="
cat packages/email/package.json | grep -A 10 '"exports"'

echo -e "\n=== Content of packages/email/emails/welcome.tsx (first 20 lines) ==="
head -20 packages/email/emails/welcome.tsx

echo -e "\n=== Content of packages/email/src/emails/welcome.tsx (first 20 lines) ==="
head -20 packages/email/src/emails/welcome.tsx 2>/dev/null || echo "File not found at packages/email/src/emails/welcome.tsx"

echo -e "\n=== Check full directory structure ==="
find packages/email -name "welcome.tsx" -type f

Repository: superset-sh/superset

Length of output: 2258


🏁 Script executed:

#!/bin/bash
# Get full content of both files to compare
echo "=== Full content of packages/email/emails/welcome.tsx ==="
wc -l packages/email/emails/welcome.tsx

echo -e "\n=== Full content of packages/email/src/emails/welcome.tsx ==="
wc -l packages/email/src/emails/welcome.tsx

echo -e "\n=== Complete diff ==="
diff -u packages/email/src/emails/welcome.tsx packages/email/emails/welcome.tsx || true

Repository: superset-sh/superset

Length of output: 3819


Remove this file – it duplicates src/emails/welcome.tsx and violates the package exports structure.

Two welcome email implementations exist:

  • packages/email/emails/welcome.tsx (current, Tailwind-based, 58 lines)
  • packages/email/src/emails/welcome.tsx (older, inline styles, 80 lines)

The package.json exports declare "./emails/*": "./src/emails/*.tsx", making src/emails/ the canonical location. This file should not exist at packages/email/emails/welcome.tsx. Delete this file and update src/emails/welcome.tsx if the new implementation is preferred, or keep the existing one.

Additionally, the hardcoded color value #515759 is repeated multiple times in className strings. Extract it to a named constant at the module top.

🤖 Prompt for AI Agents
In @packages/email/emails/welcome.tsx around lines 1 - 58, This file duplicates
the canonical welcome email in src/emails and breaks the package exports; delete
packages/email/emails/welcome.tsx entirely (remove the WelcomeEmail export and
default export in that file) and instead update
packages/email/src/emails/welcome.tsx with the preferred Tailwind implementation
if you want the new version; also refactor the repeated hardcoded color
"#515759" in the removed file by adding a single named constant (e.g., const
MUTED_COLOR = "#515759") at the top of the canonical WelcomeEmail module and
replace repeated className occurrences with that constant where inline strings
are constructed in the WelcomeEmail component and referenced by Button/Link
usage.

Comment on lines +12 to +16
"exports": {
"./emails/*": "./src/emails/*.tsx",
"./components": "./src/components/index.ts",
"./lib/*": "./src/lib/*.ts"
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the actual folder structure comprehensively
echo "=== Directory structure in packages/email ==="
find packages/email -type f -name "*.tsx" | sort

echo ""
echo "=== Full packages/email directory tree ==="
find packages/email -type d | sort

Repository: superset-sh/superset

Length of output: 1181


Remove duplicate components/ and emails/ folders at the package root.

The exports map only to ./src/emails/* and ./src/components, but duplicate folders exist at the package root (packages/email/components/ and packages/email/emails/) with identical content. These root-level folders are unreferenced dead code. Keep only the src/ versions and delete the root-level duplicates.

🤖 Prompt for AI Agents
In @packages/email/package.json around lines 12 - 16, There are duplicate
root-level folders components/ and emails/ that are dead copies of
src/components and src/emails referenced by the package exports ("./emails/*" ->
"./src/emails/*.tsx" and "./components" -> "./src/components/index.ts"); remove
the root-level components/ and emails/ directories (keeping only src/components
and src/emails), update or run a quick repo-wide search to ensure no imports
reference the removed root-level folders, and run tests/build to verify nothing
breaks.

},
"private": true,
"scripts": {
"dev": "NEXT_PUBLIC_MARKETING_URL=$(grep NEXT_PUBLIC_MARKETING_URL ../../.env | cut -d '=' -f2) email dev",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Dev script is fragile – fails silently if .env is missing.

The shell command grep NEXT_PUBLIC_MARKETING_URL ../../.env | cut -d '=' -f2 will fail silently or produce unexpected behavior if:

  • The .env file doesn't exist
  • The variable isn't defined
  • The value contains = characters

Consider adding error handling or using a tool like dotenv-cli.

♻️ Alternative approaches

Option 1: Use dotenv-cli

"dev": "dotenv -e ../../.env -- email dev"

Option 2: Add fallback

"dev": "NEXT_PUBLIC_MARKETING_URL=${NEXT_PUBLIC_MARKETING_URL:-$(grep NEXT_PUBLIC_MARKETING_URL ../../.env 2>/dev/null | cut -d '=' -f2 || echo 'http://localhost:3002')} email dev"
🤖 Prompt for AI Agents
In @packages/email/package.json at line 19, The "dev" script in package.json is
fragile because the inline grep/cut pipeline can fail silently if ../../.env is
missing, the key is absent, or the value contains '='; replace it with a robust
approach: either load the file with dotenv-cli (use dotenv -e ../../.env before
running the email dev command) or implement a safe fallback that checks for the
file and variable, redirects grep errors, and uses a default URL when missing;
update the "dev" script entry named "dev" to use one of these approaches so
NEXT_PUBLIC_MARKETING_URL is reliably set.

Comment thread packages/email/README.md
Comment on lines +35 to +54
## Structure

```
packages/email/
├── emails/ # Email templates (create new emails here)
│ └── welcome.tsx # Example welcome email
├── components/ # Reusable components
│ ├── layout/
│ │ └── StandardLayout/ # Base layout with header/footer
│ │ ├── StandardLayout.tsx
│ │ └── components/
│ │ └── Footer/ # Footer component (co-located)
│ └── ui/
│ ├── Button/ # Reusable button component
│ └── Logo/ # Header logo component
└── src/ # Source files
└── lib/ # Utilities (colors, env config)
```

**Note**: React Email CLI expects `emails/` and `components/` at the package root.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Documentation structure doesn't match package.json exports.

The documented structure shows emails/ and components/ at the package root, but package.json exports point to src/emails/*.tsx and src/components/index.ts. This inconsistency will confuse developers trying to use or extend the package.

Consider aligning the documentation with the actual export paths, or vice versa.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

37-37: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In @packages/email/README.md around lines 35 - 54, The README's directory tree
lists emails/ and components/ at the package root but package.json exports
reference src/emails/*.tsx and src/components/index.ts; update either the
documentation or the package exports so they match: either change README to show
src/emails/ and src/components/ (and note build output location), or modify
package.json exports to point to root-level emails/ and components/index.ts;
ensure you reference the exact export keys in package.json and the folders
emails/, components/, src/emails/, and src/components/index.ts when making the
change so paths are consistent.

Comment on lines +36 to +40
<Text style={companyInfo}>
© 2026 Superset. All rights reserved.
<br />
123 Main Street, San Francisco, CA 94105
</Text>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded copyright year will become stale.

The copyright year 2026 is hardcoded and will need manual updates each year. Consider using a dynamic year.

Suggested fix
 			{/* Company Info */}
 			<Text style={companyInfo}>
-				© 2026 Superset. All rights reserved.
+				© {new Date().getFullYear()} Superset. All rights reserved.
 				<br />
 				123 Main Street, San Francisco, CA 94105
 			</Text>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Text style={companyInfo}>
© 2026 Superset. All rights reserved.
<br />
123 Main Street, San Francisco, CA 94105
</Text>
<Text style={companyInfo}>
© {new Date().getFullYear()} Superset. All rights reserved.
<br />
123 Main Street, San Francisco, CA 94105
</Text>
🤖 Prompt for AI Agents
In @packages/email/src/components/ui/Footer.tsx around lines 36 - 40, The Footer
component currently hardcodes the year "2026" inside the Text element (see
Footer.tsx and the Text with style companyInfo); replace the literal year with a
dynamic value computed at render time (e.g., use new Date().getFullYear()) so
the displayed copyright year updates automatically without manual changes.

Comment on lines +1 to +18
import { Img } from "@react-email/components";

export function Logo() {
return (
<Img
src="https://superset.sh/logo.png"
alt="Superset Logo"
width="100"
height="auto"
style={logoStyle}
/>
);
}

const logoStyle = {
display: "block",
maxWidth: "130px",
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all Logo component definitions and usages
echo "=== Logo component files ==="
fd -e tsx -e ts Logo

echo -e "\n=== Logo imports across the package ==="
rg -n "from.*Logo" packages/email/

echo -e "\n=== Check if packages/email/components/ui/Logo/Logo.tsx exists ==="
ls -la packages/email/components/ui/Logo/ 2>/dev/null || echo "Path does not exist"

echo -e "\n=== Check directory structure of packages/email ==="
find packages/email -type f -name "*.tsx" -o -name "*.ts" | grep -i logo

Repository: superset-sh/superset

Length of output: 1203


🏁 Script executed:

#!/bin/bash
echo "=== Content of packages/email/components/ui/Logo/Logo.tsx ==="
cat -n packages/email/components/ui/Logo/Logo.tsx

echo -e "\n=== Content of packages/email/src/components/ui/Logo.tsx ==="
cat -n packages/email/src/components/ui/Logo.tsx

Repository: superset-sh/superset

Length of output: 993


🏁 Script executed:

#!/bin/bash
echo "=== Check what imports from src/components/index.ts exports ==="
rg -n "Logo" packages/email/src/components/index.ts -B2 -A2

echo -e "\n=== Check which files import Logo from src path ==="
rg -n "from.*src/components" packages/email/ | grep -i logo

echo -e "\n=== Check which files import Logo from components path ==="
rg -n "from.*components/ui/Logo" packages/email/ | grep -v "src/"

echo -e "\n=== Check StandardLayout files (found in imports) ==="
echo "--- packages/email/src/components/layout/StandardLayout.tsx ---"
cat -n packages/email/src/components/layout/StandardLayout.tsx | head -20

echo -e "\n--- packages/email/components/layout/StandardLayout/StandardLayout.tsx ---"
cat -n packages/email/components/layout/StandardLayout/StandardLayout.tsx | head -20

Repository: superset-sh/superset

Length of output: 1646


Remove or consolidate duplicate Logo component implementation.

This file duplicates packages/email/components/ui/Logo/Logo.tsx with incompatible differences:

Aspect This file components/ui/Logo/Logo.tsx
URL Hardcoded https://superset.sh/logo.png Uses env.NEXT_PUBLIC_MARKETING_URL
Width 100 120
Alt "Superset Logo" "Superset"
Style Has logoStyle object None

The hardcoded URL contradicts the environment variable approach used elsewhere. Both versions are actively imported—consolidate into a single implementation using the environment variable pattern.

🤖 Prompt for AI Agents
In @packages/email/src/components/ui/Logo.tsx around lines 1 - 18, This
duplicates the Logo component; replace this implementation with the consolidated
version that uses the environment variable pattern: update the Logo function to
source the image URL from env.NEXT_PUBLIC_MARKETING_URL (fallback optional), use
the same width/height and alt text as the canonical component (width 120, alt
"Superset"), remove or unify the local logoStyle so styling is consistent with
the other Logo implementation, and eliminate this duplicate file or re-export
the canonical Logo component so only one implementation is imported across the
codebase.

Comment on lines +8 to +11
export function WelcomeEmail({ userName = "there" }: WelcomeEmailProps) {
return (
<StandardLayout preview="Welcome to Superset! Let's get you started.">
<Heading style={heading}>Welcome to Superset, {userName}!</Heading>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Awkward greeting when no name is provided.

When userName defaults to "there", the greeting reads "Welcome to Superset, there!" which sounds unnatural. Consider conditionally showing the name only when explicitly provided.

♻️ Suggested fix
-export function WelcomeEmail({ userName = "there" }: WelcomeEmailProps) {
+export function WelcomeEmail({ userName }: WelcomeEmailProps) {
 	return (
 		<StandardLayout preview="Welcome to Superset! Let's get you started.">
-			<Heading style={heading}>Welcome to Superset, {userName}!</Heading>
+			<Heading style={heading}>
+				{userName ? `Welcome to Superset, ${userName}!` : "Welcome to Superset!"}
+			</Heading>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function WelcomeEmail({ userName = "there" }: WelcomeEmailProps) {
return (
<StandardLayout preview="Welcome to Superset! Let's get you started.">
<Heading style={heading}>Welcome to Superset, {userName}!</Heading>
export function WelcomeEmail({ userName }: WelcomeEmailProps) {
return (
<StandardLayout preview="Welcome to Superset! Let's get you started.">
<Heading style={heading}>
{userName ? `Welcome to Superset, ${userName}!` : "Welcome to Superset!"}
</Heading>
🤖 Prompt for AI Agents
In @packages/email/src/emails/welcome.tsx around lines 8 - 11, The greeting in
WelcomeEmail currently defaults userName to "there", producing an awkward
"Welcome to Superset, there!"; change the prop default (remove the literal
"there") and update the Heading render in WelcomeEmail to only append ",
{userName}" (or the name) when userName is truthy so the heading becomes
"Welcome to Superset!" when no name is provided and "Welcome to Superset,
<Name>!" when it is; adjust the WelcomeEmailProps/default parameter and the
Heading text interpolation accordingly (refer to WelcomeEmail, userName,
StandardLayout, and Heading).

Comment on lines +77 to +80
const link = {
color: "#966dd5",
textDecoration: "none",
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Link color doesn't match the design system.

The link color #966dd5 (purple) doesn't align with the design system colors documented in the README, which specifies Primary as #323232. This creates visual inconsistency across emails.

♻️ Suggested fix
 const link = {
-	color: "#966dd5",
+	color: "#323232", // Primary color from design system
 	textDecoration: "none",
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const link = {
color: "#966dd5",
textDecoration: "none",
};
const link = {
color: "#323232", // Primary color from design system
textDecoration: "none",
};
🤖 Prompt for AI Agents
In @packages/email/src/emails/welcome.tsx around lines 77 - 80, The link color
constant defined as link in welcome.tsx uses the wrong hex (#966dd5); update the
color to the design-system primary #323232 by changing the color property of the
link object (the link constant) to "#323232" so email links use the approved
primary color and align with the README-defined design system.

Comment on lines +5 to +8
content: [
"./emails/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}",
],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Content paths may miss components in the src folder.

The content array includes ./emails/**/* and ./components/**/* but not ./src/**/*. Components like those in packages/email/src/components/ won't have their Tailwind classes detected.

Suggested fix
 	content: [
 		"./emails/**/*.{js,jsx,ts,tsx}",
 		"./components/**/*.{js,jsx,ts,tsx}",
+		"./src/**/*.{js,jsx,ts,tsx}",
 	],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
content: [
"./emails/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}",
],
content: [
"./emails/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}",
"./src/**/*.{js,jsx,ts,tsx}",
],
🤖 Prompt for AI Agents
In @packages/email/tailwind.config.ts around lines 5 - 8, The Tailwind config's
content array is missing the src folder so classes in packages/email/src (e.g.,
components under src) won't be scanned; update the content array in
tailwind.config.ts to include the src glob (for example add
"./src/**/*.{js,jsx,ts,tsx}" and/or "./**/src/**/*.{js,jsx,ts,tsx}") alongside
the existing "./emails/**/*.{js,jsx,ts,tsx}" and
"./components/**/*.{js,jsx,ts,tsx}" entries so Tailwind picks up classes in src
components.

saddlepaddle and others added 4 commits January 12, 2026 18:57
…template

Add complete React Email package for transactional emails with:
- StandardLayout with header, content area, and footer
- Tailwind CSS integration via @react-email/tailwind
- Reusable Button and Logo components
- Welcome email template as example
- Email assets (logos, social icons) hosted on marketing site
- Environment variable management with t3-env-core
- Proper folder structure following repo conventions

Technical details:
- Uses React Email 5.0.7 (compatible with Bun)
- Loads NEXT_PUBLIC_MARKETING_URL from root .env
- TypeScript config matches UI package (react-jsx transform)
- Colors converted from app theme (OKLCH to hex)
- Co-located components (Footer under StandardLayout)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Preserve transparency while darkening icon colors to #0a0a0a

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @packages/email/README.md:
- Around line 211-215: Replace the bare URL "Preview at http://localhost:3000"
with a proper Markdown link or fenced URL to satisfy MD034; e.g., change the
line in the README where it currently reads "Preview at http://localhost:3000"
to "Preview at <http://localhost:3000>" or "Preview at
[http://localhost:3000](http://localhost:3000)" so the localhost preview link is
not a bare URL.
- Around line 37-52: The README's directory-tree fenced block is missing a
language hint (MD040); update the opening fence for the tree block in
packages/email/ README.md from ``` to ```text so the directory listing is a
typed code fence (i.e., change the untyped fenced code block containing the
packages/email/ tree to use ```text).
- Around line 113-114: Remove the package-manager-specific parenthetical "(if
using npm)" from the preview sentence and reword so it states that templates in
the emails/ directory are automatically discovered by the preview server/CLI
file scanner regardless of package manager; ensure the sentence references the
emails/ directory and the preview server/CLI file scanner (remove "(if using
npm)" and replace with a concise universal phrasing).

In @packages/email/src/components/layout/StandardLayout.tsx:
- Around line 63-68: The divider style object named "divider" uses a
radial-gradient that many email clients ignore; add a solid fallback by adding
backgroundColor: "#edeff5" (or the desired hex) alongside the existing
background property so clients that don't support gradients will use the solid
color; update the "divider" object in StandardLayout.tsx to include
backgroundColor before/with background.
🧹 Nitpick comments (3)
packages/email/components/ui/Button/Button.tsx (1)

10-20: Consider using a lookup object for variant styles.

A style map would improve extensibility if more variants are added and aligns with the guideline to prefer lookup objects over conditionals. As per coding guidelines, this also allows extracting the hardcoded class strings to named constants at module top.

♻️ Suggested refactor
+const VARIANT_CLASSES = {
+	primary:
+		"inline-block rounded-lg bg-primary text-white px-6 py-3 text-base font-semibold no-underline text-center",
+	secondary:
+		"inline-block rounded-lg bg-white border border-border text-foreground px-6 py-3 text-base font-semibold no-underline text-center",
+} as const;
+
 export function Button({ href, children, variant = "primary" }: ButtonProps) {
-	const className =
-		variant === "primary"
-			? "inline-block rounded-lg bg-primary text-white px-6 py-3 text-base font-semibold no-underline text-center"
-			: "inline-block rounded-lg bg-white border border-border text-foreground px-6 py-3 text-base font-semibold no-underline text-center";
-
 	return (
-		<ReactEmailButton href={href} className={className}>
+		<ReactEmailButton href={href} className={VARIANT_CLASSES[variant]}>
 			{children}
 		</ReactEmailButton>
 	);
 }
packages/email/src/lib/env.ts (1)

4-18: Consider adding URL validation to the schema.

The NEXT_PUBLIC_MARKETING_URL is validated only as a string. Since this is used to construct asset URLs throughout the email components, consider using z.string().url() to catch misconfiguration early and prevent broken image links in emails.

✨ Suggested improvement
 server: {
-  NEXT_PUBLIC_MARKETING_URL: z.string(),
+  NEXT_PUBLIC_MARKETING_URL: z.string().url(),
 },
packages/email/components/layout/StandardLayout/components/Footer/Footer.tsx (1)

9-13: Extract URLs to module-level constants.

Per coding guidelines, hardcoded values should be extracted to named constants at module top. The social icon paths and social media URLs are repeated inline.

✨ Suggested refactor
 import { Hr, Img, Link, Section, Text } from "@react-email/components";
 import { env } from "../../../../../src/lib/env";
 
+const SOCIAL_LINKS = {
+	x: "https://x.com/superset_sh",
+	instagram: "https://instagram.com/superset",
+	linkedin: "https://www.linkedin.com/company/superset-sh",
+} as const;
+
+const ASSET_PATHS = {
+	xIcon: "/assets/emails/x.png",
+	instagramIcon: "/assets/emails/instagram.png",
+	linkedinIcon: "/assets/emails/linkedin.png",
+	logoFull: "/assets/emails/logo-full.png",
+} as const;
+
 interface FooterProps {
 	showSocial?: boolean;
 }
 
 export function Footer({ showSocial = true }: FooterProps) {
-	const socialIcons = {
-		x: `${env.NEXT_PUBLIC_MARKETING_URL}/assets/emails/x.png`,
-		instagram: `${env.NEXT_PUBLIC_MARKETING_URL}/assets/emails/instagram.png`,
-		linkedin: `${env.NEXT_PUBLIC_MARKETING_URL}/assets/emails/linkedin.png`,
-	};
+	const baseUrl = env.NEXT_PUBLIC_MARKETING_URL;
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ce4719 and 124859b.

⛔ Files ignored due to path filters (6)
  • apps/marketing/public/assets/emails/instagram.png is excluded by !**/*.png
  • apps/marketing/public/assets/emails/linkedin.png is excluded by !**/*.png
  • apps/marketing/public/assets/emails/logo-full.png is excluded by !**/*.png
  • apps/marketing/public/assets/emails/logo.png is excluded by !**/*.png
  • apps/marketing/public/assets/emails/x.png is excluded by !**/*.png
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • packages/email/README.md
  • packages/email/components/layout/StandardLayout/StandardLayout.tsx
  • packages/email/components/layout/StandardLayout/components/Footer/Footer.tsx
  • packages/email/components/layout/StandardLayout/components/Footer/index.ts
  • packages/email/components/layout/StandardLayout/index.ts
  • packages/email/components/ui/Button/Button.tsx
  • packages/email/components/ui/Button/index.ts
  • packages/email/components/ui/Logo/Logo.tsx
  • packages/email/components/ui/Logo/index.ts
  • packages/email/emails/welcome.tsx
  • packages/email/package.json
  • packages/email/src/components/index.ts
  • packages/email/src/components/layout/StandardLayout.tsx
  • packages/email/src/components/ui/Button.tsx
  • packages/email/src/components/ui/Footer.tsx
  • packages/email/src/components/ui/Logo.tsx
  • packages/email/src/emails/welcome.tsx
  • packages/email/src/lib/colors.ts
  • packages/email/src/lib/env.ts
  • packages/email/tailwind.config.ts
  • packages/email/tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (14)
  • packages/email/components/ui/Button/index.ts
  • packages/email/components/layout/StandardLayout/components/Footer/index.ts
  • packages/email/components/ui/Logo/Logo.tsx
  • packages/email/emails/welcome.tsx
  • packages/email/components/layout/StandardLayout/StandardLayout.tsx
  • packages/email/components/layout/StandardLayout/index.ts
  • packages/email/src/components/ui/Button.tsx
  • packages/email/src/components/index.ts
  • packages/email/tailwind.config.ts
  • packages/email/src/components/ui/Logo.tsx
  • packages/email/src/emails/welcome.tsx
  • packages/email/src/components/ui/Footer.tsx
  • packages/email/components/ui/Logo/index.ts
  • packages/email/package.json
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use object parameters for functions with 2+ parameters instead of positional arguments
Functions with 2+ parameters should accept a single params object with named properties for self-documentation and extensibility
Use prefixed console logging with context pattern: [domain/operation] message
Extract magic numbers and hardcoded values to named constants at module top
Use lookup objects/maps instead of repeated if (type === ...) conditionals
Avoid using any type - maintain type safety in TypeScript code
Never swallow errors silently - at minimum log them with context
Import from concrete files directly when possible - avoid barrel file abuse that creates circular dependencies
Avoid deep nesting (4+ levels) - use early returns, extract functions, and invert conditions
Use named properties in options objects instead of boolean parameters to avoid boolean blindness

Files:

  • packages/email/src/lib/env.ts
  • packages/email/components/layout/StandardLayout/components/Footer/Footer.tsx
  • packages/email/components/ui/Button/Button.tsx
  • packages/email/src/components/layout/StandardLayout.tsx
  • packages/email/src/lib/colors.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Biome for formatting and linting - run at root level with bun run lint:fix or biome check --write

Files:

  • packages/email/src/lib/env.ts
  • packages/email/components/layout/StandardLayout/components/Footer/Footer.tsx
  • packages/email/components/ui/Button/Button.tsx
  • packages/email/src/components/layout/StandardLayout.tsx
  • packages/email/src/lib/colors.ts
**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

One component per file - do not create multi-component files

Files:

  • packages/email/components/layout/StandardLayout/components/Footer/Footer.tsx
  • packages/email/components/ui/Button/Button.tsx
  • packages/email/src/components/layout/StandardLayout.tsx
🧠 Learnings (4)
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/main/index.ts : Load environment variables from monorepo root .env in desktop app with override: true before any imports in src/main/index.ts and electron.vite.config.ts

Applied to files:

  • packages/email/src/lib/env.ts
📚 Learning: 2025-12-21T04:39:28.543Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-12-21T04:39:28.543Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : Use alias as defined in `tsconfig.json` when possible

Applied to files:

  • packages/email/tsconfig.json
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to **/*.test.{ts,tsx} : Co-locate tests with implementation files using .test.ts or .test.tsx suffix

Applied to files:

  • packages/email/tsconfig.json
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to **/*.{ts,tsx} : Extract magic numbers and hardcoded values to named constants at module top

Applied to files:

  • packages/email/src/lib/colors.ts
🧬 Code graph analysis (1)
packages/email/src/components/layout/StandardLayout.tsx (3)
packages/email/src/components/index.ts (3)
  • StandardLayout (1-1)
  • Logo (4-4)
  • Footer (3-3)
packages/email/src/components/ui/Logo.tsx (1)
  • Logo (3-13)
packages/email/src/components/ui/Footer.tsx (1)
  • Footer (4-43)
🪛 markdownlint-cli2 (0.18.1)
packages/email/README.md

37-37: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


213-213: Bare URL used

(MD034, no-bare-urls)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Deploy Marketing
  • GitHub Check: Deploy Web
  • GitHub Check: Deploy Admin
  • GitHub Check: Deploy Docs
  • GitHub Check: Deploy API
  • GitHub Check: Build
🔇 Additional comments (5)
packages/email/components/ui/Button/Button.tsx (1)

1-8: LGTM!

Clean imports and well-typed interface with proper union type for variant.

packages/email/tsconfig.json (1)

1-20: LGTM!

The TypeScript configuration is well-structured. The react-jsx transform aligns with the PR objective of eliminating explicit React imports. Note that the @/* path alias only resolves to ./src/*, so components in the root components/ directory will need to use relative imports to access src/lib/ modules (as Footer.tsx correctly does with ../../../../../src/lib/env).

packages/email/src/components/layout/StandardLayout.tsx (1)

18-42: LGTM!

The layout structure is clean and follows react-email best practices. The composition of Html, Head, Preview, Body, Container, and Section components is well-organized with clear separation of concerns.

packages/email/src/lib/colors.ts (1)

1-24: LGTM!

Well-structured color constants module. The hex format ensures email client compatibility, and the as const assertion with the Color type export provides good type safety. The documentation clearly explains the OKLCH-to-hex conversion rationale.

packages/email/components/layout/StandardLayout/components/Footer/Footer.tsx (1)

1-106: Duplicate StandardLayout and Footer components - consolidate or remove legacy implementation.

Two StandardLayout implementations exist with different Footer components:

  1. packages/email/src/components/layout/StandardLayout.tsx (exported) — uses ui/Footer with inline styles and address
  2. packages/email/components/layout/StandardLayout/ (internal) — uses custom Footer with Tailwind classes and social icons

Only the src version is exported from the public API, but the components directory version creates maintenance burden and confusion for developers working within the package. Both implementations differ in styling approach (inline vs Tailwind), content (tagline, address), and features (social icons). Remove the unused packages/email/components/ implementation or clarify its purpose and consolidate into a single StandardLayout with configurable Footer.

⛔ Skipped due to learnings
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/*/src/components/**/*.{ts,tsx} : Co-locate component dependencies: utils, hooks, constants, config, tests, stories in the same folder as the component using them

Comment thread packages/email/README.md
Comment on lines +37 to +52
```
packages/email/
├── emails/ # Email templates (create new emails here)
│ └── welcome.tsx # Example welcome email
├── components/ # Reusable components
│ ├── layout/
│ │ └── StandardLayout/ # Base layout with header/footer
│ │ ├── StandardLayout.tsx
│ │ └── components/
│ │ └── Footer/ # Footer component (co-located)
│ └── ui/
│ ├── Button/ # Reusable button component
│ └── Logo/ # Header logo component
└── src/ # Source files
└── lib/ # Utilities (colors, env config)
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix markdownlint MD040: add a language to the directory-tree fence.
This block is currently an untyped fenced code block.

Proposed diff
-```
+```text
 packages/email/
 ├── emails/                      # Email templates (create new emails here)
 │   └── welcome.tsx              # Example welcome email
 ├── components/                  # Reusable components
 │   ├── layout/
 │   │   └── StandardLayout/      # Base layout with header/footer
 │   │       ├── StandardLayout.tsx
 │   │       └── components/
 │   │           └── Footer/      # Footer component (co-located)
 │   └── ui/
 │       ├── Button/              # Reusable button component
 │       └── Logo/                # Header logo component
 └── src/                         # Source files
     └── lib/                     # Utilities (colors, env config)
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

37-37: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In @packages/email/README.md around lines 37 - 52, The README's directory-tree
fenced block is missing a language hint (MD040); update the opening fence for
the tree block in packages/email/ README.md from ``` to ```text so the directory
listing is a typed code fence (i.e., change the untyped fenced code block
containing the packages/email/ tree to use ```text).

Comment thread packages/email/README.md
Comment on lines +113 to +114
2. The template will automatically appear in the preview server (if using npm).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, find the README file
fd "README.md" packages/email

# Then read the context around lines 113-114

Repository: superset-sh/superset

Length of output: 88


🏁 Script executed:

# Read the relevant section of the README
sed -n '100,130p' packages/email/README.md

Repository: superset-sh/superset

Length of output: 873


🌐 Web query:

React Email 5.x preview server template discovery mechanism

💡 Result:

Short summary — how the React Email 5.x preview server finds templates:

  • Directory convention: the CLI/preview looks for your email templates under the configured directory (default "emails" / --dir option). It recursively scans that directory for template files. [1][2]
  • Preview components: a template can define Preview props or have a separate preview component/file (commonly placed in a previews/ subfolder). The preview server uses those preview components/props to render sample previews. [2][3]
  • File watching / delivery: the CLI watches your project/dependency graph (chokidar) and pushes changed files to the preview server (socket.io); the preview server bundles user files with esbuild at runtime and renders via the render utility. [2]
  • Ignoring and static files: directories prefixed with _ are ignored by the scanner; static files may live under a static folder inside the emails directory and are served by the preview server at /static. [2]
  • Implementation detail (preview-server package): the preview server is a separate package that bundles/render templates at runtime (package: @react-email/preview-server). [4]

Sources:
[1] React Email CLI docs (email dev, --dir, static files, ignore rule).
[2] React Email codebase overview (preview server architecture, chokidar, socket.io, esbuild, bundling/rendering).
[3] React Email preview + templates docs (preview components / PreviewProps usage).
[4] @react-email/preview-server npm page.


🌐 Web query:

React Email preview server how templates are discovered emails directory

💡 Result:

Short answer: The preview server scans your emails directory (default ./emails, configurable with --dir) and recursively bundles any file that looks like an email template. It considers a file an email if it has a .js/.jsx/.ts/.tsx extension and contains an export default (heuristic). Subfolders are supported; any folder prefixed with _ is ignored. Static assets should live under emails/static and are served by the preview server. See docs: [1][2][3].

How to use / modify:

  • Place templates in ./emails (or run email dev -d ). [1][2]
  • Ensure each template exports a default component (export default …). The server uses a regex to detect this. [1]
  • Ignore helper/component dirs by prefixing them with _. [1][3]
  • Put images/static files in ./emails/static (or /static) to have them served by the preview server. [1][2]

Sources:

  • React Email CLI / preview docs (directory, heuristics, static files, ignore with _). [1]
  • Getting started / Manual Setup (default emails dir and running email dev). [2]
  • Changelog notes on subfolders, _ ignore and improved export-default filtering. [3]

Remove package-manager-specific reference; template discovery is automatic and universal.

React Email's preview server automatically discovers templates from the emails/ directory regardless of whether you use npm, Bun, or any other package manager—this is handled by the CLI's file scanner, not the package manager. The current "(if using npm)" suggests otherwise and should be removed or clarified.

Proposed diff
-2. The template will automatically appear in the preview server (if using npm).
+2. The template will automatically appear in the preview server (React Email discovers templates from `emails/`).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
2. The template will automatically appear in the preview server (if using npm).
2. The template will automatically appear in the preview server (React Email discovers templates from `emails/`).
🤖 Prompt for AI Agents
In @packages/email/README.md around lines 113 - 114, Remove the
package-manager-specific parenthetical "(if using npm)" from the preview
sentence and reword so it states that templates in the emails/ directory are
automatically discovered by the preview server/CLI file scanner regardless of
package manager; ensure the sentence references the emails/ directory and the
preview server/CLI file scanner (remove "(if using npm)" and replace with a
concise universal phrasing).

Comment thread packages/email/README.md
Comment on lines +211 to +215
When you have the preview server working:
1. Create your email template in `emails/`
2. Preview at http://localhost:3000
3. Test HTML rendering with `@react-email/render`
4. Send test emails before production use

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix markdownlint MD034: avoid bare URL for localhost preview.

Proposed diff
-2. Preview at http://localhost:3000
+2. Preview at `http://localhost:3000`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
When you have the preview server working:
1. Create your email template in `emails/`
2. Preview at http://localhost:3000
3. Test HTML rendering with `@react-email/render`
4. Send test emails before production use
When you have the preview server working:
1. Create your email template in `emails/`
2. Preview at `http://localhost:3000`
3. Test HTML rendering with `@react-email/render`
4. Send test emails before production use
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

213-213: Bare URL used

(MD034, no-bare-urls)

🤖 Prompt for AI Agents
In @packages/email/README.md around lines 211 - 215, Replace the bare URL
"Preview at http://localhost:3000" with a proper Markdown link or fenced URL to
satisfy MD034; e.g., change the line in the README where it currently reads
"Preview at http://localhost:3000" to "Preview at <http://localhost:3000>" or
"Preview at [http://localhost:3000](http://localhost:3000)" so the localhost
preview link is not a bare URL.

Comment on lines +63 to +68
const divider = {
background: "radial-gradient(circle farthest-side, #dfe1e4, #edeff5)",
borderTop: "1px solid transparent",
height: "1px",
margin: "28px 36px",
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

CSS radial-gradient email client support 2025

💡 Result:

Short summary — as of early 2025:

  • Good support (CSS radial/linear gradients render): Apple Mail (macOS), iOS Mail, modern Gmail apps (mobile + web), Gmail web (Google Workspace), Samsung Mail, Thunderbird and most WebKit/Blink webmail clients. [1][2][3]
  • Partial / inconsistent: Outlook for Mac & Outlook mobile sometimes support gradients; Gmail Android/iOS behavior has changed in 2025 (some builds restrict gradients when used with background-image vs background shorthand). Test specific app versions. [1][4]
  • Poor/no support (will not render CSS gradients): Outlook for Windows (Word-based renderers / desktop Outlook 2007–2019/Office 365 on Windows) — use VML fallback; many webmail providers (Outlook.com, Yahoo!, some corporate webmail) fall back to solid color or ignore gradients. [2][3][5]

Practical guidance:

  • Use a solid background-color first, then background: radial-gradient(...) as enhancement. Provide VML or image fallbacks for Outlook Windows and expect some webmail clients to show the solid color instead. Test across clients (Litmus/Email on Acid / real devices). [2][3][6]

Sources: Can I Email (2025 updates), Campaign Monitor/Litmus guides, Maizzle gradients guide, Email on Acid testing notes. [1][2][3][4][6]


🏁 Script executed:

cat -n packages/email/src/components/layout/StandardLayout.tsx | head -80

Repository: superset-sh/superset

Length of output: 1961


Add solid color fallback for email client compatibility.

The radial-gradient will not render in Outlook for Windows (2007–2019/Office 365), Outlook.com, Yahoo!, and other webmail providers. Add a backgroundColor property as a fallback:

Suggested fix
const divider = {
	backgroundColor: "#edeff5",
	background: "radial-gradient(circle farthest-side, #dfe1e4, #edeff5)",
	borderTop: "1px solid transparent",
	height: "1px",
	margin: "28px 36px",
};

Email clients that support gradients will render the gradient; others will fall back to the solid color. Test across target clients (Litmus/Email on Acid recommended).

🤖 Prompt for AI Agents
In @packages/email/src/components/layout/StandardLayout.tsx around lines 63 -
68, The divider style object named "divider" uses a radial-gradient that many
email clients ignore; add a solid fallback by adding backgroundColor: "#edeff5"
(or the desired hex) alongside the existing background property so clients that
don't support gradients will use the solid color; update the "divider" object in
StandardLayout.tsx to include backgroundColor before/with background.

@saddlepaddle
saddlepaddle merged commit 4d19881 into main Jan 13, 2026
13 checks passed
@Kitenite
Kitenite deleted the arrogant-sailfish-fa7e39 branch January 13, 2026 17:50

This branch was previously deployed

1 inactive deployment
preview — 124859ba Deployed Jan 13, 2026 by saddlepaddle via Deploy Docs #829
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant