├── .eslintrc.js ├── .gitignore ├── README.md ├── apps └── web │ ├── .eslintrc.js │ ├── README.md │ ├── app │ ├── layout.tsx │ └── page.tsx │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── public │ └── static │ │ ├── vercel-arrow.png │ │ ├── vercel-logo.png │ │ ├── vercel-team.png │ │ └── vercel-user.png │ └── tsconfig.json ├── package-lock.json ├── package.json ├── packages └── transactional │ ├── emails │ ├── static │ │ ├── vercel-arrow.png │ │ ├── vercel-logo.png │ │ ├── vercel-team.png │ │ └── vercel-user.png │ └── vercel-invite-user.tsx │ ├── package.json │ ├── readme.md │ └── tsconfig.json ├── renovate.json ├── tsconfig.json └── turbo.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require("node:path"); 2 | 3 | const project = resolve(process.cwd(), "tsconfig.json"); 4 | 5 | /** @type {import("eslint").Linter.Config} */ 6 | module.exports = { 7 | extends: ["eslint:recommended", "prettier"], 8 | plugins: ["only-warn"], 9 | globals: { 10 | React: true, 11 | JSX: true, 12 | }, 13 | env: { 14 | node: true, 15 | }, 16 | settings: { 17 | "import/resolver": { 18 | typescript: { 19 | project, 20 | }, 21 | }, 22 | }, 23 | ignorePatterns: [ 24 | // Ignore dotfiles 25 | ".*.js", 26 | "node_modules/", 27 | "dist/", 28 | ], 29 | overrides: [ 30 | { 31 | files: ["*.js?(x)", "*.ts?(x)"], 32 | }, 33 | ], 34 | }; 35 | -------------------------------------------------------------------------------- /.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 | # Local env files 9 | .env 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | # Testing 16 | coverage 17 | 18 | # Turbo 19 | .turbo 20 | 21 | # Vercel 22 | .vercel 23 | 24 | # Build Outputs 25 | .react-email/ 26 | .next/ 27 | out/ 28 | build 29 | dist 30 | 31 | # Debug 32 | npm-debug.log* 33 | yarn-debug.log* 34 | yarn-error.log* 35 | 36 | # Misc 37 | .DS_Store 38 | *.pem 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Email + Turborepo + npm workspaces 2 | 3 | This example shows how to use React Email with [Turborepo](https://turbo.build) + [npm workspaces](https://docs.npmjs.com/cli/v10/using-npm/workspaces). 4 | 5 | ### Structure 6 | 7 | This monorepo includes the following apps: 8 | 9 | - `apps/web`: a [Next.js](https://nextjs.org) app 10 | - `packages/transactional`: a package with [react.email](https://react.email) email templates 11 | 12 | ## Instructions 13 | 14 | 1. Install dependencies: 15 | 16 | ```sh 17 | npm install 18 | ``` 19 | 20 | 2. Run locally: 21 | 22 | ```sh 23 | npm run dev 24 | ``` 25 | 26 | 3. Open URL in the browser: 27 | 28 | * http://localhost:3000 29 | * http://localhost:3001 30 | 31 | ## License 32 | 33 | MIT License 34 | -------------------------------------------------------------------------------- /apps/web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require("node:path"); 2 | 3 | const project = resolve(process.cwd(), "tsconfig.json"); 4 | 5 | /** @type {import("eslint").Linter.Config} */ 6 | module.exports = { 7 | extends: [ 8 | "eslint:recommended", 9 | "prettier", 10 | require.resolve("@vercel/style-guide/eslint/next") 11 | ], 12 | parser: '@typescript-eslint/parser', 13 | globals: { 14 | React: true, 15 | JSX: true, 16 | }, 17 | env: { 18 | node: true, 19 | }, 20 | plugins: ["only-warn", "@typescript-eslint"], 21 | settings: { 22 | "import/resolver": { 23 | typescript: { 24 | project, 25 | }, 26 | }, 27 | }, 28 | ignorePatterns: [ 29 | // Ignore dotfiles 30 | ".*.js", 31 | "node_modules/", 32 | ], 33 | overrides: [{ files: ["*.js?(x)", "*.ts?(x)"] }], 34 | }; 35 | -------------------------------------------------------------------------------- /apps/web/README.md: -------------------------------------------------------------------------------- 1 | ## NextJS Web app 2 | 3 | A simple example NextJS app that imports the email template from the 4 | [transactional](../../packages/transactional/readme.md) package and renders 5 | it on the index page. 6 | 7 | ### Running app 8 | 9 | If you want to run the app individually you can just run: 10 | 11 | ```sh 12 | npm run dev 13 | ``` 14 | 15 | ## License 16 | 17 | MIT License 18 | -------------------------------------------------------------------------------- /apps/web/app/layout.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Next.js', 3 | description: 'Generated by Next.js', 4 | } 5 | 6 | export default function RootLayout({ 7 | children, 8 | }: { 9 | children: React.ReactNode 10 | }) { 11 | return ( 12 | 13 | {children} 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /apps/web/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@react-email/components'; 2 | 3 | import { VercelInviteUserEmail } from 'transactional/emails/vercel-invite-user'; 4 | 5 | export default function Page(): JSX.Element { 6 | const emailHTML = render(VercelInviteUserEmail({ })); 7 | 8 | return ( 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /apps/web/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/web/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = {}; 3 | -------------------------------------------------------------------------------- /apps/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "eslint . --max-warnings 0" 10 | }, 11 | "dependencies": { 12 | "@react-email/components": "0.0.14", 13 | "next": "14.1.0", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "transactional": "^1.0.0" 17 | }, 18 | "devDependencies": { 19 | "@next/eslint-plugin-next": "^14.0.2", 20 | "@types/eslint": "^8.44.7", 21 | "@types/node": "^17.0.12", 22 | "@types/react": "^18.0.22", 23 | "@types/react-dom": "^18.0.7", 24 | "eslint": "^8.53.0", 25 | "typescript": "^5.2.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/web/public/static/vercel-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email-turborepo-npm-example/ca9e866302113c0c9a57f36ea2bae715ba3c4640/apps/web/public/static/vercel-arrow.png -------------------------------------------------------------------------------- /apps/web/public/static/vercel-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email-turborepo-npm-example/ca9e866302113c0c9a57f36ea2bae715ba3c4640/apps/web/public/static/vercel-logo.png -------------------------------------------------------------------------------- /apps/web/public/static/vercel-team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email-turborepo-npm-example/ca9e866302113c0c9a57f36ea2bae715ba3c4640/apps/web/public/static/vercel-team.png -------------------------------------------------------------------------------- /apps/web/public/static/vercel-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email-turborepo-npm-example/ca9e866302113c0c9a57f36ea2bae715ba3c4640/apps/web/public/static/vercel-user.png -------------------------------------------------------------------------------- /apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "module": "ESNext", 6 | "moduleResolution": "Bundler", 7 | "allowJs": true, 8 | "jsx": "preserve", 9 | "noEmit": true, 10 | "plugins": [ 11 | { 12 | "name": "next" 13 | } 14 | ] 15 | }, 16 | "include": [ 17 | "next-env.d.ts", 18 | "next.config.js", 19 | "**/*.ts", 20 | "**/*.tsx", 21 | ".next/types/**/*.ts" 22 | ], 23 | "exclude": ["node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-email-turborepo-npm-example", 3 | "private": true, 4 | "scripts": { 5 | "build": "turbo build", 6 | "dev": "turbo dev", 7 | "lint": "turbo lint", 8 | "format": "prettier --write \"**/*.{ts,tsx,md}\"" 9 | }, 10 | "devDependencies": { 11 | "prettier": "^3.1.0", 12 | "@typescript-eslint/eslint-plugin": "^7.0.0", 13 | "@typescript-eslint/parser": "^7.0.0", 14 | "@vercel/style-guide": "^5.1.0", 15 | "eslint-config-prettier": "^9.0.0", 16 | "eslint-config-turbo": "^1.10.12", 17 | "eslint-plugin-only-warn": "^1.1.0", 18 | "styled-jsx": "^5.1.2", 19 | "typescript": "^5.2.2", 20 | "turbo": "latest" 21 | }, 22 | "workspaces": ["packages/*", "apps/*"], 23 | "packageManager": "npm@9.8.1" 24 | } 25 | -------------------------------------------------------------------------------- /packages/transactional/emails/static/vercel-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email-turborepo-npm-example/ca9e866302113c0c9a57f36ea2bae715ba3c4640/packages/transactional/emails/static/vercel-arrow.png -------------------------------------------------------------------------------- /packages/transactional/emails/static/vercel-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email-turborepo-npm-example/ca9e866302113c0c9a57f36ea2bae715ba3c4640/packages/transactional/emails/static/vercel-logo.png -------------------------------------------------------------------------------- /packages/transactional/emails/static/vercel-team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email-turborepo-npm-example/ca9e866302113c0c9a57f36ea2bae715ba3c4640/packages/transactional/emails/static/vercel-team.png -------------------------------------------------------------------------------- /packages/transactional/emails/static/vercel-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resend/react-email-turborepo-npm-example/ca9e866302113c0c9a57f36ea2bae715ba3c4640/packages/transactional/emails/static/vercel-user.png -------------------------------------------------------------------------------- /packages/transactional/emails/vercel-invite-user.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Button, 4 | Column, 5 | Container, 6 | Head, 7 | Heading, 8 | Hr, 9 | Html, 10 | Img, 11 | Link, 12 | Preview, 13 | Row, 14 | Section, 15 | Tailwind, 16 | Text, 17 | } from '@react-email/components'; 18 | import * as React from 'react'; 19 | 20 | interface VercelInviteUserEmailProps { 21 | username?: string; 22 | userImage?: string; 23 | invitedByUsername?: string; 24 | invitedByEmail?: string; 25 | teamName?: string; 26 | teamImage?: string; 27 | inviteLink?: string; 28 | inviteFromIp?: string; 29 | inviteFromLocation?: string; 30 | } 31 | 32 | const baseUrl = process.env.VERCEL_URL 33 | ? `https://${process.env.VERCEL_URL}` 34 | : ''; 35 | 36 | export const VercelInviteUserEmail = ({ 37 | username = 'zenorocha', 38 | userImage = `${baseUrl}/static/vercel-user.png`, 39 | invitedByUsername = 'bukinoshita', 40 | invitedByEmail = 'bukinoshita@example.com', 41 | teamName = 'My Project', 42 | teamImage = `${baseUrl}/static/vercel-team.png`, 43 | inviteLink = 'https://vercel.com/teams/invite/foo', 44 | inviteFromIp = '204.13.186.218', 45 | inviteFromLocation = 'São Paulo, Brazil', 46 | }: VercelInviteUserEmailProps) => { 47 | const previewText = `Join ${invitedByUsername} on Vercel`; 48 | 49 | return ( 50 | 51 | 52 | {previewText} 53 | 54 | 55 | 56 |
57 | Vercel 64 |
65 | 66 | Join {teamName} on Vercel 67 | 68 | 69 | Hello {username}, 70 | 71 | 72 | bukinoshita ( 73 | 77 | {invitedByEmail} 78 | 79 | ) has invited you to the {teamName} team on{' '} 80 | Vercel. 81 | 82 |
83 | 84 | 85 | 86 | 87 | 88 | invited you to 94 | 95 | 96 | 97 | 98 | 99 |
100 |
101 | 107 |
108 | 109 | or copy and paste this URL into your browser:{' '} 110 | 114 | {inviteLink} 115 | 116 | 117 |
118 | 119 | This invitation was intended for{' '} 120 | {username} .This invite was sent from{' '} 121 | {inviteFromIp} located in{' '} 122 | {inviteFromLocation}. If you were not 123 | expecting this invitation, you can ignore this email. If you are 124 | concerned about your account's safety, please reply to this email to 125 | get in touch with us. 126 | 127 |
128 | 129 |
130 | 131 | ); 132 | }; 133 | 134 | export default VercelInviteUserEmail; 135 | -------------------------------------------------------------------------------- /packages/transactional/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "transactional", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "email dev -p 3001", 7 | "export": "email export" 8 | }, 9 | "dependencies": { 10 | "@react-email/components": "0.0.14", 11 | "browserslist": "^4.22.2", 12 | "react-email": "2.0.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/transactional/readme.md: -------------------------------------------------------------------------------- 1 | ## Transactional 2 | 3 | This is usually where you store transactional-related code of your application to organize and simplify things. 4 | This also allows for using the email templates you make anywhere on your codebase by just installing with 5 | the monorepo setup. 6 | 7 | This also uses the [react.email](https://react.email/) CLI for previewing and compiling the email templates 8 | into HTML ones. 9 | 10 | Using the CLI you can also preview your emails, see how they are going to look and try sending them 11 | to yourself for testing purposes. 12 | 13 | ### Previewing email templates 14 | 15 | First, install the dependencies: 16 | 17 | ```sh 18 | npm install 19 | ``` 20 | 21 | Then, you can run the react.email developmenet server by running: 22 | 23 | ```sh 24 | npm run dev 25 | ``` 26 | 27 | Open [localhost:3001](http://localhost:3001) with your browser to see the result. 28 | 29 | --- 30 | 31 | See the [react.email docs](https://react.email/docs/introduction) for more details. 32 | 33 | ## License 34 | 35 | MIT License 36 | -------------------------------------------------------------------------------- /packages/transactional/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "include": [ 5 | "." 6 | ], 7 | "exclude": [ 8 | "dist", 9 | "build", 10 | "node_modules" 11 | ], 12 | "compilerOptions": { 13 | "strict": true, 14 | "jsx": "react-jsx" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "declaration": true, 6 | "declarationMap": true, 7 | "esModuleInterop": true, 8 | "incremental": false, 9 | "isolatedModules": true, 10 | "lib": ["es2022", "DOM", "DOM.Iterable"], 11 | "module": "NodeNext", 12 | "moduleDetection": "force", 13 | "moduleResolution": "NodeNext", 14 | "noUncheckedIndexedAccess": true, 15 | "resolveJsonModule": true, 16 | "skipLibCheck": true, 17 | "strict": true, 18 | "target": "ES2022" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "globalDependencies": ["**/.env.*local"], 4 | "pipeline": { 5 | "build": { 6 | "dependsOn": ["^build"], 7 | "outputs": [".next/**", "!.next/cache/**"] 8 | }, 9 | "lint": { 10 | "dependsOn": ["^lint"] 11 | }, 12 | "dev": { 13 | "cache": false, 14 | "persistent": true 15 | } 16 | } 17 | } 18 | --------------------------------------------------------------------------------