├── .changeset ├── README.md └── config.json ├── .github ├── CODEOWNERS ├── DISCUSSION_TEMPLATE │ ├── help.yml │ └── ideas.yml ├── ISSUE_TEMPLATE │ ├── 1.bug_report.yml │ └── config.yml ├── pull_request_template.md └── workflows │ ├── lint.yml │ ├── pin-dependencies-check.yml │ ├── preview-release.yml │ ├── pull-request-title-check.yml │ ├── release-canary.yml │ ├── release.yml │ └── tests.yml ├── .gitignore ├── .npmrc ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── SECURITY.md ├── apps ├── demo │ ├── .gitignore │ ├── emails │ │ ├── magic-links │ │ │ ├── aws-verify-email.tsx │ │ │ ├── linear-login-code.tsx │ │ │ ├── notion-magic-link.tsx │ │ │ ├── plaid-verify-identity.tsx │ │ │ ├── raycast-magic-link.tsx │ │ │ └── slack-confirm.tsx │ │ ├── newsletters │ │ │ ├── codepen-challengers.tsx │ │ │ ├── google-play-policy-update.tsx │ │ │ └── stack-overflow-tips.tsx │ │ ├── notifications │ │ │ ├── github-access-token.tsx │ │ │ ├── papermark-year-in-review.tsx │ │ │ ├── vercel-invite-user.tsx │ │ │ └── yelp-recent-login.tsx │ │ ├── receipts │ │ │ ├── apple-receipt.tsx │ │ │ └── nike-receipt.tsx │ │ ├── reset-password │ │ │ ├── dropbox-reset-password.tsx │ │ │ └── twitch-reset-password.tsx │ │ ├── reviews │ │ │ ├── airbnb-review.tsx │ │ │ └── amazon-review.tsx │ │ ├── static │ │ │ ├── airbnb-logo.png │ │ │ ├── airbnb-review-user.jpg │ │ │ ├── amazon-book.jpg │ │ │ ├── amazon-facebook.jpg │ │ │ ├── amazon-instagram.jpg │ │ │ ├── amazon-logo.png │ │ │ ├── amazon-prime-logo.png │ │ │ ├── amazon-rating.gif │ │ │ ├── amazon-twitter.jpg │ │ │ ├── apple-card-icon.png │ │ │ ├── apple-hbo-max-icon.jpeg │ │ │ ├── apple-logo.png │ │ │ ├── apple-wallet.png │ │ │ ├── aws-logo.png │ │ │ ├── codepen-challengers.png │ │ │ ├── codepen-cube.png │ │ │ ├── codepen-pro.png │ │ │ ├── dropbox-logo.png │ │ │ ├── github.png │ │ │ ├── google-play-academy.png │ │ │ ├── google-play-chat.png │ │ │ ├── google-play-footer.png │ │ │ ├── google-play-header.png │ │ │ ├── google-play-icon.png │ │ │ ├── google-play-logo.png │ │ │ ├── google-play-pl.png │ │ │ ├── google-play.png │ │ │ ├── koala-logo.png │ │ │ ├── linear-logo.png │ │ │ ├── netlify-logo.png │ │ │ ├── nike-logo.png │ │ │ ├── nike-phone.png │ │ │ ├── nike-product.png │ │ │ ├── nike-recomendation-1.png │ │ │ ├── nike-recomendation-2.png │ │ │ ├── nike-recomendation-3.png │ │ │ ├── nike-recomendation-4.png │ │ │ ├── notion-logo.png │ │ │ ├── plaid-logo.png │ │ │ ├── raycast-bg.png │ │ │ ├── raycast-logo.png │ │ │ ├── slack-facebook.png │ │ │ ├── slack-linkedin.png │ │ │ ├── slack-logo.png │ │ │ ├── slack-twitter.png │ │ │ ├── stack-overflow-header.png │ │ │ ├── stack-overflow-logo-sm.png │ │ │ ├── stack-overflow-logo.png │ │ │ ├── stripe-logo.png │ │ │ ├── twitch-icon-facebook.png │ │ │ ├── twitch-icon-twitter.png │ │ │ ├── twitch-logo.png │ │ │ ├── vercel-arrow.png │ │ │ ├── vercel-logo.png │ │ │ ├── vercel-team.png │ │ │ ├── vercel-user.png │ │ │ ├── yelp-footer.png │ │ │ ├── yelp-header.png │ │ │ └── yelp-logo.png │ │ └── welcome │ │ │ ├── koala-welcome.tsx │ │ │ ├── netlify-welcome.tsx │ │ │ └── stripe-welcome.tsx │ ├── license.md │ └── package.json ├── docs │ ├── changelog.mdx │ ├── cli.mdx │ ├── components │ │ ├── button.mdx │ │ ├── code-block.mdx │ │ ├── code-inline.mdx │ │ ├── column.mdx │ │ ├── container.mdx │ │ ├── font.mdx │ │ ├── head.mdx │ │ ├── heading.mdx │ │ ├── hr.mdx │ │ ├── html.mdx │ │ ├── image.mdx │ │ ├── link.mdx │ │ ├── markdown.mdx │ │ ├── preview.mdx │ │ ├── row.mdx │ │ ├── section.mdx │ │ ├── tailwind.mdx │ │ └── text.mdx │ ├── contributing.mdx │ ├── contributing │ │ ├── codebase-overview.mdx │ │ ├── development-workflow │ │ │ ├── 1-setup.mdx │ │ │ ├── 2-running-tests.mdx │ │ │ ├── 3-linting.mdx │ │ │ ├── 4-building.mdx │ │ │ └── 5-writing-docs.mdx │ │ ├── introduction.mdx │ │ ├── opening-issues.mdx │ │ └── opening-pull-requests.mdx │ ├── deployment.mdx │ ├── docs.json │ ├── favicon.png │ ├── getting-started │ │ ├── automatic-setup.mdx │ │ ├── manual-setup.mdx │ │ ├── migrating-to-react-email.mdx │ │ └── monorepo-setup │ │ │ ├── bun.mdx │ │ │ ├── choose-package-manager.mdx │ │ │ ├── npm.mdx │ │ │ ├── pnpm.mdx │ │ │ └── yarn.mdx │ ├── images │ │ ├── background.png │ │ ├── bg.png │ │ ├── local-dev.jpg │ │ └── preview-server-vercel-settings.png │ ├── integrations │ │ ├── aws-ses.mdx │ │ ├── mailersend.mdx │ │ ├── nodemailer.mdx │ │ ├── overview.mdx │ │ ├── plunk.mdx │ │ ├── postmark.mdx │ │ ├── resend.mdx │ │ ├── scaleway.mdx │ │ └── sendgrid.mdx │ ├── introduction.mdx │ ├── license.md │ ├── logo │ │ ├── dark.svg │ │ └── light.svg │ ├── package.json │ ├── roadmap.mdx │ ├── snippets │ │ ├── integrations.mdx │ │ ├── localdev.mdx │ │ ├── next-steps.mdx │ │ └── support.mdx │ └── utilities │ │ └── render.mdx └── web │ ├── .gitignore │ ├── .vscode │ └── settings.json │ ├── README.md │ ├── components │ ├── _components │ │ └── layout.tsx │ ├── article-with-image-as-background │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── article-with-image-on-right │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── article-with-image │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── article-with-multiple-authors │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── article-with-single-author │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── article-with-two-cards │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── bento-grid │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── checkout │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── code-block-with-custom-theme │ │ └── index.tsx │ ├── code-block-with-line-numbers │ │ └── index.tsx │ ├── code-block-with-predefined-theme │ │ └── index.tsx │ ├── code-block-without-theme │ │ └── index.tsx │ ├── code-inline-with-different-colors │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── customer-reviews │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── divider-between-rows-and-columns │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── download-buttons │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── ensure-matching-variants.spec.tsx │ ├── footer-with-one-column │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── footer-with-two-columns │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── four-images-in-a-grid │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── header-and-four-paragraphs-and-two-columns │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── header-and-four-paragraphs │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── header-and-list-items │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── header-and-numbered-list-items │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── header-and-three-centered-paragraphs │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── header-with-centered-menu │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── header-with-side-menu │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── header-with-social-icons │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── image-with-varying-sizes │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── images-on-horizontal-grid │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── images-on-vertical-grid │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── link-inline-with-text │ │ └── index.tsx │ ├── list-with-image-on-left │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── markdown-with-container-styles │ │ └── index.tsx │ ├── markdown-with-custom-styles │ │ └── index.tsx │ ├── multiple-headings │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── one-product-with-image-on-the-left │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── one-product │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── one-row-three-columns │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── one-row-two-columns │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── rounded-image │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── section-with-rows-and-columns │ │ └── index.tsx │ ├── simple-code-inline │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── simple-container │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── simple-divider │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── simple-heading │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── simple-image │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── simple-link │ │ └── index.tsx │ ├── simple-list │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── simple-markdown │ │ └── index.tsx │ ├── simple-pricing-table │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── simple-rating-survey │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── simple-section │ │ └── index.tsx │ ├── simple-text │ │ └── index.tsx │ ├── single-button │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── static │ │ ├── cube-icon.png │ │ ├── download-on-the-app-store.png │ │ ├── facebook-logo.png │ │ ├── get-it-on-google-play.png │ │ ├── heart-icon.png │ │ ├── in-icon.png │ │ ├── instagram-logo.png │ │ ├── logo-without-background.png │ │ ├── megaphone-icon.png │ │ ├── rocket-icon.png │ │ ├── steve-jobs.jpg │ │ ├── steve-wozniak.jpg │ │ ├── x-icon.png │ │ └── x-logo.png │ ├── structure.ts │ ├── survey-section │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── tailwind.config.ts │ ├── text-with-styling │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── three-columns-with-images │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── title-four-cards │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── title-three-cards-in-a-row │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── tsconfig.json │ ├── two-buttons │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ └── two-tiers-with-emphasized-tier │ │ ├── inline-styles.tsx │ │ └── tailwind.tsx │ ├── license.md │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── brand │ │ ├── logo-without-background.png │ │ ├── logo.png │ │ └── resend.png │ ├── examples │ │ ├── airbnb-review.png │ │ ├── apple-receipt.png │ │ ├── authors │ │ │ ├── EmersonGarrido.png │ │ │ ├── Rychillie.png │ │ │ ├── abhinandanwadwa.png │ │ │ ├── bruno88cabral.png │ │ │ ├── bukinoshita.png │ │ │ ├── c0dr.png │ │ │ ├── camillegachido.png │ │ │ ├── joaom00.png │ │ │ ├── nettofarah.png │ │ │ ├── relferreira.png │ │ │ ├── ribeiroevandro.png │ │ │ ├── thecodeinfluencer.png │ │ │ └── zenorocha.png │ │ ├── aws-verify-email.png │ │ ├── dropbox-reset-password.png │ │ ├── github-access-token.png │ │ ├── google-play-policy-update.png │ │ ├── koala-welcome.png │ │ ├── linear-login-code.png │ │ ├── nike-receipt.png │ │ ├── notion-magic-link.png │ │ ├── plaid-verify-identity.png │ │ ├── raycast-magic-link.png │ │ ├── slack-confirm.png │ │ ├── stack-overflow-tips.png │ │ ├── stripe-welcome.png │ │ ├── twitch-reset-password.png │ │ ├── vercel-invite-user.png │ │ └── yelp-recent-login.png │ ├── fonts │ │ ├── commit-mono │ │ │ ├── commit-mono-italic.ttf │ │ │ └── commit-mono-regular.ttf │ │ ├── inter │ │ │ └── inter.ttf │ │ └── shantell-sans │ │ │ ├── shantell-sans-italic.ttf │ │ │ └── shantell-sans-regular.ttf │ ├── meta │ │ ├── apple-touch-icon.png │ │ ├── cover.png │ │ ├── favicon.ico │ │ └── favicon.svg │ └── static │ │ ├── atmos-vacuum-canister.jpg │ │ ├── bg.png │ │ ├── braun-analogue-clock.jpg │ │ ├── braun-classic-watch.jpg │ │ ├── braun-collection.jpg │ │ ├── braun-vintage.jpg │ │ ├── braun-wall-clock.jpg │ │ ├── braun-wireless-alarm.jpg │ │ ├── bundle-collection.jpg │ │ ├── clara-french-press.jpg │ │ ├── clyde-electric-kettle.jpg │ │ ├── coffee-bean-storage.jpg │ │ ├── covers │ │ ├── button.png │ │ ├── code-block.png │ │ ├── code-inline.png │ │ ├── column.png │ │ ├── components.png │ │ ├── container.png │ │ ├── create-email.png │ │ ├── font.png │ │ ├── head.png │ │ ├── heading.png │ │ ├── hr.png │ │ ├── html.png │ │ ├── img.png │ │ ├── link.png │ │ ├── markdown.png │ │ ├── patterns.png │ │ ├── preview.png │ │ ├── react-email.png │ │ ├── render.png │ │ ├── row.png │ │ ├── section.png │ │ ├── tailwind.png │ │ └── text.png │ │ ├── cube-icon.png │ │ ├── download-on-the-app-store.png │ │ ├── facebook-logo.png │ │ ├── get-it-on-google-play.png │ │ ├── grinder-collection.jpg │ │ ├── heart-icon.png │ │ ├── herman-miller-chair.jpg │ │ ├── icons │ │ ├── apple-mail.svg │ │ ├── gmail.svg │ │ ├── hey.svg │ │ ├── outlook.svg │ │ ├── superhuman.svg │ │ └── yahoo-mail.svg │ │ ├── in-icon.png │ │ ├── instagram-logo.png │ │ ├── logo-without-background.png │ │ ├── megaphone-icon.png │ │ ├── monty-art-cup-1.jpg │ │ ├── monty-art-cup-2.jpg │ │ ├── mugs-collection.jpg │ │ ├── ode-grinder.jpg │ │ ├── outdoor-living.jpg │ │ ├── rocket-icon.png │ │ ├── stagg-eletric-kettle.jpg │ │ ├── steve-jobs.jpg │ │ ├── steve-wozniak.jpg │ │ ├── vacuum-canister-clear-glass-bundle.jpg │ │ ├── versatile-comfort.jpg │ │ ├── x-icon.png │ │ └── x-logo.png │ ├── src │ ├── app │ │ ├── api │ │ │ ├── check-spam │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── check-spam.spec.tsx.snap │ │ │ │ ├── check-spam.spec.tsx │ │ │ │ ├── check-spam.ts │ │ │ │ ├── route.ts │ │ │ │ └── testing │ │ │ │ │ └── stripe-welcome-email.tsx │ │ │ └── send │ │ │ │ └── test │ │ │ │ └── route.ts │ │ ├── components │ │ │ ├── [slug] │ │ │ │ └── page.tsx │ │ │ ├── get-imported-components-for.tsx │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── not-found.tsx │ │ ├── page.tsx │ │ ├── robots.ts │ │ ├── sitemap.ts │ │ └── templates │ │ │ └── page.tsx │ ├── components │ │ ├── anchor.tsx │ │ ├── button.tsx │ │ ├── code-block.tsx │ │ ├── code.tsx │ │ ├── component-code-view.tsx │ │ ├── component-preview.tsx │ │ ├── component-view.tsx │ │ ├── components-view.tsx │ │ ├── footer.tsx │ │ ├── heading.tsx │ │ ├── icon-button.tsx │ │ ├── icons │ │ │ ├── icon-arrow-left.tsx │ │ │ ├── icon-base.tsx │ │ │ ├── icon-monitor.tsx │ │ │ ├── icon-phone.tsx │ │ │ └── icon-source.tsx │ │ ├── logo.tsx │ │ ├── menu.tsx │ │ ├── page-transition.tsx │ │ ├── spotlight.tsx │ │ ├── tab-trigger.tsx │ │ ├── template.tsx │ │ ├── text.tsx │ │ ├── tooltip-content.tsx │ │ ├── tooltip.tsx │ │ └── topbar.tsx │ ├── hooks │ │ └── use-stored-state.ts │ ├── illustrations │ │ ├── articles.tsx │ │ ├── buttons.tsx │ │ ├── code-block.tsx │ │ ├── code-inline.tsx │ │ ├── container.tsx │ │ ├── divider.tsx │ │ ├── ecommerce.tsx │ │ ├── features.tsx │ │ ├── feedback.tsx │ │ ├── footers.tsx │ │ ├── gallery.tsx │ │ ├── grid.tsx │ │ ├── headers.tsx │ │ ├── heading.tsx │ │ ├── image.tsx │ │ ├── link.tsx │ │ ├── list.tsx │ │ ├── markdown.tsx │ │ ├── marketing.tsx │ │ ├── pricing.tsx │ │ ├── section.tsx │ │ └── text.tsx │ ├── styles │ │ └── globals.css │ └── utils │ │ ├── as.ts │ │ ├── convert-uris-into-urls.spec.ts │ │ ├── convert-uris-into-urls.ts │ │ ├── slugify.ts │ │ ├── spam-assassin │ │ ├── __snapshots__ │ │ │ └── parse-pointing-table-rows.spec.ts.snap │ │ ├── parse-pointing-table-rows.spec.ts │ │ ├── parse-pointing-table-rows.ts │ │ └── send-to-spamd.ts │ │ └── unreachable.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ └── vitest.config.ts ├── benchmarks ├── preview-server │ ├── bench-results-30-iterations.json │ ├── package.json │ ├── src │ │ ├── local-vs-2.1.7-canary.2-on-startup.ts │ │ ├── local-vs-2.1.7-canary.2.ts │ │ └── utils │ │ │ ├── run-server-and-fetch-preview-page.ts │ │ │ └── run-server.ts │ └── startup-bench-results-30-iterations.json └── tailwind-component │ ├── .gitignore │ ├── README.md │ ├── bench-results-100-iterations.json │ ├── package.json │ ├── src │ ├── benchmark-0.0.12-vs-local-version.tsx │ ├── benchmark-0.0.17-vs-local-version.tsx │ ├── benchmark-with-vs-without.tsx │ ├── emails │ │ ├── with-tailwind.tsx │ │ └── without-tailwind.tsx │ └── tailwind-render.tsx │ ├── tailwind.config.js │ └── tsconfig.json ├── biome.json ├── examples ├── aws-ses │ ├── package.json │ ├── src │ │ ├── email.tsx │ │ └── index.tsx │ └── tsconfig.json ├── mailersend │ ├── package.json │ ├── src │ │ ├── email.tsx │ │ └── index.tsx │ └── tsconfig.json ├── nodemailer │ ├── package.json │ ├── src │ │ ├── email.tsx │ │ └── index.tsx │ └── tsconfig.json ├── plunk │ ├── package.json │ ├── src │ │ ├── email.tsx │ │ └── index.tsx │ └── tsconfig.json ├── postmark │ ├── package.json │ ├── src │ │ ├── email.tsx │ │ └── index.tsx │ └── tsconfig.json ├── resend │ ├── next-env.d.ts │ ├── package.json │ ├── src │ │ ├── lib │ │ │ └── resend.ts │ │ └── pages │ │ │ └── api │ │ │ └── send.ts │ ├── transactional │ │ └── emails │ │ │ └── waitlist.tsx │ └── tsconfig.json ├── scaleway │ ├── next │ │ ├── next-env.d.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── lib │ │ │ │ └── scaleway.ts │ │ │ └── pages │ │ │ │ └── api │ │ │ │ └── send.tsx │ │ ├── transactional │ │ │ └── emails │ │ │ │ └── waitlist.tsx │ │ └── tsconfig.json │ └── node │ │ ├── package.json │ │ ├── src │ │ ├── email.tsx │ │ └── index.tsx │ │ └── tsconfig.json └── sendgrid │ ├── package.json │ ├── src │ ├── email.tsx │ └── index.tsx │ └── tsconfig.json ├── package.json ├── packages ├── body │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── body.spec.tsx.snap │ │ ├── body.spec.tsx │ │ ├── body.tsx │ │ └── index.ts │ └── tsconfig.json ├── button │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── button.spec.tsx.snap │ │ ├── button.spec.tsx │ │ ├── button.tsx │ │ ├── index.ts │ │ └── utils │ │ │ ├── parse-padding.ts │ │ │ ├── px-to-pt.ts │ │ │ └── utils.spec.ts │ └── tsconfig.json ├── code-block │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── code-block.tsx │ │ ├── index.ts │ │ ├── languages-available.ts │ │ ├── prism.ts │ │ └── themes.ts │ └── tsconfig.json ├── code-inline │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── code-inline.tsx │ │ └── index.ts │ └── tsconfig.json ├── column │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── column.spec.tsx.snap │ │ ├── column.spec.tsx │ │ ├── column.tsx │ │ └── index.ts │ └── tsconfig.json ├── components │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── heading.spec.tsx.snap │ │ └── index.ts │ └── tsconfig.json ├── container │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── container.spec.tsx.snap │ │ ├── container.spec.tsx │ │ ├── container.tsx │ │ └── index.ts │ └── tsconfig.json ├── create-email │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── index.js │ │ ├── index.spec.ts │ │ └── tree.js │ ├── template │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── emails │ │ │ ├── notion-magic-link.tsx │ │ │ ├── plaid-verify-identity.tsx │ │ │ ├── static │ │ │ │ ├── notion-logo.png │ │ │ │ ├── plaid-logo.png │ │ │ │ ├── plaid.png │ │ │ │ ├── stripe-logo.png │ │ │ │ ├── vercel-arrow.png │ │ │ │ ├── vercel-logo.png │ │ │ │ ├── vercel-team.png │ │ │ │ └── vercel-user.png │ │ │ ├── stripe-welcome.tsx │ │ │ └── vercel-invite-user.tsx │ │ ├── package.json │ │ ├── readme.md │ │ └── tsconfig.json │ ├── tsconfig.json │ └── vitest.config.ts ├── font │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── font.spec.tsx.snap │ │ ├── font.spec.tsx │ │ ├── font.tsx │ │ └── index.ts │ └── tsconfig.json ├── head │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── head.spec.tsx.snap │ │ ├── head.spec.tsx │ │ ├── head.tsx │ │ └── index.ts │ └── tsconfig.json ├── heading │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── heading.spec.tsx.snap │ │ ├── heading.spec.tsx │ │ ├── heading.tsx │ │ ├── index.ts │ │ └── utils │ │ │ ├── as.ts │ │ │ ├── spaces.ts │ │ │ └── utils.spec.ts │ └── tsconfig.json ├── hr │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── hr.spec.tsx.snap │ │ ├── hr.spec.tsx │ │ ├── hr.tsx │ │ └── index.ts │ └── tsconfig.json ├── html │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── html.spec.tsx.snap │ │ ├── html.spec.tsx │ │ ├── html.tsx │ │ └── index.ts │ └── tsconfig.json ├── img │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── img.spec.tsx.snap │ │ ├── img.spec.tsx │ │ ├── img.tsx │ │ └── index.ts │ └── tsconfig.json ├── link │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── link.spec.tsx.snap │ │ ├── index.ts │ │ ├── link.spec.tsx │ │ └── link.tsx │ └── tsconfig.json ├── markdown │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── markdown.spec.tsx.snap │ │ ├── index.ts │ │ ├── markdown.spec.tsx │ │ └── markdown.tsx │ └── tsconfig.json ├── preview-server │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── index.mjs │ ├── license.md │ ├── module-punycode.d.ts │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── postcss.config.js │ ├── readme.md │ ├── scripts │ │ ├── build-preview-server.mts │ │ ├── dev.mts │ │ ├── fill-caniemail-data.mts │ │ ├── seed.mts │ │ └── utils │ │ │ └── default-seed │ │ │ ├── auth │ │ │ ├── account-confirmation.tsx │ │ │ └── forgot-password.tsx │ │ │ ├── communications │ │ │ ├── payment-overdue.tsx │ │ │ ├── team-invite.tsx │ │ │ └── webhooks-failed.tsx │ │ │ ├── feedback-request.tsx │ │ │ └── marketing │ │ │ └── changelog.tsx │ ├── src │ │ ├── actions │ │ │ ├── email-validation │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── check-images.spec.tsx.snap │ │ │ │ ├── caniemail-data.ts │ │ │ │ ├── check-compatibility.ts │ │ │ │ ├── check-images.spec.tsx │ │ │ │ ├── check-images.ts │ │ │ │ ├── check-links.spec.tsx │ │ │ │ ├── check-links.ts │ │ │ │ ├── get-code-location-from-ast-element.ts │ │ │ │ └── quick-fetch.ts │ │ │ ├── get-email-path-from-slug.ts │ │ │ ├── get-emails-directory-metadata-action.ts │ │ │ └── render-email-by-path.tsx │ │ ├── animated-icons-data │ │ │ ├── help.json │ │ │ ├── link.json │ │ │ ├── load.json │ │ │ └── mail.json │ │ ├── app │ │ │ ├── env.ts │ │ │ ├── favicon.ico │ │ │ ├── fonts.ts │ │ │ ├── fonts │ │ │ │ └── SFMono │ │ │ │ │ ├── SFMonoBold.otf │ │ │ │ │ ├── SFMonoBoldItalic.otf │ │ │ │ │ ├── SFMonoHeavy.otf │ │ │ │ │ ├── SFMonoHeavyItalic.otf │ │ │ │ │ ├── SFMonoLight.otf │ │ │ │ │ ├── SFMonoLightItalic.otf │ │ │ │ │ ├── SFMonoMedium.otf │ │ │ │ │ ├── SFMonoMediumItalic.otf │ │ │ │ │ ├── SFMonoRegular.otf │ │ │ │ │ ├── SFMonoRegularItalic.otf │ │ │ │ │ ├── SFMonoSemibold.otf │ │ │ │ │ └── SFMonoSemiboldItalic.otf │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ ├── logo.png │ │ │ ├── page.tsx │ │ │ └── preview │ │ │ │ └── [...slug] │ │ │ │ ├── page.tsx │ │ │ │ ├── preview.tsx │ │ │ │ └── rendering-error.tsx │ │ ├── components │ │ │ ├── button.tsx │ │ │ ├── code-container.tsx │ │ │ ├── code-snippet.tsx │ │ │ ├── code.tsx │ │ │ ├── heading.tsx │ │ │ ├── icons │ │ │ │ ├── icon-arrow-down.tsx │ │ │ │ ├── icon-base.tsx │ │ │ │ ├── icon-bug.tsx │ │ │ │ ├── icon-button.tsx │ │ │ │ ├── icon-check.tsx │ │ │ │ ├── icon-clipboard.tsx │ │ │ │ ├── icon-download.tsx │ │ │ │ ├── icon-email.tsx │ │ │ │ ├── icon-file.tsx │ │ │ │ ├── icon-folder-open.tsx │ │ │ │ ├── icon-folder.tsx │ │ │ │ ├── icon-hide-sidebar.tsx │ │ │ │ ├── icon-image.tsx │ │ │ │ ├── icon-info.tsx │ │ │ │ ├── icon-link.tsx │ │ │ │ ├── icon-monitor.tsx │ │ │ │ ├── icon-phone.tsx │ │ │ │ ├── icon-reload.tsx │ │ │ │ ├── icon-source.tsx │ │ │ │ ├── icon-stamp.tsx │ │ │ │ └── icon-warning.tsx │ │ │ ├── index.ts │ │ │ ├── logo.tsx │ │ │ ├── resizable-wrapper.tsx │ │ │ ├── send.tsx │ │ │ ├── shell.tsx │ │ │ ├── sidebar │ │ │ │ ├── file-tree-directory-children.tsx │ │ │ │ ├── file-tree-directory.tsx │ │ │ │ ├── file-tree.tsx │ │ │ │ ├── index.ts │ │ │ │ └── sidebar.tsx │ │ │ ├── text.tsx │ │ │ ├── toolbar.tsx │ │ │ ├── toolbar │ │ │ │ ├── checking-results.tsx │ │ │ │ ├── code-preview-line-link.tsx │ │ │ │ ├── compatibility.tsx │ │ │ │ ├── linter.tsx │ │ │ │ ├── results-table.tsx │ │ │ │ ├── results.tsx │ │ │ │ ├── spam-assassin.tsx │ │ │ │ ├── toolbar-button.tsx │ │ │ │ └── use-cached-state.ts │ │ │ ├── tooltip-content.tsx │ │ │ ├── tooltip.tsx │ │ │ ├── topbar.tsx │ │ │ └── topbar │ │ │ │ ├── active-view-toggle-group.tsx │ │ │ │ └── view-size-controls.tsx │ │ ├── contexts │ │ │ ├── emails.tsx │ │ │ ├── fragment-identifier.tsx │ │ │ └── preview.tsx │ │ ├── hooks │ │ │ ├── use-clamped-state.ts │ │ │ ├── use-email-rendering-result.ts │ │ │ ├── use-fragment-identifier.ts │ │ │ ├── use-hot-reload.ts │ │ │ ├── use-icon-animation.ts │ │ │ └── use-rendering-metadata.ts │ │ └── utils │ │ │ ├── __snapshots__ │ │ │ └── get-email-component.spec.ts.snap │ │ │ ├── caniemail │ │ │ ├── all-css-properties.ts │ │ │ ├── ast │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── get-object-variables.spec.ts.snap │ │ │ │ │ └── get-used-style-properties.spec.ts.snap │ │ │ │ ├── get-object-variables.spec.ts │ │ │ │ ├── get-object-variables.ts │ │ │ │ ├── get-used-style-properties.spec.ts │ │ │ │ └── get-used-style-properties.ts │ │ │ ├── get-compatibility-stats-for-entry.ts │ │ │ ├── get-css-functions.ts │ │ │ ├── get-css-property-names.ts │ │ │ ├── get-css-property-with-value.ts │ │ │ ├── get-css-unit.ts │ │ │ ├── get-element-attributes.ts │ │ │ ├── get-element-names.ts │ │ │ └── tailwind │ │ │ │ ├── generate-tailwind-rules.ts │ │ │ │ ├── get-tailwind-config.ts │ │ │ │ ├── get-tailwind-metadata.spec.ts │ │ │ │ ├── get-tailwind-metadata.ts │ │ │ │ └── setup-tailwind-context.ts │ │ │ ├── cn.ts │ │ │ ├── constants.ts │ │ │ ├── contains-email-template.spec.ts │ │ │ ├── contains-email-template.ts │ │ │ ├── copy-text-to-clipboard.ts │ │ │ ├── esbuild │ │ │ ├── escape-string-for-regex.ts │ │ │ └── renderring-utilities-exporter.ts │ │ │ ├── get-email-component.spec.ts │ │ │ ├── get-email-component.ts │ │ │ ├── get-emails-directory-metadata.spec.ts │ │ │ ├── get-emails-directory-metadata.ts │ │ │ ├── get-line-and-column-from-offset.spec.ts │ │ │ ├── get-line-and-column-from-offset.ts │ │ │ ├── improve-error-with-sourcemap.ts │ │ │ ├── index.ts │ │ │ ├── js-email-detection.spec.ts │ │ │ ├── language-map.ts │ │ │ ├── linting.ts │ │ │ ├── load-stream.ts │ │ │ ├── register-spinner-autostopping.ts │ │ │ ├── result.ts │ │ │ ├── run-bundled-code.ts │ │ │ ├── sanitize.ts │ │ │ ├── static-node-modules-for-vm.ts │ │ │ ├── testing │ │ │ ├── js-email-export-default.js │ │ │ ├── js-email-test.js │ │ │ ├── mdx-email-test.js │ │ │ └── request-response-email.tsx │ │ │ ├── types │ │ │ ├── as.ts │ │ │ ├── email-template.ts │ │ │ ├── error-object.ts │ │ │ └── hot-reload-change.ts │ │ │ └── unreachable.ts │ ├── tailwind-internals.d.ts │ ├── tailwind.config.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── preview │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── preview.spec.tsx.snap │ │ ├── index.ts │ │ ├── preview.spec.tsx │ │ └── preview.tsx │ └── tsconfig.json ├── react-email │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── dev │ │ ├── CHANGELOG.md │ │ ├── index.js │ │ └── package.json │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── actions │ │ │ └── email-validation │ │ │ │ └── __snapshots__ │ │ │ │ └── check-images.spec.tsx.snap │ │ ├── commands │ │ │ ├── .npmignore │ │ │ ├── build.ts │ │ │ ├── dev.ts │ │ │ ├── export.ts │ │ │ ├── start.ts │ │ │ └── testing │ │ │ │ ├── .gitignore │ │ │ │ ├── __snapshots__ │ │ │ │ └── export.spec.ts.snap │ │ │ │ └── export.spec.ts │ │ ├── index.ts │ │ └── utils │ │ │ ├── __snapshots__ │ │ │ └── tree.spec.ts.snap │ │ │ ├── esbuild │ │ │ ├── escape-string-for-regex.ts │ │ │ └── renderring-utilities-exporter.ts │ │ │ ├── get-emails-directory-metadata.spec.ts │ │ │ ├── get-emails-directory-metadata.ts │ │ │ ├── get-preview-server-location.ts │ │ │ ├── index.ts │ │ │ ├── packageJson.ts │ │ │ ├── preview │ │ │ ├── get-env-variables-for-preview-app.ts │ │ │ ├── hot-reloading │ │ │ │ ├── create-dependency-graph.spec.ts │ │ │ │ ├── create-dependency-graph.ts │ │ │ │ ├── get-imported-modules.spec.ts │ │ │ │ ├── get-imported-modules.ts │ │ │ │ ├── resolve-path-aliases.spec.ts │ │ │ │ ├── resolve-path-aliases.ts │ │ │ │ ├── setup-hot-reloading.ts │ │ │ │ └── test │ │ │ │ │ ├── some-file.ts │ │ │ │ │ └── tsconfig.json │ │ │ ├── index.ts │ │ │ ├── serve-static-file.ts │ │ │ └── start-dev-server.ts │ │ │ ├── register-spinner-autostopping.ts │ │ │ ├── tree.spec.ts │ │ │ ├── tree.ts │ │ │ └── types │ │ │ ├── hot-reload-change.ts │ │ │ └── hot-reload-event.ts │ ├── tsconfig.json │ ├── tsup.config.ts │ └── vitest.config.ts ├── render │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── browser │ │ │ ├── __snapshots__ │ │ │ │ └── render-web.spec.tsx.snap │ │ │ ├── index.ts │ │ │ ├── read-stream.ts │ │ │ ├── render-web.spec.tsx │ │ │ └── render.tsx │ │ ├── node │ │ │ ├── __snapshots__ │ │ │ │ ├── render-async-edge.spec.tsx.snap │ │ │ │ ├── render-async-node.spec.tsx.snap │ │ │ │ ├── render-edge.spec.tsx.snap │ │ │ │ └── render-node.spec.tsx.snap │ │ │ ├── index.ts │ │ │ ├── read-stream.ts │ │ │ ├── render-edge.spec.tsx │ │ │ ├── render-node.spec.tsx │ │ │ └── render.tsx │ │ ├── react-internals.d.ts │ │ └── shared │ │ │ ├── options.ts │ │ │ ├── plain-text-selectors.ts │ │ │ └── utils │ │ │ ├── __snapshots__ │ │ │ └── pretty.spec.ts.snap │ │ │ ├── pretty.spec.ts │ │ │ ├── pretty.ts │ │ │ ├── preview.tsx │ │ │ ├── stripe-email.html │ │ │ └── template.tsx │ ├── tsconfig.json │ └── tsup.config.ts ├── row │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── row.spec.tsx.snap │ │ ├── index.ts │ │ ├── row.spec.tsx │ │ └── row.tsx │ └── tsconfig.json ├── section │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── section.spec.tsx.snap │ │ ├── index.ts │ │ ├── section.spec.tsx │ │ └── section.tsx │ └── tsconfig.json ├── tailwind │ ├── CHANGELOG.md │ ├── copy-tailwind-types.mjs │ ├── integrations │ │ ├── integrations.spec.ts │ │ ├── nextjs │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── emails │ │ │ │ └── vercel-invite-user.tsx │ │ │ ├── next-env.d.ts │ │ │ ├── next.config.mjs │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ └── app │ │ │ │ │ ├── favicon.ico │ │ │ │ │ ├── layout.tsx │ │ │ │ │ └── page.tsx │ │ │ └── tsconfig.json │ │ └── vite │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── emails │ │ │ └── vercel-invite-user.tsx │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── public │ │ │ └── vite.svg │ │ │ ├── src │ │ │ ├── App.tsx │ │ │ ├── main.tsx │ │ │ └── vite-env.d.ts │ │ │ ├── tsconfig.json │ │ │ ├── tsconfig.node.json │ │ │ └── vite.config.ts │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── tailwind.spec.tsx.snap │ │ ├── hooks │ │ │ ├── __snapshots__ │ │ │ │ └── use-tailwind.spec.ts.snap │ │ │ └── use-suspensed-promise.ts │ │ ├── index.ts │ │ ├── tailwind.spec.tsx │ │ ├── tailwind.tsx │ │ └── utils │ │ │ ├── __snapshots__ │ │ │ └── quick-safe-render-to-string.spec.tsx.snap │ │ │ ├── compatibility │ │ │ ├── convert-css-property-to-react-property.ts │ │ │ ├── escape-class-name.spec.ts │ │ │ ├── escape-class-name.ts │ │ │ ├── sanitize-class-name.spec.ts │ │ │ ├── sanitize-class-name.ts │ │ │ └── unescape-class.ts │ │ │ ├── css │ │ │ ├── make-inline-styles-for.spec.ts │ │ │ ├── make-inline-styles-for.ts │ │ │ ├── minify-css.ts │ │ │ ├── remove-if-empty-recursively.ts │ │ │ ├── remove-rule-duplicates-from-root.ts │ │ │ ├── resolve-all-css-variables.spec.ts │ │ │ ├── resolve-all-css-variables.ts │ │ │ ├── sanitize-declarations.ts │ │ │ ├── sanitize-non-inlinable-classes.spec.ts │ │ │ └── sanitize-non-inlinable-classes.ts │ │ │ ├── react │ │ │ ├── is-component.ts │ │ │ ├── map-react-tree.spec.tsx │ │ │ └── map-react-tree.ts │ │ │ ├── tailwindcss │ │ │ ├── __snapshots__ │ │ │ │ └── setup-tailwind.spec.ts.snap │ │ │ ├── clone-element-with-inlined-styles.ts │ │ │ ├── setup-tailwind-context.ts │ │ │ ├── setup-tailwind.spec.ts │ │ │ ├── setup-tailwind.ts │ │ │ └── tailwind-internals.d.ts │ │ │ └── text │ │ │ └── from-dash-case-to-camel-case.ts │ ├── tsconfig.json │ ├── vite.config.ts │ └── vitest.config.ts ├── text │ ├── CHANGELOG.md │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── text.spec.tsx.snap │ │ ├── index.ts │ │ ├── text.spec.tsx │ │ ├── text.tsx │ │ └── utils │ │ │ ├── compute-margins.spec.ts │ │ │ └── compute-margins.ts │ └── tsconfig.json └── tsconfig │ ├── base.json │ ├── nextjs.json │ ├── package.json │ └── react-library.json ├── playground ├── README.md ├── components.ts ├── emails │ ├── .gitignore │ └── example.tsx ├── package.json └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── renovate.json ├── scripts ├── check-dependency-versions.ts └── pull-request-title-check.ts ├── tsconfig.json ├── turbo.json └── vitest.config.ts /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", 3 | "access": "public", 4 | "baseBranch": "main", 5 | "changelog": "@changesets/cli/changelog", 6 | "commit": false, 7 | "fixed": [["react-email", "@react-email/preview-server"]], 8 | "ignore": [ 9 | "@benchmarks/preview-server", 10 | "@benchmarks/tailwind-component", 11 | "demo", 12 | "email-dev", 13 | "web" 14 | ], 15 | "updateInternalDependencies": "patch" 16 | } 17 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @resend/engineering 2 | -------------------------------------------------------------------------------- /.github/DISCUSSION_TEMPLATE/help.yml: -------------------------------------------------------------------------------- 1 | body: 2 | - type: textarea 3 | attributes: 4 | label: Summary 5 | description: What do you need help with? 6 | validations: 7 | required: true 8 | - type: textarea 9 | attributes: 10 | label: Additional information 11 | description: Any code snippets, error messages, or dependency details that may be related? 12 | render: js 13 | validations: 14 | required: false 15 | - type: input 16 | attributes: 17 | label: Example 18 | description: A link to a minimal reproduction is helpful for collaborative debugging! 19 | validations: 20 | required: false 21 | -------------------------------------------------------------------------------- /.github/DISCUSSION_TEMPLATE/ideas.yml: -------------------------------------------------------------------------------- 1 | body: 2 | - type: textarea 3 | attributes: 4 | label: Goals 5 | description: Short list of what the feature request aims to address? 6 | value: | 7 | 1. 8 | 2. 9 | 3. 10 | validations: 11 | required: true 12 | - type: textarea 13 | attributes: 14 | label: Background 15 | description: Discuss prior art, why do you think this feature is needed? Are there current alternatives? 16 | validations: 17 | required: true 18 | - type: textarea 19 | attributes: 20 | label: Proposal 21 | description: How should this feature be implemented? Are you interested in contributing? 22 | validations: 23 | required: true 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question 4 | url: https://github.com/resend/react-email/discussions 5 | about: Ask questions and discuss with other community members 6 | - name: Feature request 7 | url: https://github.com/resend/react-email/discussions/new?category=ideas 8 | about: Feature requests should be opened as discussions 9 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | <!-- Thanks for opening a PR! Your contribution is much appreciated. 2 | To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below. 3 | Choose the right checklist for the change(s) that you're making: 4 | 5 | The PR name should follow `<type>(<scope>): <Message>` 6 | 7 | Examples: 8 | - New feature: `feat(button): Add new thing` 9 | - Fix: `fix(react-email): Dev command 10 | - Misc/Chore: `chore(root): Update `readme.md` 11 | --> 12 | -------------------------------------------------------------------------------- /.github/workflows/pin-dependencies-check.yml: -------------------------------------------------------------------------------- 1 | name: Pin Dependencies Check 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - canary 7 | pull_request: 8 | permissions: 9 | contents: read 10 | pull-requests: read 11 | jobs: 12 | pin-dependencies-check: 13 | runs-on: buildjet-4vcpu-ubuntu-2204 14 | container: 15 | image: node:22 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | - name: Check for pinned dependencies 20 | run: npx tsx ./scripts/check-dependency-versions.ts 21 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-title-check.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Title Check 2 | on: 3 | pull_request: 4 | types: [opened, edited, synchronize] 5 | permissions: 6 | pull-requests: read 7 | jobs: 8 | pull-request-title-check: 9 | runs-on: buildjet-4vcpu-ubuntu-2204 10 | container: 11 | image: node:22 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | - name: Check pull request title 16 | run: | 17 | npx tsx ./scripts/pull-request-title-check.ts 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage 10 | **/*/package-lock.json 11 | **/*/yalc.lock 12 | 13 | # next.js 14 | .next/ 15 | out/ 16 | build 17 | dist 18 | .vercel 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | .react-email 24 | 25 | # debug 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | .pnpm-debug.log* 30 | 31 | # local env files 32 | .env 33 | .env.local 34 | .env.development.local 35 | .env.test.local 36 | .env.production.local 37 | 38 | # turbo 39 | .turbo 40 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers = true 2 | -------------------------------------------------------------------------------- /apps/demo/.gitignore: -------------------------------------------------------------------------------- 1 | .react-email 2 | .vercel 3 | -------------------------------------------------------------------------------- /apps/demo/emails/static/airbnb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/airbnb-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/airbnb-review-user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/airbnb-review-user.jpg -------------------------------------------------------------------------------- /apps/demo/emails/static/amazon-book.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/amazon-book.jpg -------------------------------------------------------------------------------- /apps/demo/emails/static/amazon-facebook.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/amazon-facebook.jpg -------------------------------------------------------------------------------- /apps/demo/emails/static/amazon-instagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/amazon-instagram.jpg -------------------------------------------------------------------------------- /apps/demo/emails/static/amazon-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/amazon-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/amazon-prime-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/amazon-prime-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/amazon-rating.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/amazon-rating.gif -------------------------------------------------------------------------------- /apps/demo/emails/static/amazon-twitter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/amazon-twitter.jpg -------------------------------------------------------------------------------- /apps/demo/emails/static/apple-card-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/apple-card-icon.png -------------------------------------------------------------------------------- /apps/demo/emails/static/apple-hbo-max-icon.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/apple-hbo-max-icon.jpeg -------------------------------------------------------------------------------- /apps/demo/emails/static/apple-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/apple-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/apple-wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/apple-wallet.png -------------------------------------------------------------------------------- /apps/demo/emails/static/aws-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/aws-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/codepen-challengers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/codepen-challengers.png -------------------------------------------------------------------------------- /apps/demo/emails/static/codepen-cube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/codepen-cube.png -------------------------------------------------------------------------------- /apps/demo/emails/static/codepen-pro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/codepen-pro.png -------------------------------------------------------------------------------- /apps/demo/emails/static/dropbox-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/dropbox-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/github.png -------------------------------------------------------------------------------- /apps/demo/emails/static/google-play-academy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/google-play-academy.png -------------------------------------------------------------------------------- /apps/demo/emails/static/google-play-chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/google-play-chat.png -------------------------------------------------------------------------------- /apps/demo/emails/static/google-play-footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/google-play-footer.png -------------------------------------------------------------------------------- /apps/demo/emails/static/google-play-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/google-play-header.png -------------------------------------------------------------------------------- /apps/demo/emails/static/google-play-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/google-play-icon.png -------------------------------------------------------------------------------- /apps/demo/emails/static/google-play-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/google-play-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/google-play-pl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/google-play-pl.png -------------------------------------------------------------------------------- /apps/demo/emails/static/google-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/google-play.png -------------------------------------------------------------------------------- /apps/demo/emails/static/koala-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/koala-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/linear-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/linear-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/netlify-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/netlify-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/nike-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/nike-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/nike-phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/nike-phone.png -------------------------------------------------------------------------------- /apps/demo/emails/static/nike-product.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/nike-product.png -------------------------------------------------------------------------------- /apps/demo/emails/static/nike-recomendation-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/nike-recomendation-1.png -------------------------------------------------------------------------------- /apps/demo/emails/static/nike-recomendation-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/nike-recomendation-2.png -------------------------------------------------------------------------------- /apps/demo/emails/static/nike-recomendation-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/nike-recomendation-3.png -------------------------------------------------------------------------------- /apps/demo/emails/static/nike-recomendation-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/nike-recomendation-4.png -------------------------------------------------------------------------------- /apps/demo/emails/static/notion-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/notion-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/plaid-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/plaid-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/raycast-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/raycast-bg.png -------------------------------------------------------------------------------- /apps/demo/emails/static/raycast-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/raycast-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/slack-facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/slack-facebook.png -------------------------------------------------------------------------------- /apps/demo/emails/static/slack-linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/slack-linkedin.png -------------------------------------------------------------------------------- /apps/demo/emails/static/slack-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/slack-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/slack-twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/slack-twitter.png -------------------------------------------------------------------------------- /apps/demo/emails/static/stack-overflow-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/stack-overflow-header.png -------------------------------------------------------------------------------- /apps/demo/emails/static/stack-overflow-logo-sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/stack-overflow-logo-sm.png -------------------------------------------------------------------------------- /apps/demo/emails/static/stack-overflow-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/stack-overflow-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/stripe-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/stripe-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/twitch-icon-facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/twitch-icon-facebook.png -------------------------------------------------------------------------------- /apps/demo/emails/static/twitch-icon-twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/twitch-icon-twitter.png -------------------------------------------------------------------------------- /apps/demo/emails/static/twitch-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/twitch-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/vercel-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/vercel-arrow.png -------------------------------------------------------------------------------- /apps/demo/emails/static/vercel-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/vercel-logo.png -------------------------------------------------------------------------------- /apps/demo/emails/static/vercel-team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/vercel-team.png -------------------------------------------------------------------------------- /apps/demo/emails/static/vercel-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/vercel-user.png -------------------------------------------------------------------------------- /apps/demo/emails/static/yelp-footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/yelp-footer.png -------------------------------------------------------------------------------- /apps/demo/emails/static/yelp-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/yelp-header.png -------------------------------------------------------------------------------- /apps/demo/emails/static/yelp-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/demo/emails/static/yelp-logo.png -------------------------------------------------------------------------------- /apps/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "email-dev build", 7 | "dev": "email-dev dev", 8 | "start": "email-dev start", 9 | "export": "email-dev export" 10 | }, 11 | "dependencies": { 12 | "@react-email/components": "workspace:*", 13 | "react": "^19", 14 | "react-dom": "^19", 15 | "email-dev": "workspace:*" 16 | }, 17 | "devDependencies": { 18 | "@react-email/preview-server": "workspace:*", 19 | "next": "^15.3.2", 20 | "@types/react": "^19", 21 | "@types/react-dom": "^19" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/docs/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/docs/favicon.png -------------------------------------------------------------------------------- /apps/docs/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/docs/images/background.png -------------------------------------------------------------------------------- /apps/docs/images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/docs/images/bg.png -------------------------------------------------------------------------------- /apps/docs/images/local-dev.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/docs/images/local-dev.jpg -------------------------------------------------------------------------------- /apps/docs/images/preview-server-vercel-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/docs/images/preview-server-vercel-settings.png -------------------------------------------------------------------------------- /apps/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "mintlify dev" 7 | }, 8 | "dependencies": { 9 | "mintlify": "4.1.96", 10 | "zod": "3.24.3" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /apps/docs/snippets/localdev.mdx: -------------------------------------------------------------------------------- 1 | 1. Clone the repository: 2 | 3 | ```sh 4 | git clone https://github.com/resend/react-email.git 5 | ``` 6 | 7 | 2. Install all dependencies: 8 | 9 | ```sh 10 | pnpm install 11 | ``` 12 | 13 | 3. Run local servers and watch for changes: 14 | 15 | ```sh 16 | pnpm dev 17 | ``` 18 | -------------------------------------------------------------------------------- /apps/docs/snippets/next-steps.mdx: -------------------------------------------------------------------------------- 1 | Try adding these other components to your email. 2 | 3 | <CardGroup> 4 | <Card title="Image" icon='image' href="/components/image"> 5 | Display an image in your email. 6 | </Card> 7 | <Card title="Link" icon='link' href="/components/link"> 8 | A hyperlink to web pages or anything else a URL can address. 9 | </Card> 10 | <Card title="Divider" icon='horizontal-rule' href="/components/hr"> 11 | Display a divider that separates content areas in your email. 12 | </Card> 13 | <Card 14 | title="Preview" 15 | icon='magnifying-glass' 16 | href="/components/preview" 17 | > 18 | A preview text that will be displayed in the inbox of the recipient. 19 | </Card> 20 | </CardGroup> 21 | 22 | -------------------------------------------------------------------------------- /apps/web/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.css": "tailwindcss" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /apps/web/components/code-inline-with-different-colors/inline-styles.tsx: -------------------------------------------------------------------------------- 1 | import { CodeInline, Text } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Text style={{ textAlign: 'center' }}> 6 | Install the{' '} 7 | <CodeInline 8 | style={{ 9 | backgroundColor: 'rgb(134,239,172)', 10 | borderRadius: 6, 11 | paddingLeft: 4, 12 | paddingRight: 4, 13 | paddingTop: 2, 14 | paddingBottom: 2, 15 | }} 16 | > 17 | @react-email/components 18 | </CodeInline>{' '} 19 | package 20 | </Text> 21 | ); 22 | 23 | export default () => { 24 | return <Layout>{component}</Layout>; 25 | }; 26 | -------------------------------------------------------------------------------- /apps/web/components/code-inline-with-different-colors/tailwind.tsx: -------------------------------------------------------------------------------- 1 | import { CodeInline, Text } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Text className="text-center"> 6 | Install the{' '} 7 | <CodeInline className="rounded-[6px] bg-green-300 px-[4px] py-[2px]"> 8 | @react-email/components 9 | </CodeInline>{' '} 10 | package 11 | </Text> 12 | ); 13 | 14 | export default () => { 15 | return <Layout>{component}</Layout>; 16 | }; 17 | -------------------------------------------------------------------------------- /apps/web/components/divider-between-rows-and-columns/inline-styles.tsx: -------------------------------------------------------------------------------- 1 | import { Column, Hr, Row } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <> 6 | <Row> 7 | <Column>First column</Column> 8 | <Column>Second column</Column> 9 | </Row> 10 | <Hr 11 | style={{ 12 | marginTop: 16, 13 | borderColor: 'rgb(209,213,219)', 14 | marginBottom: 16, 15 | }} 16 | /> 17 | <Row> 18 | <Column>First column</Column> 19 | <Column>Second column</Column> 20 | </Row> 21 | </> 22 | ); 23 | 24 | export default () => { 25 | return <Layout>{component}</Layout>; 26 | }; 27 | -------------------------------------------------------------------------------- /apps/web/components/divider-between-rows-and-columns/tailwind.tsx: -------------------------------------------------------------------------------- 1 | import { Column, Hr, Row } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <> 6 | <Row> 7 | <Column>First column</Column> 8 | <Column>Second column</Column> 9 | </Row> 10 | <Hr className="my-[16px] border-gray-300" /> 11 | <Row> 12 | <Column>First column</Column> 13 | <Column>Second column</Column> 14 | </Row> 15 | </> 16 | ); 17 | 18 | export default () => { 19 | return <Layout>{component}</Layout>; 20 | }; 21 | -------------------------------------------------------------------------------- /apps/web/components/link-inline-with-text/index.tsx: -------------------------------------------------------------------------------- 1 | import { Link, Text } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Text> 6 | This is <Link href="https://react.email">React Email</Link> 7 | </Text> 8 | ); 9 | 10 | export default () => { 11 | return <Layout>{component}</Layout>; 12 | }; 13 | -------------------------------------------------------------------------------- /apps/web/components/markdown-with-container-styles/index.tsx: -------------------------------------------------------------------------------- 1 | import { Markdown } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Markdown 6 | markdownContainerStyles={{ 7 | marginBlock: 30, 8 | }} 9 | > 10 | {`## Hello, this is my email template 11 | 12 | This is meant to be rendered as a paragraph. There is no way around it. 13 | 14 | ### Another heading that I wrote 15 | `} 16 | </Markdown> 17 | ); 18 | 19 | export default () => { 20 | return <Layout>{component}</Layout>; 21 | }; 22 | -------------------------------------------------------------------------------- /apps/web/components/markdown-with-custom-styles/index.tsx: -------------------------------------------------------------------------------- 1 | import { Markdown } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Markdown 6 | markdownCustomStyles={{ 7 | h1: { color: 'red' }, 8 | h2: { color: 'blue' }, 9 | codeInline: { background: 'grey' }, 10 | }} 11 | > 12 | {`## Hello, this is my email template 13 | 14 | This is meant to be rendered as a paragraph. There is no way around it. 15 | 16 | ### Another heading that I wrote 17 | `} 18 | </Markdown> 19 | ); 20 | 21 | export default () => { 22 | return <Layout>{component}</Layout>; 23 | }; 24 | -------------------------------------------------------------------------------- /apps/web/components/one-row-three-columns/tailwind.tsx: -------------------------------------------------------------------------------- 1 | import { Column, Row } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Row> 6 | <Column align="center" className="h-[40px] w-1/3 bg-orange-400/60"> 7 | 1/3 8 | </Column> 9 | <Column align="center" className="h-[40px] w-1/3 bg-emerald-400/60"> 10 | 1/3 11 | </Column> 12 | <Column align="center" className="h-[40px] w-1/3 bg-cyan-400/60"> 13 | 1/3 14 | </Column> 15 | </Row> 16 | ); 17 | 18 | export default () => { 19 | return <Layout>{component}</Layout>; 20 | }; 21 | -------------------------------------------------------------------------------- /apps/web/components/rounded-image/inline-styles.tsx: -------------------------------------------------------------------------------- 1 | import { Img } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Img 6 | alt="Stagg Electric Kettle" 7 | height={250} 8 | src="/static/stagg-eletric-kettle.jpg" 9 | style={{ borderRadius: 12, margin: '0 auto' }} 10 | /> 11 | ); 12 | 13 | export default () => { 14 | return <Layout>{component}</Layout>; 15 | }; 16 | -------------------------------------------------------------------------------- /apps/web/components/rounded-image/tailwind.tsx: -------------------------------------------------------------------------------- 1 | import { Img } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Img 6 | alt="Stagg Electric Kettle" 7 | className="rounded-[12px] [margin:0_auto]" 8 | height={250} 9 | src="/static/stagg-eletric-kettle.jpg" 10 | /> 11 | ); 12 | 13 | export default () => { 14 | return <Layout>{component}</Layout>; 15 | }; 16 | -------------------------------------------------------------------------------- /apps/web/components/section-with-rows-and-columns/index.tsx: -------------------------------------------------------------------------------- 1 | import { Column, Row, Section } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Section> 6 | <Row> 7 | <Column>Column 1, Row 1</Column> 8 | <Column>Column 2, Row 1</Column> 9 | </Row> 10 | <Row> 11 | <Column>Column 1, Row 2</Column> 12 | <Column>Column 2, Row 2</Column> 13 | </Row> 14 | </Section> 15 | ); 16 | 17 | export default () => { 18 | return <Layout>{component}</Layout>; 19 | }; 20 | -------------------------------------------------------------------------------- /apps/web/components/simple-code-inline/inline-styles.tsx: -------------------------------------------------------------------------------- 1 | import { CodeInline, Text } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Text style={{ textAlign: 'center' }}> 6 | Install the{' '} 7 | <CodeInline 8 | style={{ 9 | backgroundColor: 'rgb(209,213,219)', 10 | borderRadius: 6, 11 | paddingLeft: 4, 12 | paddingRight: 4, 13 | paddingTop: 2, 14 | paddingBottom: 2, 15 | }} 16 | > 17 | @react-email/components 18 | </CodeInline>{' '} 19 | package 20 | </Text> 21 | ); 22 | 23 | export default () => { 24 | return <Layout>{component}</Layout>; 25 | }; 26 | -------------------------------------------------------------------------------- /apps/web/components/simple-code-inline/tailwind.tsx: -------------------------------------------------------------------------------- 1 | import { CodeInline, Text } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Text className="text-center"> 6 | Install the{' '} 7 | <CodeInline className="rounded-[6px] bg-gray-300 px-[4px] py-[2px]"> 8 | @react-email/components 9 | </CodeInline>{' '} 10 | package 11 | </Text> 12 | ); 13 | 14 | export default () => { 15 | return <Layout>{component}</Layout>; 16 | }; 17 | -------------------------------------------------------------------------------- /apps/web/components/simple-container/inline-styles.tsx: -------------------------------------------------------------------------------- 1 | import { Container, Text } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Container style={{ backgroundColor: 'rgb(156,163,175)' }}> 6 | <Text 7 | style={{ color: 'rgb(255,255,255)', paddingLeft: 12, paddingRight: 12 }} 8 | > 9 | Hello, I am a container. I keep content centered and maintain it to a 10 | maximum width while still taking up as much space as possible! 11 | </Text> 12 | </Container> 13 | ); 14 | 15 | export default () => { 16 | return <Layout>{component}</Layout>; 17 | }; 18 | -------------------------------------------------------------------------------- /apps/web/components/simple-container/tailwind.tsx: -------------------------------------------------------------------------------- 1 | import { Container, Text } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Container className="bg-gray-400"> 6 | <Text className="px-[12px] text-white"> 7 | Hello, I am a container. I keep content centered and maintain it to a 8 | maximum width while still taking up as much space as possible! 9 | </Text> 10 | </Container> 11 | ); 12 | 13 | export default () => { 14 | return <Layout>{component}</Layout>; 15 | }; 16 | -------------------------------------------------------------------------------- /apps/web/components/simple-divider/inline-styles.tsx: -------------------------------------------------------------------------------- 1 | import { Hr, Text } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <> 6 | <Text>Before divider</Text> 7 | <Hr 8 | style={{ 9 | marginTop: 16, 10 | borderColor: 'rgb(209,213,219)', 11 | marginBottom: 16, 12 | borderTopWidth: 2, 13 | }} 14 | /> 15 | <Text>After divider</Text> 16 | </> 17 | ); 18 | 19 | export default () => { 20 | return <Layout>{component}</Layout>; 21 | }; 22 | -------------------------------------------------------------------------------- /apps/web/components/simple-divider/tailwind.tsx: -------------------------------------------------------------------------------- 1 | import { Hr, Text } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <> 6 | <Text>Before divider</Text> 7 | <Hr className="my-[16px] border-gray-300 border-t-2" /> 8 | <Text>After divider</Text> 9 | </> 10 | ); 11 | 12 | export default () => { 13 | return <Layout>{component}</Layout>; 14 | }; 15 | -------------------------------------------------------------------------------- /apps/web/components/simple-heading/inline-styles.tsx: -------------------------------------------------------------------------------- 1 | import { Heading } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Heading style={{ textAlign: 'center' }}>Ray Tomlinson</Heading> 6 | ); 7 | 8 | export default () => { 9 | return <Layout>{component}</Layout>; 10 | }; 11 | -------------------------------------------------------------------------------- /apps/web/components/simple-heading/tailwind.tsx: -------------------------------------------------------------------------------- 1 | import { Heading } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Heading className="text-center">Ray Tomlinson</Heading> 6 | ); 7 | 8 | export default () => { 9 | return <Layout>{component}</Layout>; 10 | }; 11 | -------------------------------------------------------------------------------- /apps/web/components/simple-image/inline-styles.tsx: -------------------------------------------------------------------------------- 1 | import { Img } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Img 6 | alt="Ode Grinder" 7 | height={250} 8 | src="/static/ode-grinder.jpg" 9 | style={{ marginLeft: 'auto', marginRight: 'auto' }} 10 | /> 11 | ); 12 | 13 | export default () => { 14 | return <Layout>{component}</Layout>; 15 | }; 16 | -------------------------------------------------------------------------------- /apps/web/components/simple-image/tailwind.tsx: -------------------------------------------------------------------------------- 1 | import { Img } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Img 6 | alt="Ode Grinder" 7 | className="mx-auto" 8 | height={250} 9 | src="/static/ode-grinder.jpg" 10 | /> 11 | ); 12 | 13 | export default () => { 14 | return <Layout>{component}</Layout>; 15 | }; 16 | -------------------------------------------------------------------------------- /apps/web/components/simple-link/index.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = <Link href="https://react.email">React Email</Link>; 5 | 6 | export default () => { 7 | return <Layout>{component}</Layout>; 8 | }; 9 | -------------------------------------------------------------------------------- /apps/web/components/simple-markdown/index.tsx: -------------------------------------------------------------------------------- 1 | import { Markdown } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Markdown> 6 | {`## Hello, this is my email template 7 | 8 | This is meant to be rendered as a paragraph. There is no way around it. 9 | 10 | ### Another heading that I wrote 11 | `} 12 | </Markdown> 13 | ); 14 | 15 | export default () => { 16 | return <Layout>{component}</Layout>; 17 | }; 18 | -------------------------------------------------------------------------------- /apps/web/components/simple-section/index.tsx: -------------------------------------------------------------------------------- 1 | import { Section, Text } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Section> 6 | <Text>Hello my section!</Text> 7 | </Section> 8 | ); 9 | 10 | export default () => { 11 | return <Layout>{component}</Layout>; 12 | }; 13 | -------------------------------------------------------------------------------- /apps/web/components/simple-text/index.tsx: -------------------------------------------------------------------------------- 1 | import { Text } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = <Text>A simple paragraph</Text>; 5 | 6 | export default () => { 7 | return <Layout>{component}</Layout>; 8 | }; 9 | -------------------------------------------------------------------------------- /apps/web/components/single-button/inline-styles.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Button 6 | href="https://react.email" 7 | style={{ 8 | width: '100%', 9 | boxSizing: 'border-box', 10 | padding: 12, 11 | fontWeight: 600, 12 | borderRadius: 8, 13 | textAlign: 'center', 14 | backgroundColor: 'rgb(79,70,229)', 15 | color: 'rgb(255,255,255)', 16 | }} 17 | > 18 | Get started 19 | </Button> 20 | ); 21 | 22 | export default () => { 23 | return <Layout>{component}</Layout>; 24 | }; 25 | -------------------------------------------------------------------------------- /apps/web/components/single-button/tailwind.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <Button 6 | className="box-border w-full rounded-[8px] bg-indigo-600 px-[12px] py-[12px] text-center font-semibold text-white" 7 | href="https://react.email" 8 | > 9 | Get started 10 | </Button> 11 | ); 12 | 13 | export default () => { 14 | return <Layout>{component}</Layout>; 15 | }; 16 | -------------------------------------------------------------------------------- /apps/web/components/static/cube-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/components/static/cube-icon.png -------------------------------------------------------------------------------- /apps/web/components/static/download-on-the-app-store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/components/static/download-on-the-app-store.png -------------------------------------------------------------------------------- /apps/web/components/static/facebook-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/components/static/facebook-logo.png -------------------------------------------------------------------------------- /apps/web/components/static/get-it-on-google-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/components/static/get-it-on-google-play.png -------------------------------------------------------------------------------- /apps/web/components/static/heart-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/components/static/heart-icon.png -------------------------------------------------------------------------------- /apps/web/components/static/in-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/components/static/in-icon.png -------------------------------------------------------------------------------- /apps/web/components/static/instagram-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/components/static/instagram-logo.png -------------------------------------------------------------------------------- /apps/web/components/static/logo-without-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/components/static/logo-without-background.png -------------------------------------------------------------------------------- /apps/web/components/static/megaphone-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/components/static/megaphone-icon.png -------------------------------------------------------------------------------- /apps/web/components/static/rocket-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/components/static/rocket-icon.png -------------------------------------------------------------------------------- /apps/web/components/static/steve-jobs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/components/static/steve-jobs.jpg -------------------------------------------------------------------------------- /apps/web/components/static/steve-wozniak.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/components/static/steve-wozniak.jpg -------------------------------------------------------------------------------- /apps/web/components/static/x-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/components/static/x-icon.png -------------------------------------------------------------------------------- /apps/web/components/static/x-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/components/static/x-logo.png -------------------------------------------------------------------------------- /apps/web/components/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /apps/web/components/text-with-styling/inline-styles.tsx: -------------------------------------------------------------------------------- 1 | import { Text } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <> 6 | <Text 7 | style={{ 8 | color: 'rgb(129,140,248)', 9 | fontSize: 24, 10 | lineHeight: '32px', 11 | fontWeight: 600, 12 | }} 13 | > 14 | Amazing content 15 | </Text> 16 | <Text> 17 | This is the actual content that the accented text above refers to. 18 | </Text> 19 | </> 20 | ); 21 | 22 | export default () => { 23 | return <Layout>{component}</Layout>; 24 | }; 25 | -------------------------------------------------------------------------------- /apps/web/components/text-with-styling/tailwind.tsx: -------------------------------------------------------------------------------- 1 | import { Text } from '@react-email/components'; 2 | import { Layout } from '../_components/layout'; 3 | 4 | export const component = ( 5 | <> 6 | <Text className="font-semibold text-[24px] text-indigo-400 leading-[32px]"> 7 | Amazing content 8 | </Text> 9 | <Text> 10 | This is the actual content that the accented text above refers to. 11 | </Text> 12 | </> 13 | ); 14 | 15 | export default () => { 16 | return <Layout>{component}</Layout>; 17 | }; 18 | -------------------------------------------------------------------------------- /apps/web/components/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react-jsx" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /apps/web/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="next" /> 2 | /// <reference types="next/image-types/global" /> 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/web/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/web/public/brand/logo-without-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/brand/logo-without-background.png -------------------------------------------------------------------------------- /apps/web/public/brand/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/brand/logo.png -------------------------------------------------------------------------------- /apps/web/public/brand/resend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/brand/resend.png -------------------------------------------------------------------------------- /apps/web/public/examples/airbnb-review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/airbnb-review.png -------------------------------------------------------------------------------- /apps/web/public/examples/apple-receipt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/apple-receipt.png -------------------------------------------------------------------------------- /apps/web/public/examples/authors/EmersonGarrido.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/authors/EmersonGarrido.png -------------------------------------------------------------------------------- /apps/web/public/examples/authors/Rychillie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/authors/Rychillie.png -------------------------------------------------------------------------------- /apps/web/public/examples/authors/abhinandanwadwa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/authors/abhinandanwadwa.png -------------------------------------------------------------------------------- /apps/web/public/examples/authors/bruno88cabral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/authors/bruno88cabral.png -------------------------------------------------------------------------------- /apps/web/public/examples/authors/bukinoshita.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/authors/bukinoshita.png -------------------------------------------------------------------------------- /apps/web/public/examples/authors/c0dr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/authors/c0dr.png -------------------------------------------------------------------------------- /apps/web/public/examples/authors/camillegachido.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/authors/camillegachido.png -------------------------------------------------------------------------------- /apps/web/public/examples/authors/joaom00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/authors/joaom00.png -------------------------------------------------------------------------------- /apps/web/public/examples/authors/nettofarah.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/authors/nettofarah.png -------------------------------------------------------------------------------- /apps/web/public/examples/authors/relferreira.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/authors/relferreira.png -------------------------------------------------------------------------------- /apps/web/public/examples/authors/ribeiroevandro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/authors/ribeiroevandro.png -------------------------------------------------------------------------------- /apps/web/public/examples/authors/thecodeinfluencer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/authors/thecodeinfluencer.png -------------------------------------------------------------------------------- /apps/web/public/examples/authors/zenorocha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/authors/zenorocha.png -------------------------------------------------------------------------------- /apps/web/public/examples/aws-verify-email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/aws-verify-email.png -------------------------------------------------------------------------------- /apps/web/public/examples/dropbox-reset-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/dropbox-reset-password.png -------------------------------------------------------------------------------- /apps/web/public/examples/github-access-token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/github-access-token.png -------------------------------------------------------------------------------- /apps/web/public/examples/google-play-policy-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/google-play-policy-update.png -------------------------------------------------------------------------------- /apps/web/public/examples/koala-welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/koala-welcome.png -------------------------------------------------------------------------------- /apps/web/public/examples/linear-login-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/linear-login-code.png -------------------------------------------------------------------------------- /apps/web/public/examples/nike-receipt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/nike-receipt.png -------------------------------------------------------------------------------- /apps/web/public/examples/notion-magic-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/notion-magic-link.png -------------------------------------------------------------------------------- /apps/web/public/examples/plaid-verify-identity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/plaid-verify-identity.png -------------------------------------------------------------------------------- /apps/web/public/examples/raycast-magic-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/raycast-magic-link.png -------------------------------------------------------------------------------- /apps/web/public/examples/slack-confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/slack-confirm.png -------------------------------------------------------------------------------- /apps/web/public/examples/stack-overflow-tips.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/stack-overflow-tips.png -------------------------------------------------------------------------------- /apps/web/public/examples/stripe-welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/stripe-welcome.png -------------------------------------------------------------------------------- /apps/web/public/examples/twitch-reset-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/twitch-reset-password.png -------------------------------------------------------------------------------- /apps/web/public/examples/vercel-invite-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/vercel-invite-user.png -------------------------------------------------------------------------------- /apps/web/public/examples/yelp-recent-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/examples/yelp-recent-login.png -------------------------------------------------------------------------------- /apps/web/public/fonts/commit-mono/commit-mono-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/fonts/commit-mono/commit-mono-italic.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/commit-mono/commit-mono-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/fonts/commit-mono/commit-mono-regular.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/inter/inter.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/fonts/inter/inter.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/shantell-sans/shantell-sans-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/fonts/shantell-sans/shantell-sans-italic.ttf -------------------------------------------------------------------------------- /apps/web/public/fonts/shantell-sans/shantell-sans-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/fonts/shantell-sans/shantell-sans-regular.ttf -------------------------------------------------------------------------------- /apps/web/public/meta/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/meta/apple-touch-icon.png -------------------------------------------------------------------------------- /apps/web/public/meta/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/meta/cover.png -------------------------------------------------------------------------------- /apps/web/public/meta/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/meta/favicon.ico -------------------------------------------------------------------------------- /apps/web/public/static/atmos-vacuum-canister.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/atmos-vacuum-canister.jpg -------------------------------------------------------------------------------- /apps/web/public/static/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/bg.png -------------------------------------------------------------------------------- /apps/web/public/static/braun-analogue-clock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/braun-analogue-clock.jpg -------------------------------------------------------------------------------- /apps/web/public/static/braun-classic-watch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/braun-classic-watch.jpg -------------------------------------------------------------------------------- /apps/web/public/static/braun-collection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/braun-collection.jpg -------------------------------------------------------------------------------- /apps/web/public/static/braun-vintage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/braun-vintage.jpg -------------------------------------------------------------------------------- /apps/web/public/static/braun-wall-clock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/braun-wall-clock.jpg -------------------------------------------------------------------------------- /apps/web/public/static/braun-wireless-alarm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/braun-wireless-alarm.jpg -------------------------------------------------------------------------------- /apps/web/public/static/bundle-collection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/bundle-collection.jpg -------------------------------------------------------------------------------- /apps/web/public/static/clara-french-press.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/clara-french-press.jpg -------------------------------------------------------------------------------- /apps/web/public/static/clyde-electric-kettle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/clyde-electric-kettle.jpg -------------------------------------------------------------------------------- /apps/web/public/static/coffee-bean-storage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/coffee-bean-storage.jpg -------------------------------------------------------------------------------- /apps/web/public/static/covers/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/button.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/code-block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/code-block.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/code-inline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/code-inline.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/column.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/column.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/components.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/container.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/create-email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/create-email.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/font.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/head.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/head.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/heading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/heading.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/hr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/hr.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/html.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/img.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/link.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/markdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/markdown.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/patterns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/patterns.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/preview.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/react-email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/react-email.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/render.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/row.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/row.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/section.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/section.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/tailwind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/tailwind.png -------------------------------------------------------------------------------- /apps/web/public/static/covers/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/covers/text.png -------------------------------------------------------------------------------- /apps/web/public/static/cube-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/cube-icon.png -------------------------------------------------------------------------------- /apps/web/public/static/download-on-the-app-store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/download-on-the-app-store.png -------------------------------------------------------------------------------- /apps/web/public/static/facebook-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/facebook-logo.png -------------------------------------------------------------------------------- /apps/web/public/static/get-it-on-google-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/get-it-on-google-play.png -------------------------------------------------------------------------------- /apps/web/public/static/grinder-collection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/grinder-collection.jpg -------------------------------------------------------------------------------- /apps/web/public/static/heart-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/heart-icon.png -------------------------------------------------------------------------------- /apps/web/public/static/herman-miller-chair.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/herman-miller-chair.jpg -------------------------------------------------------------------------------- /apps/web/public/static/in-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/in-icon.png -------------------------------------------------------------------------------- /apps/web/public/static/instagram-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/instagram-logo.png -------------------------------------------------------------------------------- /apps/web/public/static/logo-without-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/logo-without-background.png -------------------------------------------------------------------------------- /apps/web/public/static/megaphone-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/megaphone-icon.png -------------------------------------------------------------------------------- /apps/web/public/static/monty-art-cup-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/monty-art-cup-1.jpg -------------------------------------------------------------------------------- /apps/web/public/static/monty-art-cup-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/monty-art-cup-2.jpg -------------------------------------------------------------------------------- /apps/web/public/static/mugs-collection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/mugs-collection.jpg -------------------------------------------------------------------------------- /apps/web/public/static/ode-grinder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/ode-grinder.jpg -------------------------------------------------------------------------------- /apps/web/public/static/outdoor-living.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/outdoor-living.jpg -------------------------------------------------------------------------------- /apps/web/public/static/rocket-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/rocket-icon.png -------------------------------------------------------------------------------- /apps/web/public/static/stagg-eletric-kettle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/stagg-eletric-kettle.jpg -------------------------------------------------------------------------------- /apps/web/public/static/steve-jobs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/steve-jobs.jpg -------------------------------------------------------------------------------- /apps/web/public/static/steve-wozniak.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/steve-wozniak.jpg -------------------------------------------------------------------------------- /apps/web/public/static/vacuum-canister-clear-glass-bundle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/vacuum-canister-clear-glass-bundle.jpg -------------------------------------------------------------------------------- /apps/web/public/static/versatile-comfort.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/versatile-comfort.jpg -------------------------------------------------------------------------------- /apps/web/public/static/x-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/x-icon.png -------------------------------------------------------------------------------- /apps/web/public/static/x-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/apps/web/public/static/x-logo.png -------------------------------------------------------------------------------- /apps/web/src/app/robots.ts: -------------------------------------------------------------------------------- 1 | const Robots = () => { 2 | return { 3 | rules: [ 4 | { 5 | userAgent: '*', 6 | allow: '/', 7 | }, 8 | ], 9 | sitemap: 'https://react.email/sitemap.xml', 10 | host: 'https://react.email', 11 | }; 12 | }; 13 | 14 | export default Robots; 15 | -------------------------------------------------------------------------------- /apps/web/src/app/sitemap.ts: -------------------------------------------------------------------------------- 1 | const Sitemap = async () => { 2 | const routes = ['', '/components', '/examples'].map((route) => ({ 3 | url: `https://react.email${route}`, 4 | lastModified: new Date().toISOString().split('T')[0], 5 | })); 6 | 7 | return [...routes]; 8 | }; 9 | 10 | export default Sitemap; 11 | -------------------------------------------------------------------------------- /apps/web/src/components/anchor.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import type * as React from 'react'; 3 | 4 | export const Anchor: React.FC< 5 | Readonly<React.ComponentPropsWithoutRef<'a'>> 6 | > = ({ className, ...props }) => ( 7 | <a 8 | className={classNames( 9 | 'rounded-sm outline-none transition-transform duration-200 ease-in-out', 10 | 'hover:-translate-y-1', 11 | 'focus:ring-2 focus:ring-white/20 focus:ring-offset-4 focus:ring-offset-black', 12 | 'text-slate-12', 13 | className, 14 | )} 15 | {...props} 16 | > 17 | {props.children} 18 | </a> 19 | ); 20 | -------------------------------------------------------------------------------- /apps/web/src/components/components-view.tsx: -------------------------------------------------------------------------------- 1 | import type { ImportedComponent } from '../app/components/get-imported-components-for'; 2 | import { ComponentView } from './component-view'; 3 | 4 | interface ComponentsViewProps { 5 | components: ImportedComponent[]; 6 | } 7 | 8 | export const ComponentsView: React.FC<ComponentsViewProps> = ({ 9 | components, 10 | }) => { 11 | return ( 12 | <> 13 | {components.map((component, index) => ( 14 | <ComponentView 15 | className={index !== 0 ? 'border-slate-4 border-t pt-4' : ''} 16 | component={component} 17 | key={component.slug} 18 | /> 19 | ))} 20 | </> 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /apps/web/src/components/icon-button.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import type * as React from 'react'; 3 | 4 | type IconButtonProps = React.ComponentPropsWithoutRef<'button'>; 5 | 6 | export const IconButton: React.FC<Readonly<IconButtonProps>> = ({ 7 | children, 8 | className, 9 | ...props 10 | }) => ( 11 | <button 12 | {...props} 13 | className={classNames( 14 | 'rounded p-1 text-[#EEF7FE] transition duration-200 ease-in-out hover:text-white focus:text-white focus:outline-none focus:ring-2 focus:ring-slate-6', 15 | className, 16 | )} 17 | type="button" 18 | > 19 | {children} 20 | </button> 21 | ); 22 | -------------------------------------------------------------------------------- /apps/web/src/components/icons/icon-base.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export type IconElement = SVGSVGElement; 4 | export interface IconProps extends React.ComponentPropsWithoutRef<'svg'> { 5 | size?: number; 6 | } 7 | 8 | export const IconBase = React.forwardRef<IconElement, Readonly<IconProps>>( 9 | ({ size = 20, ...props }, forwardedRef) => ( 10 | <svg 11 | fill="none" 12 | height={size} 13 | ref={forwardedRef} 14 | viewBox="0 0 24 24" 15 | width={size} 16 | xmlns="http://www.w3.org/2000/svg" 17 | {...props} 18 | /> 19 | ), 20 | ); 21 | 22 | IconBase.displayName = 'IconBase'; 23 | -------------------------------------------------------------------------------- /apps/web/src/components/icons/icon-source.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import type { IconElement, IconProps } from './icon-base'; 3 | import { IconBase } from './icon-base'; 4 | 5 | export const IconSource = React.forwardRef<IconElement, Readonly<IconProps>>( 6 | ({ ...props }, forwardedRef) => ( 7 | <IconBase ref={forwardedRef} {...props}> 8 | <path 9 | d="M17.4 15L21 11.5L17.4 8M6.6 8L3 11.5L6.6 15M14.25 4.5L9.75 18.5" 10 | stroke="currentColor" 11 | strokeLinecap="round" 12 | strokeLinejoin="round" 13 | strokeWidth="1.5" 14 | /> 15 | </IconBase> 16 | ), 17 | ); 18 | 19 | IconSource.displayName = 'IconSource'; 20 | -------------------------------------------------------------------------------- /apps/web/src/components/tooltip.tsx: -------------------------------------------------------------------------------- 1 | import * as TooltipPrimitive from '@radix-ui/react-tooltip'; 2 | import type * as React from 'react'; 3 | import { TooltipContent } from './tooltip-content'; 4 | 5 | type RootProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Root>; 6 | 7 | export type TooltipProps = RootProps; 8 | 9 | export const TooltipRoot: React.FC<Readonly<TooltipProps>> = ({ 10 | children, 11 | ...props 12 | }) => <TooltipPrimitive.Root {...props}>{children}</TooltipPrimitive.Root>; 13 | 14 | export const Tooltip = Object.assign(TooltipRoot, { 15 | Arrow: TooltipPrimitive.TooltipArrow, 16 | Provider: TooltipPrimitive.TooltipProvider, 17 | Content: TooltipContent, 18 | Trigger: TooltipPrimitive.TooltipTrigger, 19 | }); 20 | -------------------------------------------------------------------------------- /apps/web/src/components/topbar.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import Link from 'next/link'; 3 | import type * as React from 'react'; 4 | import { Logo } from './logo'; 5 | import { Menu } from './menu'; 6 | 7 | export const Topbar: React.FC< 8 | Readonly<React.ComponentPropsWithoutRef<'header'>> 9 | > = ({ className, ...props }) => ( 10 | <header 11 | className={classNames( 12 | 'z-[3] flex items-center justify-between px-6 py-8', 13 | className, 14 | )} 15 | {...props} 16 | > 17 | <Link 18 | className="-ml-[.375rem] flex scroll-m-2 rounded-md pr-[.375rem] transition-colors focus:outline-none focus:ring focus:ring-slate-4" 19 | href="/" 20 | > 21 | <Logo /> 22 | </Link> 23 | <Menu /> 24 | </header> 25 | ); 26 | -------------------------------------------------------------------------------- /apps/web/src/hooks/use-stored-state.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export const useStoredState = <T extends string | undefined>( 4 | key: string, 5 | defaultValue: T, 6 | ): [state: T, setState: (newValue: T) => void] => { 7 | const [state, setState] = React.useState<T>(defaultValue); 8 | React.useEffect(() => { 9 | const storedValue = localStorage.getItem(key); 10 | if (storedValue) { 11 | setState(storedValue as T); 12 | } 13 | }, [key]); 14 | 15 | return [ 16 | state, 17 | (newValue) => { 18 | if (newValue) { 19 | localStorage.setItem(key, newValue); 20 | } 21 | setState(newValue); 22 | }, 23 | ]; 24 | }; 25 | -------------------------------------------------------------------------------- /apps/web/src/illustrations/articles.tsx: -------------------------------------------------------------------------------- 1 | const IllustrationArticles: React.FC = () => ( 2 | <div className="relative flex aspect-square w-[45%] translate-y-3 flex-col items-center gap-1.5 rounded-md bg-[#0F0F10] bg-gradient-to-b from-transparent via-black/20 to-black/10 px-3 pt-2 pb-5 shadow-sm transition-transform duration-150 ease-[cubic-bezier(.42,0,.58,1.8)] group-hover:skew-x-2"> 3 | <div className="aspect-video w-full rounded-sm bg-slate-4" /> 4 | <div className="h-3 w-[66%] rounded-sm bg-slate-5" /> 5 | <div className="h-3 w-[66%] rounded-sm bg-slate-5" /> 6 | <div className="mt-1 h-3 w-[24%] rounded-sm border border-[#2EBDC9] bg-[#25AEBA] shadow-[0px_0px_15px_5px_rgba(37,174,186,0.30)]" /> 7 | </div> 8 | ); 9 | 10 | export default IllustrationArticles; 11 | -------------------------------------------------------------------------------- /apps/web/src/illustrations/buttons.tsx: -------------------------------------------------------------------------------- 1 | import { MousePointer2Icon } from 'lucide-react'; 2 | 3 | const IllustrationButtons: React.FC = () => ( 4 | <div className="relative flex h-6 w-[24%] items-center justify-center rounded-md border border-[#2EBDC9] bg-[#25AEBA] p-1 shadow-sm transition-transform duration-150 ease-[cubic-bezier(.42,0,.58,1.8)] group-hover:rotate-3"> 5 | <div className="h-2 w-[80%] rounded-sm bg-black/30" /> 6 | <MousePointer2Icon 7 | className="-bottom-4 group-hover:-rotate-12 absolute left-[80%] transition-transform duration-150 ease-[cubic-bezier(.42,0,.58,1.8)]" 8 | fill="currentColor" 9 | stroke="currentColor" 10 | /> 11 | </div> 12 | ); 13 | 14 | export default IllustrationButtons; 15 | -------------------------------------------------------------------------------- /apps/web/src/illustrations/code-inline.tsx: -------------------------------------------------------------------------------- 1 | import { ChevronRightIcon } from 'lucide-react'; 2 | 3 | const IllustrationCodeInline: React.FC = () => ( 4 | <div className="relative flex w-[40%] items-center gap-2 rounded-full bg-[#0F0F10] bg-gradient-to-b from-transparent via-black/20 to-black/20 p-3 shadow-sm transition-transform duration-150 ease-[cubic-bezier(.42,0,.58,1.8)] group-hover:skew-x-2"> 5 | <ChevronRightIcon size={14} strokeWidth={4} /> 6 | <div className="h-1 w-[24%] rounded-sm border border-[#2EBDC9] bg-[#25AEBA] shadow-[0px_0px_9px_4px_rgba(37,174,186,0.10)] transition-colors ease-in-out" /> 7 | <div className="h-1 shrink grow basis-0 rounded-sm bg-slate-5" /> 8 | </div> 9 | ); 10 | 11 | export default IllustrationCodeInline; 12 | -------------------------------------------------------------------------------- /apps/web/src/illustrations/divider.tsx: -------------------------------------------------------------------------------- 1 | const IllustrationDivider: React.FC = () => ( 2 | <div className="relative flex w-[40%] flex-col items-center gap-4 rounded-sm bg-[#0F0F10] bg-gradient-to-b from-transparent via-black/20 to-black/20 p-3 shadow-sm transition-transform duration-150 ease-[cubic-bezier(.42,0,.58,1.8)] group-hover:skew-x-3"> 3 | <div className="h-3 w-[66%] rounded-sm bg-slate-5" /> 4 | <div className="mr-1 flex h-0.5 w-full items-center justify-center gap-1 rounded-sm border border-[#2EBDC9] bg-[#25AEBA] shadow-[0px_0px_9px_4px_rgba(37,174,186,0.10)] transition-all group-hover:scale-x-105" /> 5 | <div className="h-3 w-[66%] rounded-sm bg-slate-5" /> 6 | </div> 7 | ); 8 | 9 | export default IllustrationDivider; 10 | -------------------------------------------------------------------------------- /apps/web/src/illustrations/heading.tsx: -------------------------------------------------------------------------------- 1 | const IllustrationHeading: React.FC = () => ( 2 | <div className="relative flex w-[40%] flex-col items-center gap-2 rounded-md bg-[#0F0F10] bg-gradient-to-b from-transparent via-black/20 to-black/20 px-2 pt-2 pb-4 shadow-sm"> 3 | <div className="h-2 w-[84%] rounded-sm bg-slate-6 transition-transform group-hover:scale-x-105" /> 4 | <div className="h-2 w-[72%] rounded-sm bg-slate-5 transition-transform group-hover:scale-x-105" /> 5 | <div className="h-2 w-[60%] rounded-sm bg-slate-4 transition-transform group-hover:scale-x-105" /> 6 | <div className="h-2 w-[42%] rounded-sm bg-slate-3 transition-transform group-hover:scale-x-105" /> 7 | </div> 8 | ); 9 | 10 | export default IllustrationHeading; 11 | -------------------------------------------------------------------------------- /apps/web/src/illustrations/image.tsx: -------------------------------------------------------------------------------- 1 | import { ImageIcon } from 'lucide-react'; 2 | 3 | const IllustrationImage: React.FC = () => ( 4 | <div className="flex aspect-square w-[30%] items-center justify-center rounded-md bg-slate-4 transition-colors group-hover:bg-slate-5"> 5 | <ImageIcon className="opacity-5 transition-opacity group-hover:opacity-20" /> 6 | </div> 7 | ); 8 | 9 | export default IllustrationImage; 10 | -------------------------------------------------------------------------------- /apps/web/src/illustrations/section.tsx: -------------------------------------------------------------------------------- 1 | const IllustrationSection: React.FC = () => ( 2 | <div className="relative flex aspect-video w-[40%] flex-col items-center gap-2 rounded-md border-[.1875rem] border-slate-4 border-dashed bg-[#0F0F10] bg-gradient-to-b from-transparent via-black/20 to-black/20 px-2 pt-2 pb-4 shadow-sm transition-transform duration-150 ease-[cubic-bezier(.42,0,.58,1.8)] group-hover:skew-x-2" /> 3 | ); 4 | 5 | export default IllustrationSection; 6 | -------------------------------------------------------------------------------- /apps/web/src/illustrations/text.tsx: -------------------------------------------------------------------------------- 1 | const IllustrationText: React.FC = () => ( 2 | <div className="group-hover:-skew-x-6 relative flex w-[40%] translate-y-3 flex-col gap-2 rounded-md bg-[#0F0F10] bg-gradient-to-b from-transparent via-black/20 to-black/20 p-2 shadow-sm transition-transform duration-150 ease-[cubic-bezier(.42,0,.58,1.8)]"> 3 | <div className="h-1 w-[84%] rounded-sm bg-slate-5" /> 4 | <div className="flex w-full gap-1"> 5 | <div className="h-1 shrink grow basis-0 rounded-sm bg-slate-5" /> 6 | <div className="h-1 shrink grow basis-0 rounded-sm bg-slate-8" /> 7 | </div> 8 | </div> 9 | ); 10 | 11 | export default IllustrationText; 12 | -------------------------------------------------------------------------------- /apps/web/src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @media (prefers-reduced-motion: no-preference) { 6 | * { 7 | scroll-behavior: smooth; 8 | } 9 | } 10 | 11 | #root, 12 | #__next { 13 | isolation: isolate; 14 | } 15 | -------------------------------------------------------------------------------- /apps/web/src/utils/as.ts: -------------------------------------------------------------------------------- 1 | export type As< 2 | DefaultTag extends React.ElementType, 3 | T1 extends React.ElementType, 4 | T2 extends React.ElementType = T1, 5 | T3 extends React.ElementType = T1, 6 | T4 extends React.ElementType = T1, 7 | T5 extends React.ElementType = T1, 8 | > = 9 | | (React.ComponentPropsWithRef<DefaultTag> & { 10 | as?: DefaultTag; 11 | }) 12 | | (React.ComponentPropsWithRef<T1> & { 13 | as: T1; 14 | }) 15 | | (React.ComponentPropsWithRef<T2> & { 16 | as: T2; 17 | }) 18 | | (React.ComponentPropsWithRef<T3> & { 19 | as: T3; 20 | }) 21 | | (React.ComponentPropsWithRef<T4> & { 22 | as: T4; 23 | }) 24 | | (React.ComponentPropsWithRef<T5> & { 25 | as: T5; 26 | }); 27 | -------------------------------------------------------------------------------- /apps/web/src/utils/slugify.ts: -------------------------------------------------------------------------------- 1 | export const slugify = (text: string): string => 2 | text 3 | .toLowerCase() 4 | .trim() 5 | .replace(/\s+/g, '-') 6 | .replace(/[^\w-]+/g, '') 7 | .replace(/--+/g, '-'); 8 | -------------------------------------------------------------------------------- /apps/web/src/utils/unreachable.ts: -------------------------------------------------------------------------------- 1 | export const unreachable = ( 2 | condition: never, 3 | message = `Entered unreachable code. Received '${ 4 | typeof condition === 'string' ? condition : JSON.stringify(condition) 5 | }'.`, 6 | ): never => { 7 | throw new TypeError(message); 8 | }; 9 | -------------------------------------------------------------------------------- /apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/nextjs.json", 3 | "compilerOptions": { 4 | "target": "ES2018", 5 | "plugins": [{ "name": "next" }], 6 | "paths": { "@/*": ["./src/*"] }, 7 | "types": ["vitest/globals"] 8 | }, 9 | "include": [ 10 | "next-env.d.ts", 11 | "**/*.ts", 12 | "**/*.tsx", 13 | ".next/types/**/*.ts", 14 | "next.config.js" 15 | ], 16 | "exclude": ["node_modules"] 17 | } 18 | -------------------------------------------------------------------------------- /apps/web/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { loadEnvConfig } from '@next/env'; 3 | import { defineConfig } from 'vitest/config'; 4 | 5 | loadEnvConfig(__dirname, true); 6 | 7 | export default defineConfig({ 8 | test: { 9 | globals: true, 10 | environment: 'jsdom', 11 | }, 12 | esbuild: { 13 | tsconfigRaw: { 14 | compilerOptions: { 15 | jsx: 'react-jsx', 16 | }, 17 | }, 18 | }, 19 | resolve: { 20 | alias: { 21 | '@': path.resolve(__dirname, './src'), 22 | }, 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /benchmarks/tailwind-component/.gitignore: -------------------------------------------------------------------------------- 1 | isolate-*.log 2 | flamegraph.html 3 | .preview 4 | .vscpreview 5 | -------------------------------------------------------------------------------- /benchmarks/tailwind-component/src/tailwind-render.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@react-email/render'; 2 | import { Tailwind as CurrentTailwind } from '@react-email/tailwind'; 3 | import EmailWithTailwind from './emails/with-tailwind.js'; 4 | 5 | await render(<EmailWithTailwind Tailwind={CurrentTailwind} />); 6 | -------------------------------------------------------------------------------- /benchmarks/tailwind-component/tailwind.config.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/benchmarks/tailwind-component/tailwind.config.js -------------------------------------------------------------------------------- /benchmarks/tailwind-component/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "tsconfig/react-library.json", 4 | "include": ["src"], 5 | "exclude": ["dist", "build", "node_modules"], 6 | "compilerOptions": { 7 | "target": "esnext", 8 | "noUncheckedIndexedAccess": true, 9 | "resolveJsonModule": true, 10 | "moduleResolution": "Node", 11 | "declarationMap": false, 12 | "declaration": false, 13 | "outDir": "dist" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/aws-ses/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-email-with-aws-ses", 3 | "license": "MIT", 4 | "private": true, 5 | "sideEffects": false, 6 | "type": "module", 7 | "main": "./dist/index.js", 8 | "files": [ 9 | "dist/**" 10 | ], 11 | "scripts": { 12 | "build": "tsup-node src/index.tsx --format esm --target node20", 13 | "dev": "tsup-node src/index.tsx --format esm --target node20 --watch", 14 | "clean": "rm -rf dist" 15 | }, 16 | "dependencies": { 17 | "@aws-sdk/client-ses": "^3", 18 | "@react-email/components": "^0.0.36", 19 | "react": "^19", 20 | "react-dom": "^19" 21 | }, 22 | "devDependencies": { 23 | "tsup": "^8.0.0", 24 | "typescript": "^4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/aws-ses/src/email.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Html } from '@react-email/components'; 2 | import type * as React from 'react'; 3 | 4 | interface EmailProps { 5 | url: string; 6 | } 7 | 8 | export const Email: React.FC<Readonly<EmailProps>> = ({ url }) => { 9 | return ( 10 | <Html lang="en"> 11 | <Button href={url}>Click me</Button> 12 | </Html> 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /examples/mailersend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-email-with-mailersend", 3 | "license": "MIT", 4 | "private": true, 5 | "sideEffects": false, 6 | "type": "module", 7 | "main": "./dist/index.js", 8 | "files": [ 9 | "dist/**" 10 | ], 11 | "scripts": { 12 | "build": "tsup-node src/index.tsx --format esm --target node20", 13 | "dev": "tsup-node src/index.tsx --format esm --target node20 --watch", 14 | "clean": "rm -rf dist" 15 | }, 16 | "dependencies": { 17 | "@react-email/components": "^0.0.36", 18 | "mailersend": "^2", 19 | "react": "^19", 20 | "react-dom": "^19" 21 | }, 22 | "devDependencies": { 23 | "tsup": "^8.0.0", 24 | "typescript": "^4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/mailersend/src/email.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Html } from '@react-email/components'; 2 | 3 | interface EmailProps { 4 | url: string; 5 | } 6 | 7 | export const Email: React.FC<Readonly<EmailProps>> = ({ url }) => { 8 | return ( 9 | <Html lang="en"> 10 | <Button href={url}>Click me</Button> 11 | </Html> 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/mailersend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@react-email/components'; 2 | import { EmailParams, MailerSend, Recipient, Sender } from 'mailersend'; 3 | import { Email } from './email'; 4 | 5 | const mailerSend = new MailerSend({ 6 | apiKey: process.env.MAILERSEND_API_KEY || '', 7 | }); 8 | 9 | const emailHtml = await render(<Email url="https://example.com" />); 10 | 11 | const sentFrom = new Sender('you@yourdomain.com', 'Your name'); 12 | const recipients = [new Recipient('your@client.com', 'Your Client')]; 13 | 14 | const emailParams = new EmailParams() 15 | .setFrom(sentFrom) 16 | .setTo(recipients) 17 | .setSubject('This is a Subject') 18 | .setHtml(emailHtml); 19 | 20 | await mailerSend.email.send(emailParams); 21 | -------------------------------------------------------------------------------- /examples/nodemailer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-email-with-nodemailer", 3 | "license": "MIT", 4 | "private": true, 5 | "sideEffects": false, 6 | "type": "module", 7 | "main": "./dist/index.js", 8 | "files": [ 9 | "dist/**" 10 | ], 11 | "scripts": { 12 | "build": "tsup-node src/index.tsx --format esm --target node20", 13 | "dev": "tsup-node src/index.tsx --format esm --target node20 --watch", 14 | "clean": "rm -rf dist" 15 | }, 16 | "dependencies": { 17 | "@react-email/components": "^0.0.36", 18 | "nodemailer": "^6", 19 | "react": "^19", 20 | "react-dom": "^19" 21 | }, 22 | "devDependencies": { 23 | "@types/nodemailer": "^6", 24 | "tsup": "^8.0.0", 25 | "typescript": "^4" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/nodemailer/src/email.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Html } from '@react-email/components'; 2 | 3 | interface EmailProps { 4 | url: string; 5 | } 6 | 7 | export const Email: React.FC<Readonly<EmailProps>> = ({ url }) => { 8 | return ( 9 | <Html lang="en"> 10 | <Button href={url}>Click me</Button> 11 | </Html> 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/nodemailer/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@react-email/components'; 2 | import nodemailer from 'nodemailer'; 3 | import { Email } from './email'; 4 | 5 | const transporter = nodemailer.createTransport({ 6 | host: 'smtp.forwardemail.net', 7 | port: 465, 8 | secure: true, 9 | auth: { 10 | user: 'my_user', 11 | pass: 'my_password', 12 | }, 13 | }); 14 | 15 | const emailHtml = await render(<Email url="https://example.com" />); 16 | 17 | const options = { 18 | from: 'you@example.com', 19 | to: 'user@gmail.com', 20 | subject: 'hello world', 21 | html: emailHtml, 22 | }; 23 | 24 | await transporter.sendMail(options); 25 | -------------------------------------------------------------------------------- /examples/plunk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-email-with-plunk", 3 | "license": "MIT", 4 | "private": true, 5 | "sideEffects": false, 6 | "type": "module", 7 | "main": "./dist/index.js", 8 | "files": [ 9 | "dist/**" 10 | ], 11 | "scripts": { 12 | "build": "tsup-node src/index.tsx --format esm --target node20", 13 | "dev": "tsup-node src/index.tsx --format esm --target node20 --watch", 14 | "clean": "rm -rf dist" 15 | }, 16 | "dependencies": { 17 | "@plunk/node": "^3", 18 | "@react-email/components": "^0.0.36", 19 | "react": "^19", 20 | "react-dom": "^19" 21 | }, 22 | "devDependencies": { 23 | "tsup": "^8.0.0", 24 | "typescript": "^4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/plunk/src/email.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Html } from '@react-email/components'; 2 | 3 | interface EmailProps { 4 | url: string; 5 | } 6 | 7 | export const Email: React.FC<Readonly<EmailProps>> = ({ url }) => { 8 | return ( 9 | <Html lang="en"> 10 | <Button href={url}>Click me</Button> 11 | </Html> 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/plunk/src/index.tsx: -------------------------------------------------------------------------------- 1 | import plunkImport from '@plunk/node'; 2 | import { render } from '@react-email/components'; 3 | import { Email } from './email'; 4 | 5 | const Plunk = ( 6 | plunkImport as unknown as { 7 | default: typeof plunkImport; 8 | } 9 | ).default; 10 | 11 | // See https://github.com/useplunk/node/issues/2 for why Plunk.default 12 | const plunk = new Plunk(process.env.PLUNK_API_KEY || ''); 13 | 14 | const emailHtml = await render(<Email url="https://example.com" />); 15 | 16 | await plunk.emails.send({ 17 | to: 'hello@useplunk.com', 18 | subject: 'Hello world', 19 | body: emailHtml, 20 | }); 21 | -------------------------------------------------------------------------------- /examples/postmark/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-email-with-postmark", 3 | "license": "MIT", 4 | "private": true, 5 | "type": "module", 6 | "sideEffects": false, 7 | "main": "./dist/index.js", 8 | "files": [ 9 | "dist/**" 10 | ], 11 | "scripts": { 12 | "build": "tsup-node src/index.tsx --format esm --target node20", 13 | "dev": "tsup-node src/index.tsx --format esm --target node20 --watch", 14 | "clean": "rm -rf dist" 15 | }, 16 | "dependencies": { 17 | "postmark": "^3", 18 | "@react-email/components": "^0.0.36", 19 | "react": "^19", 20 | "react-dom": "^19" 21 | }, 22 | "devDependencies": { 23 | "tsup": "^8.0.0", 24 | "typescript": "^4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/postmark/src/email.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Html } from '@react-email/components'; 2 | 3 | interface EmailProps { 4 | url: string; 5 | } 6 | 7 | export const Email: React.FC<Readonly<EmailProps>> = ({ url }) => { 8 | return ( 9 | <Html lang="en"> 10 | <Button href={url}>Click me</Button> 11 | </Html> 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/postmark/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@react-email/components'; 2 | import postmark from 'postmark'; 3 | import { Email } from './email'; 4 | 5 | const client = new postmark.ServerClient(process.env.POSTMARK_API_KEY || ''); 6 | 7 | const emailHtml = await render(<Email url="https://example.com" />); 8 | 9 | const options = { 10 | From: 'you@example.com', 11 | To: 'user@gmail.com', 12 | Subject: 'hello world', 13 | HtmlBody: emailHtml, 14 | }; 15 | 16 | await client.sendEmail(options); 17 | -------------------------------------------------------------------------------- /examples/resend/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="next" /> 2 | /// <reference types="next/image-types/global" /> 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. 6 | -------------------------------------------------------------------------------- /examples/resend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-email-with-resend", 3 | "license": "MIT", 4 | "private": true, 5 | "sideEffects": false, 6 | "scripts": { 7 | "build": "next build", 8 | "dev": "next dev", 9 | "start": "next start" 10 | }, 11 | "dependencies": { 12 | "next": "^15", 13 | "@react-email/components": "^0.0.36", 14 | "react": "^19", 15 | "react-dom": "^19", 16 | "resend": "^4" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^22.0.0", 20 | "@types/react": "^19", 21 | "@types/react-dom": "^19", 22 | "typescript": "^5" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/resend/src/lib/resend.ts: -------------------------------------------------------------------------------- 1 | import { Resend } from 'resend'; 2 | 3 | export const resend = new Resend(process.env.RESEND_API_KEY); 4 | -------------------------------------------------------------------------------- /examples/resend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": false, 7 | "forceConsistentCasingInFileNames": true, 8 | "strictNullChecks": true, 9 | "noEmit": true, 10 | "incremental": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "target": "ES2017" 18 | }, 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /examples/scaleway/next/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="next" /> 2 | /// <reference types="next/image-types/global" /> 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. 6 | -------------------------------------------------------------------------------- /examples/scaleway/next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-email-with-next-scaleway", 3 | "license": "MIT", 4 | "private": true, 5 | "sideEffects": false, 6 | "scripts": { 7 | "build": "next build", 8 | "dev": "next dev", 9 | "start": "next start" 10 | }, 11 | "dependencies": { 12 | "@scaleway/sdk": "^1", 13 | "next": "^15", 14 | "@react-email/components": "^0.0.36", 15 | "react": "^19", 16 | "react-dom": "^19" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^22.0.0", 20 | "@types/react": "^19", 21 | "@types/react-dom": "^19", 22 | "typescript": "^4" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/scaleway/next/src/lib/scaleway.ts: -------------------------------------------------------------------------------- 1 | import { createClient, TransactionalEmail } from '@scaleway/sdk'; 2 | 3 | const client = createClient({ 4 | accessKey: process.env.ACCESS_KEY, 5 | secretKey: process.env.SECRET_KEY, 6 | defaultProjectId: process.env.PROJECT_ID, 7 | defaultRegion: 'fr-par', 8 | defaultZone: 'fr-par-1', 9 | }); 10 | 11 | export const scalewayTEM = new TransactionalEmail.v1alpha1.API(client); 12 | -------------------------------------------------------------------------------- /examples/scaleway/next/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": false, 7 | "forceConsistentCasingInFileNames": true, 8 | "noEmit": true, 9 | "incremental": true, 10 | "strictNullChecks": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "target": "ES2017" 18 | }, 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /examples/scaleway/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-email-with-node-scaleway", 3 | "license": "MIT", 4 | "private": true, 5 | "sideEffects": false, 6 | "main": "./dist/index.js", 7 | "type": "module", 8 | "files": [ 9 | "dist/**" 10 | ], 11 | "scripts": { 12 | "build": "tsup-node src/index.tsx --format esm --target node20", 13 | "dev": "tsup-node src/index.tsx --format esm --target node20 --watch" 14 | }, 15 | "dependencies": { 16 | "@scaleway/sdk": "^1", 17 | "@react-email/components": "^0.0.36", 18 | "react": "^19", 19 | "react-dom": "^19" 20 | }, 21 | "devDependencies": { 22 | "tsup": "^8.0.0", 23 | "typescript": "^4" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/sendgrid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-email-with-sendgrid", 3 | "license": "MIT", 4 | "private": true, 5 | "sideEffects": false, 6 | "type": "module", 7 | "main": "./dist/index.js", 8 | "files": [ 9 | "dist/**" 10 | ], 11 | "scripts": { 12 | "build": "tsup-node src/index.tsx --format esm --target node20", 13 | "dev": "tsup-node src/index.tsx --format esm --target node20 --watch", 14 | "clean": "rm -rf dist" 15 | }, 16 | "dependencies": { 17 | "@sendgrid/mail": "^7", 18 | "@react-email/components": "^0.0.36", 19 | "react": "^19", 20 | "react-dom": "^19" 21 | }, 22 | "devDependencies": { 23 | "tsup": "^8.0.0", 24 | "typescript": "^4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/sendgrid/src/email.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Html } from '@react-email/components'; 2 | 3 | interface EmailProps { 4 | url: string; 5 | } 6 | 7 | export const Email: React.FC<Readonly<EmailProps>> = ({ url }) => { 8 | return ( 9 | <Html lang="en"> 10 | <Button href={url}>Click me</Button> 11 | </Html> 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/sendgrid/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@react-email/components'; 2 | import sendgrid from '@sendgrid/mail'; 3 | import { Email } from './email'; 4 | 5 | sendgrid.setApiKey(process.env.SENDGRID_API_KEY || ''); 6 | 7 | const emailHtml = await render(<Email url="https://example.com" />); 8 | 9 | const options = { 10 | from: 'you@example.com', 11 | to: 'user@gmail.com', 12 | subject: 'hello world', 13 | html: emailHtml, 14 | }; 15 | 16 | await sendgrid.send(options); 17 | -------------------------------------------------------------------------------- /packages/body/src/__snapshots__/body.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`<Body> component > renders correctly 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><body>Lorem ipsum<!--/$--></body>"`; 4 | -------------------------------------------------------------------------------- /packages/body/src/body.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export type BodyProps = Readonly<React.HtmlHTMLAttributes<HTMLBodyElement>>; 4 | 5 | export const Body = React.forwardRef<HTMLBodyElement, BodyProps>( 6 | ({ children, style, ...props }, ref) => { 7 | return ( 8 | <body {...props} ref={ref} style={style}> 9 | {children} 10 | </body> 11 | ); 12 | }, 13 | ); 14 | 15 | Body.displayName = 'Body'; 16 | -------------------------------------------------------------------------------- /packages/body/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './body'; 2 | -------------------------------------------------------------------------------- /packages/body/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/button/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './button'; 2 | -------------------------------------------------------------------------------- /packages/button/src/utils/px-to-pt.ts: -------------------------------------------------------------------------------- 1 | export const pxToPt = (px: number | undefined): number | undefined => 2 | typeof px === 'number' && !Number.isNaN(Number(px)) 3 | ? (px * 3) / 4 4 | : undefined; 5 | -------------------------------------------------------------------------------- /packages/button/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/code-block/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './code-block'; 2 | export * from './languages-available'; 3 | export * from './themes'; 4 | -------------------------------------------------------------------------------- /packages/code-block/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/code-inline/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './code-inline'; 2 | -------------------------------------------------------------------------------- /packages/code-inline/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/column/src/__snapshots__/column.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`<Column> component > renders correctly 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><td data-id="__react-email-column">Lorem ipsum</td><!--/$-->"`; 4 | -------------------------------------------------------------------------------- /packages/column/src/column.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export type ColumnProps = Readonly<React.ComponentPropsWithoutRef<'td'>>; 4 | 5 | export const Column = React.forwardRef<HTMLTableCellElement, ColumnProps>( 6 | ({ children, style, ...props }, ref) => { 7 | return ( 8 | <td {...props} data-id="__react-email-column" ref={ref} style={style}> 9 | {children} 10 | </td> 11 | ); 12 | }, 13 | ); 14 | 15 | Column.displayName = 'Column'; 16 | -------------------------------------------------------------------------------- /packages/column/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './column'; 2 | -------------------------------------------------------------------------------- /packages/column/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/components/src/__snapshots__/heading.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`render renders the <Heading> component 1`] = `"<!DOCTYPE html PUBLIC \\"-//W3C//DTD XHTML 1.0 Transitional//EN\\" \\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\\"><h2 style=\\"margin-left:4px;margin-right:4px\\">Lorem ipsum</h2>"`; 4 | -------------------------------------------------------------------------------- /packages/components/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/container/src/__snapshots__/container.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`<Container> component > renders correctly 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="max-width:300px"><tbody><tr style="width:100%"><td><button type="button">Hi</button></td></tr></tbody></table><!--/$-->"`; 4 | -------------------------------------------------------------------------------- /packages/container/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './container'; 2 | -------------------------------------------------------------------------------- /packages/container/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/create-email/.gitignore: -------------------------------------------------------------------------------- 1 | .test 2 | -------------------------------------------------------------------------------- /packages/create-email/.npmignore: -------------------------------------------------------------------------------- 1 | .test 2 | template/node_modules 3 | template/CHANGELOG.md 4 | template/.turbo 5 | template/.react-email 6 | -------------------------------------------------------------------------------- /packages/create-email/template/.gitignore: -------------------------------------------------------------------------------- 1 | .react-email -------------------------------------------------------------------------------- /packages/create-email/template/emails/static/notion-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/create-email/template/emails/static/notion-logo.png -------------------------------------------------------------------------------- /packages/create-email/template/emails/static/plaid-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/create-email/template/emails/static/plaid-logo.png -------------------------------------------------------------------------------- /packages/create-email/template/emails/static/plaid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/create-email/template/emails/static/plaid.png -------------------------------------------------------------------------------- /packages/create-email/template/emails/static/stripe-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/create-email/template/emails/static/stripe-logo.png -------------------------------------------------------------------------------- /packages/create-email/template/emails/static/vercel-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/create-email/template/emails/static/vercel-arrow.png -------------------------------------------------------------------------------- /packages/create-email/template/emails/static/vercel-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/create-email/template/emails/static/vercel-logo.png -------------------------------------------------------------------------------- /packages/create-email/template/emails/static/vercel-team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/create-email/template/emails/static/vercel-team.png -------------------------------------------------------------------------------- /packages/create-email/template/emails/static/vercel-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/create-email/template/emails/static/vercel-user.png -------------------------------------------------------------------------------- /packages/create-email/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-email-starter", 3 | "version": "1.1.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "email build", 7 | "dev": "email dev", 8 | "export": "email export" 9 | }, 10 | "dependencies": { 11 | "@react-email/components": "INSERT_COMPONENTS_VERSION", 12 | "react-dom": "^19.0.0", 13 | "react": "^19.0.0" 14 | }, 15 | "devDependencies": { 16 | "@react-email/preview-server": "INSERT_REACT_EMAIL_VERSION", 17 | "@types/react": "^19.0.1", 18 | "@types/react-dom": "^19.0.1", 19 | "react-email": "INSERT_REACT_EMAIL_VERSION" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/create-email/template/readme.md: -------------------------------------------------------------------------------- 1 | # React Email Starter 2 | 3 | A live preview right in your browser so you don't need to keep sending real emails during development. 4 | 5 | ## Getting Started 6 | 7 | First, install the dependencies: 8 | 9 | ```sh 10 | npm install 11 | # or 12 | yarn 13 | ``` 14 | 15 | Then, run the development server: 16 | 17 | ```sh 18 | npm run dev 19 | # or 20 | yarn dev 21 | ``` 22 | 23 | Open [localhost:3000](http://localhost:3000) with your browser to see the result. 24 | 25 | ## License 26 | 27 | MIT License 28 | -------------------------------------------------------------------------------- /packages/create-email/template/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": false, 4 | "jsx": "react-jsx", 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "strictNullChecks": true 9 | }, 10 | "include": ["**/*.ts", "**/*.tsx"], 11 | "exclude": ["node_modules", ".react-email"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/create-email/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": ["**/*.ts", "**/*.tsx"], 4 | "exclude": ["dist", "build", "node_modules", ".test"], 5 | "compilerOptions": { 6 | "noEmit": true, 7 | "types": ["vitest/globals"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/create-email/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | environment: 'happy-dom', 7 | exclude: ['.test/**/*', 'template/**/*', '**/node_modules'], 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /packages/font/src/__snapshots__/font.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`<Font> component > renders correctly 1`] = ` 4 | "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><style> 5 | @font-face { 6 | font-family: 'Roboto'; 7 | font-style: normal; 8 | font-weight: 400; 9 | mso-font-alt: 'Verdana'; 10 | 11 | } 12 | 13 | * { 14 | font-family: 'Roboto', Verdana; 15 | } 16 | </style><!--/$-->" 17 | `; 18 | -------------------------------------------------------------------------------- /packages/font/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './font'; 2 | -------------------------------------------------------------------------------- /packages/font/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/head/src/head.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export type HeadProps = Readonly<React.ComponentPropsWithoutRef<'head'>>; 4 | 5 | export const Head = React.forwardRef<HTMLHeadElement, HeadProps>( 6 | ({ children, ...props }, ref) => ( 7 | <head {...props} ref={ref}> 8 | <meta content="text/html; charset=UTF-8" httpEquiv="Content-Type" /> 9 | <meta name="x-apple-disable-message-reformatting" /> 10 | {children} 11 | </head> 12 | ), 13 | ); 14 | 15 | Head.displayName = 'Head'; 16 | -------------------------------------------------------------------------------- /packages/head/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './head'; 2 | -------------------------------------------------------------------------------- /packages/head/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/heading/src/__snapshots__/heading.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`render > renders the <Heading> component 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><h2 style="margin-left:4px;margin-right:4px">Lorem ipsum</h2><!--/$-->"`; 4 | -------------------------------------------------------------------------------- /packages/heading/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './heading'; 2 | -------------------------------------------------------------------------------- /packages/heading/src/utils/as.ts: -------------------------------------------------------------------------------- 1 | export type As< 2 | DefaultTag extends React.ElementType, 3 | T1 extends React.ElementType, 4 | T2 extends React.ElementType = T1, 5 | T3 extends React.ElementType = T1, 6 | T4 extends React.ElementType = T1, 7 | T5 extends React.ElementType = T1, 8 | > = 9 | | (React.ComponentPropsWithRef<DefaultTag> & { 10 | as?: DefaultTag; 11 | }) 12 | | (React.ComponentPropsWithRef<T1> & { 13 | as: T1; 14 | }) 15 | | (React.ComponentPropsWithRef<T2> & { 16 | as: T2; 17 | }) 18 | | (React.ComponentPropsWithRef<T3> & { 19 | as: T3; 20 | }) 21 | | (React.ComponentPropsWithRef<T4> & { 22 | as: T4; 23 | }) 24 | | (React.ComponentPropsWithRef<T5> & { 25 | as: T5; 26 | }); 27 | -------------------------------------------------------------------------------- /packages/heading/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/hr/src/__snapshots__/hr.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`<Hr> component > renders correctly 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><hr style="width:100%;border:none;border-top:1px solid #eaeaea"/><!--/$-->"`; 4 | -------------------------------------------------------------------------------- /packages/hr/src/hr.spec.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@react-email/render'; 2 | import { Hr } from './index'; 3 | 4 | describe('<Hr> component', () => { 5 | it('passes styles and other props correctly', async () => { 6 | const style = { 7 | width: '50%', 8 | borderColor: 'black', 9 | }; 10 | const html = await render(<Hr data-testid="hr-test" style={style} />); 11 | expect(html).toContain('width:50%'); 12 | expect(html).toContain('border-color:black'); 13 | expect(html).toContain('data-testid="hr-test"'); 14 | }); 15 | 16 | it('renders correctly', async () => { 17 | const actualOutput = await render(<Hr />); 18 | expect(actualOutput).toMatchSnapshot(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/hr/src/hr.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export type HrProps = Readonly<React.ComponentPropsWithoutRef<'hr'>>; 4 | 5 | export const Hr = React.forwardRef<HTMLHRElement, HrProps>( 6 | ({ style, ...props }, ref) => ( 7 | <hr 8 | {...props} 9 | ref={ref} 10 | style={{ 11 | width: '100%', 12 | border: 'none', 13 | borderTop: '1px solid #eaeaea', 14 | ...style, 15 | }} 16 | /> 17 | ), 18 | ); 19 | 20 | Hr.displayName = 'Hr'; 21 | -------------------------------------------------------------------------------- /packages/hr/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hr'; 2 | -------------------------------------------------------------------------------- /packages/hr/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/html/src/__snapshots__/html.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`<Html> component > renders correctly 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html dir="ltr" lang="en"><head></head><!--$--><!--/$--></html>"`; 4 | -------------------------------------------------------------------------------- /packages/html/src/html.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export type HtmlProps = Readonly<React.ComponentPropsWithoutRef<'html'>>; 4 | 5 | export const Html = React.forwardRef<HTMLHtmlElement, HtmlProps>( 6 | ({ children, lang = 'en', dir = 'ltr', ...props }, ref) => ( 7 | <html {...props} dir={dir} lang={lang} ref={ref}> 8 | {children} 9 | </html> 10 | ), 11 | ); 12 | 13 | Html.displayName = 'Html'; 14 | -------------------------------------------------------------------------------- /packages/html/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './html'; 2 | -------------------------------------------------------------------------------- /packages/html/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/img/src/__snapshots__/img.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`<Img> component > renders correctly 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><link rel="preload" as="image" href="cat.jpg"/><!--$--><img alt="Cat" height="300" src="cat.jpg" style="display:block;outline:none;border:none;text-decoration:none" width="300"/><!--/$-->"`; 4 | -------------------------------------------------------------------------------- /packages/img/src/img.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export type ImgProps = Readonly<React.ComponentPropsWithoutRef<'img'>>; 4 | 5 | export const Img = React.forwardRef<HTMLImageElement, ImgProps>( 6 | ({ alt, src, width, height, style, ...props }, ref) => ( 7 | <img 8 | {...props} 9 | alt={alt} 10 | height={height} 11 | ref={ref} 12 | src={src} 13 | style={{ 14 | display: 'block', 15 | outline: 'none', 16 | border: 'none', 17 | textDecoration: 'none', 18 | ...style, 19 | }} 20 | width={width} 21 | /> 22 | ), 23 | ); 24 | 25 | Img.displayName = 'Img'; 26 | -------------------------------------------------------------------------------- /packages/img/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './img'; 2 | -------------------------------------------------------------------------------- /packages/img/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/link/src/__snapshots__/link.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`<Link> component > renders correctly 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><a href="https://example.com" style="color:#067df7;text-decoration-line:none" target="_blank">Example</a><!--/$-->"`; 4 | -------------------------------------------------------------------------------- /packages/link/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './link'; 2 | -------------------------------------------------------------------------------- /packages/link/src/link.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export type LinkProps = Readonly<React.ComponentPropsWithoutRef<'a'>>; 4 | 5 | export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>( 6 | ({ target = '_blank', style, ...props }, ref) => ( 7 | <a 8 | {...props} 9 | ref={ref} 10 | style={{ 11 | color: '#067df7', 12 | textDecorationLine: 'none', 13 | ...style, 14 | }} 15 | target={target} 16 | > 17 | {props.children} 18 | </a> 19 | ), 20 | ); 21 | 22 | Link.displayName = 'Link'; 23 | -------------------------------------------------------------------------------- /packages/link/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/markdown/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './markdown'; 2 | -------------------------------------------------------------------------------- /packages/markdown/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/preview-server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | 4 | # for testing 5 | static 6 | emails 7 | .env* 8 | -------------------------------------------------------------------------------- /packages/preview-server/.npmignore: -------------------------------------------------------------------------------- 1 | .react-email 2 | ./emails 3 | ./emails/static 4 | node_modules 5 | .turbo 6 | -------------------------------------------------------------------------------- /packages/preview-server/index.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * this file is required so that import.meta.resolve and require.resolve can properly can find the module for this package 3 | */ 4 | import fs from 'node:fs/promises'; 5 | import path from 'node:path'; 6 | import url from 'node:url'; 7 | 8 | const filename = url.fileURLToPath(import.meta.url); 9 | const dirname = path.dirname(filename); 10 | const packageJson = JSON.parse( 11 | await fs.readFile(path.join(dirname, 'package.json'), 'utf-8'), 12 | ); 13 | 14 | /** 15 | * @type {string} 16 | */ 17 | export const version = packageJson.version; 18 | -------------------------------------------------------------------------------- /packages/preview-server/module-punycode.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'module-punycode' { 2 | export * from 'node:punycode'; 3 | } 4 | -------------------------------------------------------------------------------- /packages/preview-server/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="next" /> 2 | /// <reference types="next/image-types/global" /> 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /packages/preview-server/postcss.config.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path'); 2 | 3 | module.exports = { 4 | plugins: { 5 | tailwindcss: { config: path.resolve(__dirname, 'tailwind.config.ts') }, 6 | autoprefixer: {}, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /packages/preview-server/src/actions/email-validation/get-code-location-from-ast-element.ts: -------------------------------------------------------------------------------- 1 | import type { HTMLElement } from 'node-html-parser'; 2 | import { getLineAndColumnFromOffset } from '../../utils/get-line-and-column-from-offset'; 3 | 4 | export interface CodeLocation { 5 | line: number; 6 | column: number; 7 | } 8 | 9 | export const getCodeLocationFromAstElement = ( 10 | ast: HTMLElement, 11 | html: string, 12 | ): CodeLocation => { 13 | const [line, column] = getLineAndColumnFromOffset(ast.range[0], html); 14 | return { 15 | line, 16 | column, 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/preview-server/src/actions/email-validation/quick-fetch.ts: -------------------------------------------------------------------------------- 1 | import type { IncomingMessage } from 'node:http'; 2 | import http from 'node:http'; 3 | import https from 'node:https'; 4 | 5 | export const quickFetch = (url: URL) => { 6 | return new Promise<IncomingMessage>((resolve, reject) => { 7 | const caller = url.protocol === 'https:' ? https : http; 8 | caller 9 | .get(url, (res) => { 10 | resolve(res); 11 | }) 12 | .on('error', (error) => reject(error)); 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/preview-server/src/actions/get-emails-directory-metadata-action.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | 3 | import type { EmailsDirectory } from '../utils/get-emails-directory-metadata'; 4 | import { getEmailsDirectoryMetadata } from '../utils/get-emails-directory-metadata'; 5 | 6 | export const getEmailsDirectoryMetadataAction = async ( 7 | absolutePathToEmailsDirectory: string, 8 | keepFileExtensions = false, 9 | isSubDirectory = false, 10 | 11 | baseDirectoryPath = absolutePathToEmailsDirectory, 12 | ): Promise<EmailsDirectory | undefined> => { 13 | return getEmailsDirectoryMetadata( 14 | absolutePathToEmailsDirectory, 15 | keepFileExtensions, 16 | isSubDirectory, 17 | baseDirectoryPath, 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /packages/preview-server/src/app/env.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | /** ONLY ACCESSIBLE ON THE SERVER */ 3 | export const emailsDirRelativePath = process.env.EMAILS_DIR_RELATIVE_PATH!; 4 | 5 | /** ONLY ACCESSIBLE ON THE SERVER */ 6 | export const userProjectLocation = process.env.USER_PROJECT_LOCATION!; 7 | 8 | /** ONLY ACCESSIBLE ON THE SERVER */ 9 | export const emailsDirectoryAbsolutePath = 10 | process.env.EMAILS_DIR_ABSOLUTE_PATH!; 11 | 12 | export const isBuilding = process.env.NEXT_PUBLIC_IS_BUILDING === 'true'; 13 | 14 | export const isPreviewDevelopment = 15 | process.env.NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT === 'true'; 16 | -------------------------------------------------------------------------------- /packages/preview-server/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/preview-server/src/app/favicon.ico -------------------------------------------------------------------------------- /packages/preview-server/src/app/fonts/SFMono/SFMonoBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/preview-server/src/app/fonts/SFMono/SFMonoBold.otf -------------------------------------------------------------------------------- /packages/preview-server/src/app/fonts/SFMono/SFMonoBoldItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/preview-server/src/app/fonts/SFMono/SFMonoBoldItalic.otf -------------------------------------------------------------------------------- /packages/preview-server/src/app/fonts/SFMono/SFMonoHeavy.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/preview-server/src/app/fonts/SFMono/SFMonoHeavy.otf -------------------------------------------------------------------------------- /packages/preview-server/src/app/fonts/SFMono/SFMonoHeavyItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/preview-server/src/app/fonts/SFMono/SFMonoHeavyItalic.otf -------------------------------------------------------------------------------- /packages/preview-server/src/app/fonts/SFMono/SFMonoLight.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/preview-server/src/app/fonts/SFMono/SFMonoLight.otf -------------------------------------------------------------------------------- /packages/preview-server/src/app/fonts/SFMono/SFMonoLightItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/preview-server/src/app/fonts/SFMono/SFMonoLightItalic.otf -------------------------------------------------------------------------------- /packages/preview-server/src/app/fonts/SFMono/SFMonoMedium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/preview-server/src/app/fonts/SFMono/SFMonoMedium.otf -------------------------------------------------------------------------------- /packages/preview-server/src/app/fonts/SFMono/SFMonoMediumItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/preview-server/src/app/fonts/SFMono/SFMonoMediumItalic.otf -------------------------------------------------------------------------------- /packages/preview-server/src/app/fonts/SFMono/SFMonoRegular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/preview-server/src/app/fonts/SFMono/SFMonoRegular.otf -------------------------------------------------------------------------------- /packages/preview-server/src/app/fonts/SFMono/SFMonoRegularItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/preview-server/src/app/fonts/SFMono/SFMonoRegularItalic.otf -------------------------------------------------------------------------------- /packages/preview-server/src/app/fonts/SFMono/SFMonoSemibold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/preview-server/src/app/fonts/SFMono/SFMonoSemibold.otf -------------------------------------------------------------------------------- /packages/preview-server/src/app/fonts/SFMono/SFMonoSemiboldItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/preview-server/src/app/fonts/SFMono/SFMonoSemiboldItalic.otf -------------------------------------------------------------------------------- /packages/preview-server/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html { 6 | color-scheme: dark; 7 | } 8 | 9 | .popup-open iframe { 10 | pointer-events: none; 11 | } 12 | 13 | nav > div > div > .line { 14 | display: none; 15 | } 16 | -------------------------------------------------------------------------------- /packages/preview-server/src/app/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/preview-server/src/app/logo.png -------------------------------------------------------------------------------- /packages/preview-server/src/components/code-snippet.tsx: -------------------------------------------------------------------------------- 1 | const CodeSnippet = ({ children }) => { 2 | return ( 3 | <code className="m-0.5 inline-block rounded-md bg-white/10 p-1 font-mono leading-none text-slate-12"> 4 | {children} 5 | </code> 6 | ); 7 | }; 8 | 9 | export default CodeSnippet; 10 | -------------------------------------------------------------------------------- /packages/preview-server/src/components/icons/icon-arrow-down.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import type { IconElement, IconProps } from './icon-base'; 3 | import { IconBase } from './icon-base'; 4 | 5 | export const IconArrowDown = React.forwardRef<IconElement, Readonly<IconProps>>( 6 | ({ ...props }, forwardedRef) => ( 7 | <IconBase ref={forwardedRef} {...props}> 8 | <path 9 | d="M12 16L6 9.85966L6.84 9L12 14.2808L17.16 9L18 9.85966L12 16Z" 10 | fill="currentColor" 11 | /> 12 | </IconBase> 13 | ), 14 | ); 15 | 16 | IconArrowDown.displayName = 'IconArrowDown'; 17 | -------------------------------------------------------------------------------- /packages/preview-server/src/components/icons/icon-base.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export type IconElement = React.ElementRef<'svg'>; 4 | export type RootProps = React.ComponentPropsWithoutRef<'svg'>; 5 | 6 | export interface IconProps extends RootProps { 7 | size?: number; 8 | } 9 | 10 | export const IconBase = React.forwardRef<IconElement, Readonly<IconProps>>( 11 | ({ size = 20, children, ...props }, forwardedRef) => ( 12 | <svg 13 | fill="none" 14 | height={size} 15 | ref={forwardedRef} 16 | viewBox="0 0 24 24" 17 | width={size} 18 | xmlns="http://www.w3.org/2000/svg" 19 | {...props} 20 | > 21 | {children} 22 | </svg> 23 | ), 24 | ); 25 | 26 | IconBase.displayName = 'IconBase'; 27 | -------------------------------------------------------------------------------- /packages/preview-server/src/components/icons/icon-button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { cn } from '../../utils'; 3 | 4 | export type IconButtonProps = React.ComponentPropsWithoutRef<'button'>; 5 | 6 | export const IconButton = React.forwardRef< 7 | HTMLButtonElement, 8 | Readonly<IconButtonProps> 9 | >(({ children, className, ...props }, forwardedRef) => ( 10 | <button 11 | type="button" 12 | {...props} 13 | className={cn( 14 | 'focus:ring-gray-8 rounded text-slate-11 transition duration-200 ease-in-out hover:text-slate-12 focus:text-slate-12 focus:outline-none focus:ring-2', 15 | className, 16 | )} 17 | ref={forwardedRef} 18 | > 19 | {children} 20 | </button> 21 | )); 22 | 23 | IconButton.displayName = 'IconButton'; 24 | -------------------------------------------------------------------------------- /packages/preview-server/src/components/icons/icon-check.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import type { IconElement, IconProps } from './icon-base'; 3 | import { IconBase } from './icon-base'; 4 | 5 | export const IconCheck = React.forwardRef<IconElement, Readonly<IconProps>>( 6 | ({ ...props }, forwardedRef) => ( 7 | <IconBase ref={forwardedRef} {...props}> 8 | <path 9 | d="M16.25 8.75L10.406 15.25L7.75 12.75" 10 | stroke="currentColor" 11 | strokeLinecap="round" 12 | strokeLinejoin="round" 13 | strokeWidth="1.5" 14 | /> 15 | </IconBase> 16 | ), 17 | ); 18 | 19 | IconCheck.displayName = 'IconCheck'; 20 | -------------------------------------------------------------------------------- /packages/preview-server/src/components/icons/icon-download.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import type { IconElement, IconProps } from './icon-base'; 3 | import { IconBase } from './icon-base'; 4 | 5 | export const IconDownload = React.forwardRef<IconElement, Readonly<IconProps>>( 6 | ({ ...props }, forwardedRef) => ( 7 | <IconBase ref={forwardedRef} {...props}> 8 | <path 9 | d="M4.75 14.75v1.5a3 3 0 0 0 3 3h8.5a3 3 0 0 0 3-3v-1.5M12 14.25v-9.5M8.75 10.75l3.25 3.5 3.25-3.5" 10 | stroke="currentColor" 11 | strokeLinecap="round" 12 | strokeLinejoin="round" 13 | strokeWidth={1.5} 14 | /> 15 | </IconBase> 16 | ), 17 | ); 18 | 19 | IconDownload.displayName = 'IconDownload'; 20 | -------------------------------------------------------------------------------- /packages/preview-server/src/components/icons/icon-email.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import type { IconElement, IconProps } from './icon-base'; 3 | import { IconBase } from './icon-base'; 4 | 5 | export const IconEmail = React.forwardRef<IconElement, Readonly<IconProps>>( 6 | (props, forwardedRef) => { 7 | return ( 8 | <IconBase {...props} ref={forwardedRef}> 9 | <path 10 | d="M20 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2m0 4.7l-8 5.334L4 8.7V6.297l8 5.333l8-5.333z" 11 | fill="currentColor" 12 | /> 13 | </IconBase> 14 | ); 15 | }, 16 | ); 17 | 18 | IconEmail.displayName = 'IconEmail'; 19 | -------------------------------------------------------------------------------- /packages/preview-server/src/components/icons/icon-image.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef } from 'react'; 2 | import type { IconElement, IconProps } from './icon-base'; 3 | import { IconBase } from './icon-base'; 4 | 5 | export const IconImage = forwardRef<IconElement, IconProps>((props, ref) => ( 6 | <IconBase {...props} ref={ref}> 7 | <g 8 | fill="none" 9 | stroke="currentColor" 10 | strokeLinecap="round" 11 | strokeLinejoin="round" 12 | strokeWidth="2" 13 | > 14 | <rect width="18" height="18" x="3" y="3" rx="2" ry="2" /> 15 | <circle cx="9" cy="9" r="2" /> 16 | <path d="m21 15l-3.086-3.086a2 2 0 0 0-2.828 0L6 21" /> 17 | </g> 18 | </IconBase> 19 | )); 20 | -------------------------------------------------------------------------------- /packages/preview-server/src/components/icons/icon-source.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import type { IconElement, IconProps } from './icon-base'; 3 | import { IconBase } from './icon-base'; 4 | 5 | export const IconSource = React.forwardRef<IconElement, Readonly<IconProps>>( 6 | ({ ...props }, forwardedRef) => ( 7 | <IconBase ref={forwardedRef} {...props}> 8 | <path 9 | d="M17.4 15L21 11.5L17.4 8M6.6 8L3 11.5L6.6 15M14.25 4.5L9.75 18.5" 10 | stroke="currentColor" 11 | strokeLinecap="round" 12 | strokeLinejoin="round" 13 | strokeWidth="1.5" 14 | /> 15 | </IconBase> 16 | ), 17 | ); 18 | 19 | IconSource.displayName = 'IconSource'; 20 | -------------------------------------------------------------------------------- /packages/preview-server/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './button'; 2 | export * from './code'; 3 | export * from './heading'; 4 | export * from './logo'; 5 | export * from './sidebar'; 6 | export * from './text'; 7 | export * from './topbar'; 8 | -------------------------------------------------------------------------------- /packages/preview-server/src/components/sidebar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sidebar'; 2 | -------------------------------------------------------------------------------- /packages/preview-server/src/components/toolbar/results-table.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/preview-server/src/components/toolbar/results-table.tsx -------------------------------------------------------------------------------- /packages/preview-server/src/components/tooltip.tsx: -------------------------------------------------------------------------------- 1 | import * as TooltipPrimitive from '@radix-ui/react-tooltip'; 2 | import type * as React from 'react'; 3 | import { TooltipContent } from './tooltip-content'; 4 | 5 | type RootProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Root>; 6 | 7 | export type TooltipProps = RootProps; 8 | 9 | export const TooltipRoot: React.FC<Readonly<TooltipProps>> = ({ 10 | children, 11 | ...props 12 | }) => <TooltipPrimitive.Root {...props}>{children}</TooltipPrimitive.Root>; 13 | 14 | export const Tooltip = Object.assign(TooltipRoot, { 15 | Arrow: TooltipPrimitive.TooltipArrow, 16 | Provider: TooltipPrimitive.TooltipProvider, 17 | Content: TooltipContent, 18 | Trigger: TooltipPrimitive.TooltipTrigger, 19 | }); 20 | -------------------------------------------------------------------------------- /packages/preview-server/src/hooks/use-fragment-identifier.ts: -------------------------------------------------------------------------------- 1 | import { usePathname, useSearchParams } from 'next/navigation'; 2 | import { useEffect, useState } from 'react'; 3 | 4 | export const useFragmentIdentifier = () => { 5 | const pathname = usePathname(); 6 | const searchParams = useSearchParams(); 7 | const [fragmentIdentifier, setFragmentIdentifier] = useState<string>(); 8 | 9 | useEffect(() => { 10 | setFragmentIdentifier(global.location?.hash); 11 | }, [pathname, searchParams]); 12 | 13 | return fragmentIdentifier; 14 | }; 15 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/caniemail/ast/__snapshots__/get-used-style-properties.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`getUsedStyleProperties() 1`] = ` 4 | [ 5 | { 6 | "location": SourceLocation { 7 | "end": Position { 8 | "column": 21, 9 | "index": 91, 10 | "line": 5, 11 | }, 12 | "filename": undefined, 13 | "identifierName": undefined, 14 | "start": Position { 15 | "column": 2, 16 | "index": 72, 17 | "line": 5, 18 | }, 19 | }, 20 | "name": "borderRadius", 21 | "value": "5px", 22 | }, 23 | ] 24 | `; 25 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/caniemail/ast/get-object-variables.spec.ts: -------------------------------------------------------------------------------- 1 | import { parse } from '@babel/parser'; 2 | import { getObjectVariables } from './get-object-variables'; 3 | 4 | test('getObjectVariables()', () => { 5 | const reactCode = ` 6 | <Button style={buttonStyle}>Click me</Button> 7 | 8 | const buttonStyle = { 9 | borderRadius: '5px', 10 | }; 11 | `; 12 | const ast = parse(reactCode, { 13 | strictMode: false, 14 | errorRecovery: true, 15 | sourceType: 'unambiguous', 16 | plugins: ['jsx', 'typescript', 'decorators'], 17 | }); 18 | expect(getObjectVariables(ast)).toMatchSnapshot(); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/caniemail/get-css-property-with-value.ts: -------------------------------------------------------------------------------- 1 | const propertyRegex = 2 | /(?<propertyName>[a-z-]+)\s*:\s*(?<propertyValue>[a-zA-Z\-0-9()+*/_ ]+)/; 3 | 4 | export const getCssPropertyWithValue = (title: string) => { 5 | const match = propertyRegex.exec(title.trim()); 6 | if (match) { 7 | const [_full, propertyName, propertyValue] = match; 8 | return { 9 | name: propertyName!, 10 | value: propertyValue!, 11 | }; 12 | } 13 | return undefined; 14 | }; 15 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/caniemail/get-css-unit.ts: -------------------------------------------------------------------------------- 1 | export const getCssUnit = (title: string) => { 2 | return title.endsWith(' unit') ? title.replace(' unit', '') : undefined; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/caniemail/get-element-attributes.ts: -------------------------------------------------------------------------------- 1 | export function getElementAttributes(title: string) { 2 | if (title.endsWith(' attribute')) { 3 | return [title.replace(' attribute', '')]; 4 | } 5 | 6 | return []; 7 | } 8 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/caniemail/get-element-names.ts: -------------------------------------------------------------------------------- 1 | export const getElementNames = (title: string, keywords: string | null) => { 2 | const match = /<(?<elementName>[^>]*)> element/.exec(title); 3 | if (match) { 4 | const [_full, elementName] = match; 5 | 6 | if (elementName) { 7 | return [elementName.toLowerCase()]; 8 | } 9 | } 10 | 11 | if (keywords !== null && keywords.length > 0) { 12 | return keywords 13 | .toLowerCase() 14 | .split(/\s*,\s*/) 15 | .map((piece) => piece.trim()); 16 | } 17 | 18 | if (title.split(',').length > 1) { 19 | return title 20 | .toLowerCase() 21 | .split(/\s*,\s*/) 22 | .map((piece) => piece.trim()); 23 | } 24 | 25 | return []; 26 | }; 27 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/caniemail/tailwind/setup-tailwind-context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'tailwindcss/lib/lib/setupContextUtils'; 2 | import resolveConfig from 'tailwindcss/resolveConfig'; 3 | import type { TailwindConfig } from './get-tailwind-config'; 4 | 5 | export const setupTailwindContext = (config: TailwindConfig) => { 6 | return createContext( 7 | resolveConfig({ 8 | ...config, 9 | content: [], 10 | corePlugins: { 11 | preflight: false, 12 | }, 13 | }), 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/cn.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | export const cn = (...inputs: ClassValue[]) => { 5 | return twMerge(clsx(inputs)); 6 | }; 7 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const tabTransition = { 2 | type: 'spring', 3 | stiffness: 2000, 4 | damping: 80, 5 | mass: 1, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/copy-text-to-clipboard.ts: -------------------------------------------------------------------------------- 1 | export const copyTextToClipboard = async (text: string) => { 2 | try { 3 | await navigator.clipboard.writeText(text); 4 | } catch { 5 | throw new Error('Not able to copy'); 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/esbuild/escape-string-for-regex.ts: -------------------------------------------------------------------------------- 1 | export function escapeStringForRegex(string: string) { 2 | return string.replace(/[|\\{}()[\]^$+*?.]/g, '\\amp;').replace(/-/g, '\\x2d'); 3 | } 4 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/get-line-and-column-from-offset.spec.ts: -------------------------------------------------------------------------------- 1 | import { getLineAndColumnFromOffset } from './get-line-and-column-from-offset'; 2 | 3 | test('getLineAndColumnFromOffset()', () => { 4 | const content = `export default function MyEmail() { 5 | return <div className="testing classes to make sure this is not removed" id="my-div" aria-label="my beautiful div"> 6 | inside the div, should also stay unchanged 7 | </div>; 8 | }`; 9 | const offset = content.indexOf('className'); 10 | expect(getLineAndColumnFromOffset(offset, content)).toEqual([2, 15]); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/get-line-and-column-from-offset.ts: -------------------------------------------------------------------------------- 1 | export const getLineAndColumnFromOffset = ( 2 | offset: number, 3 | content: string, 4 | ): [line: number, column: number] => { 5 | const lineBreaks = [...content.slice(0, offset).matchAll(/\n|\r|\r\n/g)]; 6 | 7 | const line = lineBreaks.length + 1; 8 | const column = offset - (lineBreaks[lineBreaks.length - 1]?.index ?? 0); 9 | 10 | return [line, column]; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cn'; 2 | export * from './copy-text-to-clipboard'; 3 | export * from './language-map'; 4 | export * from './sanitize'; 5 | export * from './types/as'; 6 | export * from './unreachable'; 7 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/language-map.ts: -------------------------------------------------------------------------------- 1 | const languageMap = { 2 | jsx: 'React', 3 | markup: 'HTML', 4 | markdown: 'Plain Text', 5 | }; 6 | 7 | export default languageMap; 8 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/load-stream.ts: -------------------------------------------------------------------------------- 1 | export async function* loadStream<T>(stream: ReadableStream<T>) { 2 | const reader = stream.getReader(); 3 | try { 4 | while (true) { 5 | const { value, done } = await reader.read(); 6 | if (done) { 7 | break; 8 | } 9 | 10 | yield value; 11 | } 12 | } finally { 13 | reader.releaseLock(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/register-spinner-autostopping.ts: -------------------------------------------------------------------------------- 1 | import logSymbols from 'log-symbols'; 2 | import type { Ora } from 'ora'; 3 | 4 | const spinners = new Set<Ora>(); 5 | 6 | process.on('SIGINT', () => { 7 | spinners.forEach((spinner) => { 8 | if (spinner.isSpinning) { 9 | spinner.stop(); 10 | } 11 | }); 12 | }); 13 | 14 | process.on('exit', (code) => { 15 | if (code !== 0) { 16 | spinners.forEach((spinner) => { 17 | if (spinner.isSpinning) { 18 | spinner.stopAndPersist({ 19 | symbol: logSymbols.error, 20 | }); 21 | } 22 | }); 23 | } 24 | }); 25 | 26 | export const registerSpinnerAutostopping = (spinner: Ora) => { 27 | spinners.add(spinner); 28 | }; 29 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/sanitize.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sanitizes text by replacing underscores and hyphens with spaces 3 | */ 4 | export const sanitize = (text: string): string => { 5 | return text.replace(/[_-]/g, ' '); 6 | }; 7 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/testing/js-email-export-default.js: -------------------------------------------------------------------------------- 1 | // A JavaScript email component with ES6 export default 2 | import { Button, Html } from '@react-email/components'; 3 | 4 | function Email() { 5 | return ( 6 | <Html> 7 | <Button 8 | href="https://example.com" 9 | style={{ background: '#000', color: '#fff', padding: '12px 20px' }} 10 | > 11 | Click me 12 | </Button> 13 | </Html> 14 | ); 15 | } 16 | 17 | export default Email; 18 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/testing/js-email-test.js: -------------------------------------------------------------------------------- 1 | // A simple JavaScript email component 2 | const _React = require('react'); 3 | const { Html, Button } = require('@react-email/components'); 4 | 5 | function Email() { 6 | return ( 7 | <Html> 8 | <Button 9 | href="https://example.com" 10 | style={{ background: '#000', color: '#fff', padding: '12px 20px' }} 11 | > 12 | Click me 13 | </Button> 14 | </Html> 15 | ); 16 | } 17 | 18 | module.exports = Email; 19 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/testing/request-response-email.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | const _req = new Request('https://react.email'); 3 | const _res = new Response('{}'); 4 | 5 | const Email = () => { 6 | return <div />; 7 | }; 8 | 9 | export default Email; 10 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/types/as.ts: -------------------------------------------------------------------------------- 1 | export type As< 2 | DefaultTag extends React.ElementType, 3 | T1 extends React.ElementType, 4 | T2 extends React.ElementType = T1, 5 | T3 extends React.ElementType = T1, 6 | T4 extends React.ElementType = T1, 7 | T5 extends React.ElementType = T1, 8 | > = 9 | | (React.ComponentPropsWithRef<DefaultTag> & { 10 | as?: DefaultTag; 11 | }) 12 | | (React.ComponentPropsWithRef<T1> & { 13 | as: T1; 14 | }) 15 | | (React.ComponentPropsWithRef<T2> & { 16 | as: T2; 17 | }) 18 | | (React.ComponentPropsWithRef<T3> & { 19 | as: T3; 20 | }) 21 | | (React.ComponentPropsWithRef<T4> & { 22 | as: T4; 23 | }) 24 | | (React.ComponentPropsWithRef<T5> & { 25 | as: T5; 26 | }); 27 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/types/email-template.ts: -------------------------------------------------------------------------------- 1 | export interface EmailTemplate { 2 | (props: Record<string, unknown> | Record<string, never>): React.ReactNode; 3 | PreviewProps?: Record<string, unknown>; 4 | } 5 | 6 | export const isEmailTemplate = (val: unknown): val is EmailTemplate => { 7 | return typeof val === 'function'; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/types/error-object.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An object that mimics the structure of the Error class, 3 | * we just can't use the Error class here because server actions can't 4 | * return classes 5 | */ 6 | export interface ErrorObject { 7 | name: string; 8 | stack: string | undefined; 9 | cause?: unknown; 10 | message: string; 11 | } 12 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/types/hot-reload-change.ts: -------------------------------------------------------------------------------- 1 | export interface HotReloadChange { 2 | filename: string; 3 | event: 4 | | 'all' 5 | | 'ready' 6 | | 'add' 7 | | 'change' 8 | | 'addDir' 9 | | 'unlink' 10 | | 'unlinkDir' 11 | | 'raw' 12 | | 'error'; 13 | } 14 | -------------------------------------------------------------------------------- /packages/preview-server/src/utils/unreachable.ts: -------------------------------------------------------------------------------- 1 | export const unreachable = ( 2 | condition: string | Record<string, unknown>, 3 | message = `Entered unreachable code. Received '${ 4 | typeof condition === 'string' ? condition : JSON.stringify(condition) 5 | }'.`, 6 | ): never => { 7 | throw new TypeError(message); 8 | }; 9 | -------------------------------------------------------------------------------- /packages/preview-server/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | environment: 'happy-dom', 7 | }, 8 | esbuild: { 9 | tsconfigRaw: { 10 | compilerOptions: { 11 | jsx: 'react-jsx', 12 | }, 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /packages/preview/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './preview'; 2 | -------------------------------------------------------------------------------- /packages/preview/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/react-email/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | cli 4 | !src/cli 5 | 6 | # for testing 7 | .for-dependency-graph.ts 8 | static 9 | -------------------------------------------------------------------------------- /packages/react-email/.npmignore: -------------------------------------------------------------------------------- 1 | .react-email 2 | ./emails 3 | ./emails/static 4 | node_modules 5 | .turbo 6 | src/cli 7 | tsup.config.ts 8 | -------------------------------------------------------------------------------- /packages/react-email/dev/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # email-dev 2 | 3 | ## 0.0.1 4 | -------------------------------------------------------------------------------- /packages/react-email/dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "email-dev", 3 | "version": "0.0.1", 4 | "bin": "index.js", 5 | "private": true, 6 | "type": "module", 7 | "scripts": { 8 | "start": "node ." 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/react-email/src/commands/.npmignore: -------------------------------------------------------------------------------- 1 | testing 2 | -------------------------------------------------------------------------------- /packages/react-email/src/commands/testing/.gitignore: -------------------------------------------------------------------------------- 1 | # for the `email export` command 2 | out 3 | -------------------------------------------------------------------------------- /packages/react-email/src/utils/esbuild/escape-string-for-regex.ts: -------------------------------------------------------------------------------- 1 | export function escapeStringForRegex(string: string) { 2 | return string.replace(/[|\\{}()[\]^$+*?.]/g, '\\amp;').replace(/-/g, '\\x2d'); 3 | } 4 | -------------------------------------------------------------------------------- /packages/react-email/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './preview/index.js'; 2 | export * from './tree.js'; 3 | -------------------------------------------------------------------------------- /packages/react-email/src/utils/packageJson.ts: -------------------------------------------------------------------------------- 1 | // @ts-expect-error Typescript doesn't want to allow this, but it's fine since we're using tsup 2 | import packageJson from '../../package.json'; 3 | 4 | export { packageJson }; 5 | -------------------------------------------------------------------------------- /packages/react-email/src/utils/preview/get-env-variables-for-preview-app.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | 3 | export const getEnvVariablesForPreviewApp = ( 4 | relativePathToEmailsDirectory: string, 5 | cwd: string, 6 | ) => { 7 | return { 8 | EMAILS_DIR_RELATIVE_PATH: relativePathToEmailsDirectory, 9 | EMAILS_DIR_ABSOLUTE_PATH: path.resolve(cwd, relativePathToEmailsDirectory), 10 | USER_PROJECT_LOCATION: cwd, 11 | } as const; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/react-email/src/utils/preview/hot-reloading/resolve-path-aliases.spec.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { resolvePathAliases } from './resolve-path-aliases.js'; 3 | 4 | test('resolveImports()', async () => { 5 | expect( 6 | resolvePathAliases( 7 | ['@/some-file'], 8 | path.resolve(import.meta.dirname, './test'), 9 | ), 10 | ).toEqual(['./some-file']); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/react-email/src/utils/preview/hot-reloading/test/some-file.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/react-email/src/utils/preview/hot-reloading/test/some-file.ts -------------------------------------------------------------------------------- /packages/react-email/src/utils/preview/hot-reloading/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./*"] 5 | } 6 | }, 7 | "include": ["**/*.ts", "**/*.tsx"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/react-email/src/utils/preview/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hot-reloading/setup-hot-reloading.js'; 2 | export * from './start-dev-server.js'; 3 | -------------------------------------------------------------------------------- /packages/react-email/src/utils/register-spinner-autostopping.ts: -------------------------------------------------------------------------------- 1 | import logSymbols from 'log-symbols'; 2 | import type { Ora } from 'ora'; 3 | 4 | const spinners = new Set<Ora>(); 5 | 6 | process.on('SIGINT', () => { 7 | spinners.forEach((spinner) => { 8 | if (spinner.isSpinning) { 9 | spinner.stop(); 10 | } 11 | }); 12 | }); 13 | 14 | process.on('exit', (code) => { 15 | if (code !== 0) { 16 | spinners.forEach((spinner) => { 17 | if (spinner.isSpinning) { 18 | spinner.stopAndPersist({ 19 | symbol: logSymbols.error, 20 | }); 21 | } 22 | }); 23 | } 24 | }); 25 | 26 | export const registerSpinnerAutostopping = (spinner: Ora) => { 27 | spinners.add(spinner); 28 | }; 29 | -------------------------------------------------------------------------------- /packages/react-email/src/utils/tree.spec.ts: -------------------------------------------------------------------------------- 1 | import { tree } from './tree.js'; 2 | 3 | test('tree(__dirname, 2)', async () => { 4 | expect(await tree(__dirname, 2)).toMatchSnapshot(); 5 | }); 6 | -------------------------------------------------------------------------------- /packages/react-email/src/utils/types/hot-reload-change.ts: -------------------------------------------------------------------------------- 1 | import type { HotReloadEvent } from './hot-reload-event.js'; 2 | 3 | export interface HotReloadChange { 4 | filename: string; 5 | event: HotReloadEvent; 6 | } 7 | -------------------------------------------------------------------------------- /packages/react-email/src/utils/types/hot-reload-event.ts: -------------------------------------------------------------------------------- 1 | import type { EventName } from 'chokidar/handler.js'; 2 | 3 | export type HotReloadEvent = EventName; 4 | -------------------------------------------------------------------------------- /packages/react-email/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig({ 4 | dts: false, 5 | entry: ['./src/index.ts'], 6 | format: ['esm'], 7 | outDir: 'dist', 8 | }); 9 | -------------------------------------------------------------------------------- /packages/react-email/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | environment: 'happy-dom', 7 | }, 8 | esbuild: { 9 | tsconfigRaw: { 10 | compilerOptions: { 11 | jsx: 'react-jsx', 12 | }, 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /packages/render/src/browser/index.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from '../shared/options'; 2 | import { render } from './render'; 3 | 4 | /** 5 | * @deprecated use {@link render} 6 | */ 7 | export const renderAsync = (element: React.ReactElement, options?: Options) => { 8 | return render(element, options); 9 | }; 10 | 11 | export * from '../shared/options'; 12 | export * from '../shared/plain-text-selectors'; 13 | export * from '../shared/utils/pretty'; 14 | export * from './render'; 15 | -------------------------------------------------------------------------------- /packages/render/src/node/index.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from '../shared/options'; 2 | import { render } from './render'; 3 | 4 | /** 5 | * @deprecated use {@link render} 6 | */ 7 | export const renderAsync = (element: React.ReactElement, options?: Options) => { 8 | return render(element, options); 9 | }; 10 | 11 | export * from '../shared/options'; 12 | export * from '../shared/plain-text-selectors'; 13 | export * from '../shared/utils/pretty'; 14 | export * from './render'; 15 | -------------------------------------------------------------------------------- /packages/render/src/react-internals.d.ts: -------------------------------------------------------------------------------- 1 | declare module "react-dom/server.browser" { 2 | export * from "react-dom/server"; 3 | } 4 | -------------------------------------------------------------------------------- /packages/render/src/shared/options.ts: -------------------------------------------------------------------------------- 1 | import type { HtmlToTextOptions } from 'html-to-text'; 2 | import type { pretty } from './utils/pretty'; 3 | 4 | export type Options = { 5 | /** 6 | * @deprecated use {@link pretty} instead 7 | */ 8 | pretty?: boolean; 9 | } & ( 10 | | { 11 | plainText?: false; 12 | } 13 | | { 14 | plainText?: true; 15 | /** 16 | * These are options you can pass down directly to the library we use for 17 | * converting the rendered email's HTML into plain text. 18 | * 19 | * @see https://github.com/html-to-text/node-html-to-text 20 | */ 21 | htmlToTextOptions?: HtmlToTextOptions; 22 | } 23 | ); 24 | -------------------------------------------------------------------------------- /packages/render/src/shared/plain-text-selectors.ts: -------------------------------------------------------------------------------- 1 | import type { SelectorDefinition } from 'html-to-text'; 2 | 3 | export const plainTextSelectors: SelectorDefinition[] = [ 4 | { selector: 'img', format: 'skip' }, 5 | { selector: '[data-skip-in-text=true]', format: 'skip' }, 6 | { 7 | selector: 'a', 8 | options: { linkBrackets: false }, 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /packages/render/src/shared/utils/preview.tsx: -------------------------------------------------------------------------------- 1 | export const Preview: React.FC = () => ( 2 | <> 3 | <div data-skip-in-text={true}>This should be hidden from plain text</div> 4 | <h1>This should be rendered in plain text</h1> 5 | </> 6 | ); 7 | -------------------------------------------------------------------------------- /packages/render/src/shared/utils/template.tsx: -------------------------------------------------------------------------------- 1 | import type * as React from 'react'; 2 | 3 | interface TemplateProps { 4 | firstName: string; 5 | } 6 | 7 | export const Template: React.FC<Readonly<TemplateProps>> = ({ firstName }) => ( 8 | <> 9 | <h1>Welcome, {firstName}!</h1> 10 | <img alt="test" src="img/test.png" /> 11 | <p>Thanks for trying our product. We're thrilled to have you on board!</p> 12 | </> 13 | ); 14 | -------------------------------------------------------------------------------- /packages/render/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/render/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig([ 4 | { 5 | dts: true, 6 | entry: ['./src/node/index.ts'], 7 | outDir: './dist/node', 8 | format: ['cjs', 'esm'], 9 | }, 10 | { 11 | dts: true, 12 | entry: ['./src/browser/index.ts'], 13 | outDir: './dist/browser', 14 | format: ['cjs', 'esm'], 15 | }, 16 | ]); 17 | -------------------------------------------------------------------------------- /packages/row/src/__snapshots__/row.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`<Row> component > renders correctly 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation"><tbody style="width:100%"><tr style="width:100%"></tr></tbody></table><!--/$-->"`; 4 | -------------------------------------------------------------------------------- /packages/row/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './row'; 2 | -------------------------------------------------------------------------------- /packages/row/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/section/src/__snapshots__/section.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`<Section> component > renders correctly 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation"><tbody><tr><td>Lorem ipsum</td></tr></tbody></table><!--/$-->"`; 4 | -------------------------------------------------------------------------------- /packages/section/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './section'; 2 | -------------------------------------------------------------------------------- /packages/section/src/section.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export type SectionProps = Readonly<React.ComponentPropsWithoutRef<'table'>>; 4 | 5 | export const Section = React.forwardRef<HTMLTableElement, SectionProps>( 6 | ({ children, style, ...props }, ref) => { 7 | return ( 8 | <table 9 | align="center" 10 | width="100%" 11 | border={0} 12 | cellPadding="0" 13 | cellSpacing="0" 14 | role="presentation" 15 | {...props} 16 | ref={ref} 17 | style={style} 18 | > 19 | <tbody> 20 | <tr> 21 | <td>{children}</td> 22 | </tr> 23 | </tbody> 24 | </table> 25 | ); 26 | }, 27 | ); 28 | 29 | Section.displayName = 'Section'; 30 | -------------------------------------------------------------------------------- /packages/section/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/tailwind/copy-tailwind-types.mjs: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'node:fs'; 2 | import path from 'node:path'; 3 | import url from 'node:url'; 4 | 5 | const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); 6 | 7 | await fs.cp( 8 | path.resolve(__dirname, './node_modules/tailwindcss/types'), 9 | path.resolve(__dirname, './dist/tailwindcss'), 10 | { 11 | recursive: true, 12 | }, 13 | ); 14 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .yalc 7 | yalc.lock 8 | .pnp.js 9 | yalc.lock 10 | .yarn/install-state.gz 11 | 12 | # testing 13 | /coverage 14 | 15 | # next.js 16 | /.next/ 17 | /out/ 18 | 19 | # production 20 | /build 21 | 22 | # misc 23 | .DS_Store 24 | *.pem 25 | 26 | # debug 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | next-env.d.ts 40 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/nextjs/README.md: -------------------------------------------------------------------------------- 1 | This is a NextJS project bootstrapped with `create-next-app@latest` that contains a Tailwind 2 | email using most of the features we provide that will be auto-built on testing to 3 | make sure this new release works properly with the latest version of NextJS when being built. 4 | 5 | We do this testing so that things are more reliable and this couldn't be done using pnpm 6 | workspaces as they link code instead of actually copying the content into node_modules. 7 | The solution we use to copy the code in a way that mimics the actual `npm install @react-email/tailwind` is `yalc`. 8 | 9 | Se [the test file](../_tests/nextjs.spec.ts) for more info. 10 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/nextjs/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="next" /> 2 | /// <reference types="next/image-types/global" /> 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/nextjs/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | compress: false, 4 | swcMinify: false, 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-with-tailwind", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "preinstall": "yalc add @react-email/tailwind" 10 | }, 11 | "dependencies": { 12 | "@react-email/components": "0.0.36", 13 | "@react-email/tailwind": "file:.yalc/@react-email/tailwind", 14 | "next": "^15", 15 | "react": "^19", 16 | "react-dom": "^19" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^22.0.0", 20 | "@types/react": "^19", 21 | "@types/react-dom": "^19", 22 | "typescript": "^5", 23 | "yalc": "1.0.0-pre.53" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/nextjs/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email/b2407a1156d0251cfe466a159306c47d3bcfcfae/packages/tailwind/integrations/nextjs/src/app/favicon.ico -------------------------------------------------------------------------------- /packages/tailwind/integrations/nextjs/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next'; 2 | import { Inter } from 'next/font/google'; 3 | 4 | const inter = Inter({ subsets: ['latin'] }); 5 | 6 | export const metadata: Metadata = { 7 | title: 'Create Next App', 8 | description: 'Generated by create next app', 9 | }; 10 | 11 | const RootLayout = ({ 12 | children, 13 | }: Readonly<{ 14 | children: React.ReactNode; 15 | }>) => { 16 | return ( 17 | <html lang="en"> 18 | <body className={inter.className}>{children}</body> 19 | </html> 20 | ); 21 | }; 22 | 23 | export default RootLayout; 24 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/nextjs/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@react-email/components'; 2 | import { VercelInviteUserEmail } from '../../emails/vercel-invite-user'; 3 | 4 | export default async function Home() { 5 | const emailHtml = await render(<VercelInviteUserEmail />); 6 | return <div dangerouslySetInnerHTML={{ __html: emailHtml }} />; 7 | } 8 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/nextjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | }, 23 | "target": "ES2017" 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/vite/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | .yalc 13 | yalc.lock 14 | dist-ssr 15 | *.local 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/vite/README.md: -------------------------------------------------------------------------------- 1 | This is a Vite React project bootstrapped with `npm create vite@latest` that contains a Tailwind 2 | email using most of the features we provide that will be auto-built on testing to 3 | make sure this new release works properly with the latest version of Vite when being built. 4 | 5 | We do this testing so that things are more reliable before we get to publishing. This 6 | couldn't be done using pnpm workspaces as they link code instead of actually copying the content into node_modules. 7 | The solution we use to copy the code, in a way that mimics the actual `npm install @react-email/tailwind`, is `yalc`. 8 | 9 | Se [the test file](../_tests/vite.spec.ts) for more info. 10 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/vite/index.html: -------------------------------------------------------------------------------- 1 | <!doctype html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="UTF-8" /> 5 | <link rel="icon" type="image/svg+xml" href="/vite.svg" /> 6 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 | <title>Vite + React + TS</title> 8 | </head> 9 | <body> 10 | <div id="root"></div> 11 | <script type="module" src="/src/main.tsx"></script> 12 | </body> 13 | </html> 14 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-with-tailwind", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preinstall": "yalc add @react-email/tailwind", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@react-email/components": "0.0.36", 14 | "@react-email/tailwind": "file:.yalc/@react-email/tailwind", 15 | "react": "^19", 16 | "react-dom": "^19" 17 | }, 18 | "devDependencies": { 19 | "@types/react": "^19", 20 | "@types/react-dom": "^19", 21 | "@vitejs/plugin-react": "^4.2.1", 22 | "typescript": "^5.2.2", 23 | "vite": "^6.3.2", 24 | "yalc": "1.0.0-pre.53" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/vite/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@react-email/components'; 2 | import { VercelInviteUserEmail } from '../emails/vercel-invite-user'; 3 | 4 | function App() { 5 | const emailHtml = render(<VercelInviteUserEmail />); 6 | 7 | return <div dangerouslySetInnerHTML={{ __html: emailHtml }} />; 8 | } 9 | 10 | export default App; 11 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/vite/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | 5 | ReactDOM.createRoot(document.getElementById('root')!).render( 6 | <React.StrictMode> 7 | <App /> 8 | </React.StrictMode>, 9 | ); 10 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/vite/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="vite/client" /> 2 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/vite/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/tailwind/integrations/vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react'; 2 | import { defineConfig } from 'vite'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }); 8 | -------------------------------------------------------------------------------- /packages/tailwind/src/hooks/__snapshots__/use-tailwind.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`useTailwind() 1`] = ` 4 | ".text-red-500 { 5 | color: rgb(239 68 68 / 1) 6 | } 7 | @media (min-width: 640px) { 8 | .sm\\:bg-blue-300 { 9 | background-color: rgb(147 197 253 / 1) 10 | } 11 | } 12 | .bg-slate-900 { 13 | background-color: rgb(15 23 42 / 1) 14 | } 15 | " 16 | `; 17 | -------------------------------------------------------------------------------- /packages/tailwind/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './tailwind'; 2 | -------------------------------------------------------------------------------- /packages/tailwind/src/utils/__snapshots__/quick-safe-render-to-string.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`quick safe render to string 1`] = `"<div className="bg-red-500 text-gray-200"><h1><Component><div className="user-name flex text-2xl"></div></Component></h1><span className="dark:bg-green-500 hover:bg-green-800 transition-colors"></span></div><span className="dark:bg-green-500 hover:bg-green-800 transition-colors"></span>"`; 4 | -------------------------------------------------------------------------------- /packages/tailwind/src/utils/compatibility/convert-css-property-to-react-property.ts: -------------------------------------------------------------------------------- 1 | import { fromDashCaseToCamelCase } from '../text/from-dash-case-to-camel-case'; 2 | 3 | export const convertCssPropertyToReactProperty = (prop: string) => { 4 | const modifiedProp = prop.toLowerCase(); 5 | 6 | if (modifiedProp.startsWith('--')) { 7 | return modifiedProp; 8 | } 9 | 10 | if (modifiedProp.startsWith('-ms-')) { 11 | return fromDashCaseToCamelCase(modifiedProp.slice(1)); 12 | } 13 | 14 | return fromDashCaseToCamelCase(modifiedProp); 15 | }; 16 | -------------------------------------------------------------------------------- /packages/tailwind/src/utils/compatibility/escape-class-name.spec.ts: -------------------------------------------------------------------------------- 1 | import { escapeClassName } from './escape-class-name'; 2 | 3 | describe('escapeClassName function', () => { 4 | it('should escape the first character properly', () => { 5 | expect(escapeClassName('[min-height-')).toBe('\\[min-height-'); 6 | }); 7 | 8 | it('should not escape an already escaped first-character', () => { 9 | expect(escapeClassName('\\[min-height-')).toBe('\\[min-height-'); 10 | }); 11 | 12 | it('should escape `min-height-[calc(25px+100%-20%*2/4)]` correctly', () => { 13 | expect(escapeClassName('min-height-[calc(25px+100%-20%*2/4)]')).toBe( 14 | 'min-height-\\[calc\\(25px\\+100\\%-20\\%\\*2\\/4\\)\\]', 15 | ); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/tailwind/src/utils/compatibility/escape-class-name.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Escapes all characters that may not be accepted on 3 | * CSS selectors by using the regex "[^a-zA-Z0-9\-_]". 4 | * 5 | * Also does a bit more trickery to avoid escaping already 6 | * escaped characters. 7 | */ 8 | export const escapeClassName = (className: string) => { 9 | return className.replace( 10 | /* we need this look ahead capturing group to avoid using negative look behinds */ 11 | /([^\\]|^)(?=([^a-zA-Z0-9\-_]))/g, 12 | (match, prefixCharacter: string, characterToEscape: string) => { 13 | if (prefixCharacter === '' && characterToEscape === '\\') return match; 14 | 15 | return `${prefixCharacter}\\`; 16 | }, 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /packages/tailwind/src/utils/compatibility/sanitize-class-name.spec.ts: -------------------------------------------------------------------------------- 1 | import { sanitizeClassName } from './sanitize-class-name'; 2 | 3 | test('sanitizeClassName', () => { 4 | expect(sanitizeClassName('min-height-[calc(25px+100%-20%*2/4)]')).toBe( 5 | 'min-height-calc25pxplus100pc-20pc_2_4', 6 | ); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/tailwind/src/utils/compatibility/unescape-class.ts: -------------------------------------------------------------------------------- 1 | export function unescapeClass(singleClass: string) { 2 | return singleClass.replaceAll(/\\[0-9]|\\/g, ''); 3 | } 4 | -------------------------------------------------------------------------------- /packages/tailwind/src/utils/css/make-inline-styles-for.spec.ts: -------------------------------------------------------------------------------- 1 | import { setupTailwind } from '../tailwindcss/setup-tailwind'; 2 | import { makeInlineStylesFor } from './make-inline-styles-for'; 3 | 4 | test('makeInlineStylesFor()', () => { 5 | const tailwind = setupTailwind({}); 6 | 7 | const className = 8 | 'bg-red-500 sm:bg-blue-300 w-full md:max-w-[400px] my-custom-class'; 9 | const tailwindStyles = tailwind.generateRootForClasses(className.split(' ')); 10 | 11 | expect(makeInlineStylesFor(className, tailwindStyles)).toEqual({ 12 | styles: { backgroundColor: 'rgb(239 68 68 / 1)', width: '100%' }, 13 | residualClassName: 'sm:bg-blue-300 md:max-w-[400px] my-custom-class', 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/tailwind/src/utils/css/remove-if-empty-recursively.ts: -------------------------------------------------------------------------------- 1 | import type { Container, Document } from 'postcss'; 2 | 3 | export const removeIfEmptyRecursively = (node: Container | Document) => { 4 | if (node.first === undefined) { 5 | const parent = node.parent; 6 | if (parent) { 7 | node.remove(); 8 | removeIfEmptyRecursively(parent); 9 | } 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /packages/tailwind/src/utils/css/remove-rule-duplicates-from-root.ts: -------------------------------------------------------------------------------- 1 | import type { Root } from 'postcss'; 2 | import { removeIfEmptyRecursively } from './remove-if-empty-recursively'; 3 | 4 | export const removeRuleDuplicatesFromRoot = (root: Root) => { 5 | root.walkRules((rule) => { 6 | root.walkRules(rule.selector, (duplicateRule) => { 7 | if (duplicateRule === rule) return; 8 | 9 | const parent = duplicateRule.parent; 10 | duplicateRule.remove(); 11 | if (parent) { 12 | removeIfEmptyRecursively(parent); 13 | } 14 | }); 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /packages/tailwind/src/utils/react/is-component.ts: -------------------------------------------------------------------------------- 1 | export const isComponent = ( 2 | element: React.ReactElement, 3 | ): element is React.ReactElement<unknown, React.FC<unknown>> => { 4 | return ( 5 | typeof element.type === 'function' || 6 | // @ts-expect-error - we know this is a component that may have a render function 7 | element.type.render !== undefined 8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /packages/tailwind/src/utils/tailwindcss/__snapshots__/setup-tailwind.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`tailwind's generateRootForClasses() 1`] = ` 4 | ".text-red-500 { 5 | color: rgb(239 68 68 / 1) 6 | } 7 | @media (min-width: 640px) { 8 | .sm\\:bg-blue-300 { 9 | background-color: rgb(147 197 253 / 1) 10 | } 11 | } 12 | .bg-slate-900 { 13 | background-color: rgb(15 23 42 / 1) 14 | } 15 | " 16 | `; 17 | -------------------------------------------------------------------------------- /packages/tailwind/src/utils/tailwindcss/setup-tailwind-context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'tailwindcss/lib/lib/setupContextUtils'; 2 | import resolveConfig from 'tailwindcss/resolveConfig'; 3 | import type { TailwindConfig } from '../../tailwind'; 4 | 5 | export const setupTailwindContext = (config: TailwindConfig) => { 6 | return createContext( 7 | resolveConfig({ 8 | ...config, 9 | content: [], 10 | corePlugins: { 11 | preflight: false, 12 | }, 13 | }), 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /packages/tailwind/src/utils/tailwindcss/setup-tailwind.spec.ts: -------------------------------------------------------------------------------- 1 | import { setupTailwind } from './setup-tailwind'; 2 | 3 | test("tailwind's generateRootForClasses()", () => { 4 | const tailwind = setupTailwind({}); 5 | 6 | expect( 7 | tailwind 8 | .generateRootForClasses([ 9 | 'text-red-500', 10 | 'sm:bg-blue-300', 11 | 'bg-slate-900', 12 | ]) 13 | .toString(), 14 | ).toMatchSnapshot(); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/tailwind/src/utils/text/from-dash-case-to-camel-case.ts: -------------------------------------------------------------------------------- 1 | export const fromDashCaseToCamelCase = (text: string) => { 2 | return text.replace(/-(\w|$)/g, (_, p1: string) => p1.toUpperCase()); 3 | }; 4 | -------------------------------------------------------------------------------- /packages/tailwind/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "tsconfig/react-library.json", 4 | "include": ["."], 5 | "exclude": [ 6 | "dist", 7 | "build", 8 | "node_modules", 9 | "integrations/vite", 10 | "integrations/nextjs" 11 | ], 12 | "compilerOptions": { 13 | "strict": true, 14 | "noEmit": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/tailwind/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | export default defineConfig({ 5 | test: { 6 | globals: true, 7 | environment: 'happy-dom', 8 | }, 9 | server: { 10 | watch: { 11 | ignored: [path.resolve(__dirname, './integrations/**/*')], 12 | }, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /packages/text/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './text'; 2 | -------------------------------------------------------------------------------- /packages/text/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/tsconfig/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "composite": false, 6 | "declaration": true, 7 | "declarationMap": true, 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "inlineSources": false, 11 | "isolatedModules": true, 12 | "moduleResolution": "node", 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "preserveWatchOutput": true, 16 | "skipLibCheck": true, 17 | "strict": true, 18 | "strictNullChecks": true 19 | }, 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/tsconfig/nextjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Next.js", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "plugins": [{ "name": "next" }], 7 | "allowJs": true, 8 | "declaration": false, 9 | "declarationMap": false, 10 | "incremental": true, 11 | "jsx": "preserve", 12 | "lib": ["dom", "dom.iterable", "esnext"], 13 | "module": "esnext", 14 | "noEmit": true, 15 | "resolveJsonModule": true, 16 | "strict": false, 17 | "target": "es5" 18 | }, 19 | "include": ["src", "next-env.d.ts"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsconfig", 3 | "version": "0.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "publishConfig": { 7 | "access": "public" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/tsconfig/react-library.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "React Library", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "jsx": "react-jsx", 7 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 8 | "module": "ESNext", 9 | "target": "es6", 10 | "types": ["vitest/globals"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /playground/emails/.gitignore: -------------------------------------------------------------------------------- 1 | **/*.tsx 2 | !example.tsx 3 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "email dev", 7 | "export": "email export" 8 | }, 9 | "dependencies": { 10 | "@react-email/render": "workspace:*", 11 | "react": "^19", 12 | "react-dom": "^19" 13 | }, 14 | "devDependencies": { 15 | "@react-email/preview-server": "workspace:*", 16 | "@types/react": "^19", 17 | "@types/react-dom": "^19", 18 | "react-email": "workspace:*", 19 | "tsconfig": "workspace:*" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@react-email/components": ["./components.ts"] 6 | } 7 | }, 8 | "include": ["."] 9 | } 10 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "apps/*" 3 | - "benchmarks/*" 4 | - "packages/*" 5 | - "packages/react-email/dev" 6 | - "playground" 7 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:recommended"], 4 | "ignorePaths": ["benchmarks"], 5 | "schedule": ["before 3am on the first day of the month"], 6 | "updateNotScheduled": false, 7 | "rebaseWhen": "conflicted" 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "types": ["vitest/globals"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="vitest" /> 2 | 3 | import { defineConfig } from 'vitest/config'; 4 | 5 | export default defineConfig({ 6 | test: { 7 | globals: true, 8 | environment: 'happy-dom', 9 | }, 10 | }); 11 | --------------------------------------------------------------------------------