Repository navigation
feat(email): Add React Email package with StandardLayout - #729
Conversation
📝 WalkthroughWalkthroughAdds a new Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
9c89bf3 to
5ce4719
Compare
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
There was a problem hiding this comment.
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 withNEXT_PUBLIC_. If this is intentional (server-only usage), consider removing the unusedclientPrefixand emptyclientblock.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/envis 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
socialIconsobject 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) andpackages/email/src/components/(flat structure exported via package.json). The email templates import from the rootcomponents/directory, but the barrel export points tosrc/components/, creating divergent implementations.Options:
- Remove
src/components/if it's no longer needed—the rootcomponents/directory already follows the intended structure- If
src/components/serves a purpose (e.g., simplified exports for internal use), document this clearly and ensure both versions remain synchronized- 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
#515759is 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
#515759aligns with an existing theme token (likemuted), 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:
- Moving styles closer to their usage, or
- Extracting shared styles (like
paragraph,link) to a shared module insrc/lib/This would help maintain consistency as more email templates are added.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
apps/marketing/public/assets/emails/instagram.pngis excluded by!**/*.pngapps/marketing/public/assets/emails/linkedin.pngis excluded by!**/*.pngapps/marketing/public/assets/emails/logo-full.pngis excluded by!**/*.pngapps/marketing/public/assets/emails/logo.pngis excluded by!**/*.pngapps/marketing/public/assets/emails/x.pngis excluded by!**/*.pngbun.lockis excluded by!**/*.lock
📒 Files selected for processing (21)
packages/email/README.mdpackages/email/components/layout/StandardLayout/StandardLayout.tsxpackages/email/components/layout/StandardLayout/components/Footer/Footer.tsxpackages/email/components/layout/StandardLayout/components/Footer/index.tspackages/email/components/layout/StandardLayout/index.tspackages/email/components/ui/Button/Button.tsxpackages/email/components/ui/Button/index.tspackages/email/components/ui/Logo/Logo.tsxpackages/email/components/ui/Logo/index.tspackages/email/emails/welcome.tsxpackages/email/package.jsonpackages/email/src/components/index.tspackages/email/src/components/layout/StandardLayout.tsxpackages/email/src/components/ui/Button.tsxpackages/email/src/components/ui/Footer.tsxpackages/email/src/components/ui/Logo.tsxpackages/email/src/emails/welcome.tsxpackages/email/src/lib/colors.tspackages/email/src/lib/env.tspackages/email/tailwind.config.tspackages/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 usinganytype - 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.tspackages/email/src/components/ui/Button.tsxpackages/email/src/lib/env.tspackages/email/components/layout/StandardLayout/components/Footer/Footer.tsxpackages/email/components/layout/StandardLayout/StandardLayout.tsxpackages/email/src/components/ui/Footer.tsxpackages/email/components/ui/Button/index.tspackages/email/components/layout/StandardLayout/index.tspackages/email/components/ui/Button/Button.tsxpackages/email/src/lib/colors.tspackages/email/src/components/index.tspackages/email/tailwind.config.tspackages/email/src/components/ui/Logo.tsxpackages/email/src/components/layout/StandardLayout.tsxpackages/email/components/layout/StandardLayout/components/Footer/index.tspackages/email/components/ui/Logo/Logo.tsxpackages/email/emails/welcome.tsxpackages/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:fixorbiome check --write
Files:
packages/email/components/ui/Logo/index.tspackages/email/src/components/ui/Button.tsxpackages/email/src/lib/env.tspackages/email/components/layout/StandardLayout/components/Footer/Footer.tsxpackages/email/components/layout/StandardLayout/StandardLayout.tsxpackages/email/src/components/ui/Footer.tsxpackages/email/components/ui/Button/index.tspackages/email/components/layout/StandardLayout/index.tspackages/email/components/ui/Button/Button.tsxpackages/email/src/lib/colors.tspackages/email/src/components/index.tspackages/email/tailwind.config.tspackages/email/src/components/ui/Logo.tsxpackages/email/src/components/layout/StandardLayout.tsxpackages/email/components/layout/StandardLayout/components/Footer/index.tspackages/email/components/ui/Logo/Logo.tsxpackages/email/emails/welcome.tsxpackages/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.tsxpackages/email/components/layout/StandardLayout/components/Footer/Footer.tsxpackages/email/components/layout/StandardLayout/StandardLayout.tsxpackages/email/src/components/ui/Footer.tsxpackages/email/components/ui/Button/Button.tsxpackages/email/src/components/ui/Logo.tsxpackages/email/src/components/layout/StandardLayout.tsxpackages/email/components/ui/Logo/Logo.tsxpackages/email/emails/welcome.tsxpackages/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.tsxpackages/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.tspackages/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-jsxtransform aligns with the PR's goal of not requiring React imports.Note: The
@/*path alias only covers./src/*, so files incomponents/andemails/directories will need to use relative imports to reference each other or files insrc/. 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 constassertion provides proper type narrowing, and the exportedColortype 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 constassertion on Line 55 foroverflow: "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
errorparameter,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.
| 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> | ||
| ); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find both StandardLayout files mentioned in the comment
fd -t f "StandardLayout" -e ".tsx" -e ".ts" | head -20Repository: 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 -30Repository: 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.tsxRepository: 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 -40Repository: 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 2Repository: 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 2Repository: superset-sh/superset
Length of output: 343
🏁 Script executed:
# Search for imports of StandardLayout from each location
rg "from.*StandardLayout" --type ts --type js -iRepository: 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.tsRepository: 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 -30Repository: superset-sh/superset
Length of output: 1120
🏁 Script executed:
# Compare with the ui Footer
cat -n packages/email/src/components/ui/Footer.tsx | head -50Repository: superset-sh/superset
Length of output: 1452
🏁 Script executed:
# Check if the src StandardLayout is imported anywhere
rg "src/components.*StandardLayout" --type ts --type jsRepository: 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.tsRepository: superset-sh/superset
Length of output: 259
🏁 Script executed:
# Verify welcome.tsx uses the components version
cat -n packages/email/emails/welcome.tsxRepository: 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 -20Repository: 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 2Repository: 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/.
| 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; |
There was a problem hiding this comment.
🧩 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 fRepository: 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 || trueRepository: 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.
| "exports": { | ||
| "./emails/*": "./src/emails/*.tsx", | ||
| "./components": "./src/components/index.ts", | ||
| "./lib/*": "./src/lib/*.ts" | ||
| }, |
There was a problem hiding this comment.
🧩 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 | sortRepository: 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", |
There was a problem hiding this comment.
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
.envfile 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.
| ## 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. |
There was a problem hiding this comment.
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.
| <Text style={companyInfo}> | ||
| © 2026 Superset. All rights reserved. | ||
| <br /> | ||
| 123 Main Street, San Francisco, CA 94105 | ||
| </Text> |
There was a problem hiding this comment.
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.
| <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.
| 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", | ||
| }; |
There was a problem hiding this comment.
🧩 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 logoRepository: 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.tsxRepository: 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 -20Repository: 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.
| 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> |
There was a problem hiding this comment.
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.
| 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).
| const link = { | ||
| color: "#966dd5", | ||
| textDecoration: "none", | ||
| }; |
There was a problem hiding this comment.
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.
| 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.
| content: [ | ||
| "./emails/**/*.{js,jsx,ts,tsx}", | ||
| "./components/**/*.{js,jsx,ts,tsx}", | ||
| ], |
There was a problem hiding this comment.
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.
| 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.
…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>
9206e59 to
124859b
Compare
There was a problem hiding this comment.
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_URLis validated only as a string. Since this is used to construct asset URLs throughout the email components, consider usingz.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
⛔ Files ignored due to path filters (6)
apps/marketing/public/assets/emails/instagram.pngis excluded by!**/*.pngapps/marketing/public/assets/emails/linkedin.pngis excluded by!**/*.pngapps/marketing/public/assets/emails/logo-full.pngis excluded by!**/*.pngapps/marketing/public/assets/emails/logo.pngis excluded by!**/*.pngapps/marketing/public/assets/emails/x.pngis excluded by!**/*.pngbun.lockis excluded by!**/*.lock
📒 Files selected for processing (21)
packages/email/README.mdpackages/email/components/layout/StandardLayout/StandardLayout.tsxpackages/email/components/layout/StandardLayout/components/Footer/Footer.tsxpackages/email/components/layout/StandardLayout/components/Footer/index.tspackages/email/components/layout/StandardLayout/index.tspackages/email/components/ui/Button/Button.tsxpackages/email/components/ui/Button/index.tspackages/email/components/ui/Logo/Logo.tsxpackages/email/components/ui/Logo/index.tspackages/email/emails/welcome.tsxpackages/email/package.jsonpackages/email/src/components/index.tspackages/email/src/components/layout/StandardLayout.tsxpackages/email/src/components/ui/Button.tsxpackages/email/src/components/ui/Footer.tsxpackages/email/src/components/ui/Logo.tsxpackages/email/src/emails/welcome.tsxpackages/email/src/lib/colors.tspackages/email/src/lib/env.tspackages/email/tailwind.config.tspackages/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 usinganytype - 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.tspackages/email/components/layout/StandardLayout/components/Footer/Footer.tsxpackages/email/components/ui/Button/Button.tsxpackages/email/src/components/layout/StandardLayout.tsxpackages/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:fixorbiome check --write
Files:
packages/email/src/lib/env.tspackages/email/components/layout/StandardLayout/components/Footer/Footer.tsxpackages/email/components/ui/Button/Button.tsxpackages/email/src/components/layout/StandardLayout.tsxpackages/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.tsxpackages/email/components/ui/Button/Button.tsxpackages/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-jsxtransform aligns with the PR objective of eliminating explicit React imports. Note that the@/*path alias only resolves to./src/*, so components in the rootcomponents/directory will need to use relative imports to accesssrc/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 constassertion with theColortype 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:
packages/email/src/components/layout/StandardLayout.tsx(exported) — usesui/Footerwith inline styles and addresspackages/email/components/layout/StandardLayout/(internal) — uses custom Footer with Tailwind classes and social iconsOnly 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
| ``` | ||
| 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) | ||
| ``` |
There was a problem hiding this comment.
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).
| 2. The template will automatically appear in the preview server (if using npm). | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find the README file
fd "README.md" packages/email
# Then read the context around lines 113-114Repository: superset-sh/superset
Length of output: 88
🏁 Script executed:
# Read the relevant section of the README
sed -n '100,130p' packages/email/README.mdRepository: 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.
| 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).
| 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 |
There was a problem hiding this comment.
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.
| 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.
| const divider = { | ||
| background: "radial-gradient(circle farthest-side, #dfe1e4, #edeff5)", | ||
| borderTop: "1px solid transparent", | ||
| height: "1px", | ||
| margin: "28px 36px", | ||
| }; |
There was a problem hiding this comment.
🧩 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 -80Repository: 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.
Summary
Add complete React Email package for transactional emails with StandardLayout, reusable components, and welcome template example.
What's Included
Package Structure
Technical Implementation
@react-email/tailwindAssets
Environment Setup
Loads
NEXT_PUBLIC_MARKETING_URLfrom root.env:http://localhost:3002(marketing app)https://superset.shUsage
Preview
Run
bun devinpackages/emailto preview templates at http://localhost:3000Test Plan
🤖 Generated with Claude Code
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.