├── .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 |