├── .gitignore ├── .vscode ├── extensions.json └── launch.json ├── README.md ├── astro.config.mjs ├── package-lock.json ├── package.json ├── public └── favicon.svg ├── src ├── components │ └── EmailForm.tsx ├── emails │ └── SampleEmail.tsx ├── env.d.ts └── pages │ ├── api │ └── sendEmail.json.ts │ └── index.astro ├── tailwind.config.mjs └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Astro Starter Kit: Minimal 2 | 3 | ```sh 4 | npm create astro@latest -- --template minimal 5 | ``` 6 | 7 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal) 8 | [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/minimal) 9 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/minimal/devcontainer.json) 10 | 11 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 12 | 13 | ## 🚀 Project Structure 14 | 15 | Inside of your Astro project, you'll see the following folders and files: 16 | 17 | ```text 18 | / 19 | ├── public/ 20 | ├── src/ 21 | │ └── pages/ 22 | │ └── index.astro 23 | └── package.json 24 | ``` 25 | 26 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 27 | 28 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. 29 | 30 | Any static assets, like images, can be placed in the `public/` directory. 31 | 32 | ## 🧞 Commands 33 | 34 | All commands are run from the root of the project, from a terminal: 35 | 36 | | Command | Action | 37 | | :------------------------ | :----------------------------------------------- | 38 | | `npm install` | Installs dependencies | 39 | | `npm run dev` | Starts local dev server at `localhost:4321` | 40 | | `npm run build` | Build your production site to `./dist/` | 41 | | `npm run preview` | Preview your build locally, before deploying | 42 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | 43 | | `npm run astro -- --help` | Get help using the Astro CLI | 44 | 45 | ## 👀 Want to learn more? 46 | 47 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). 48 | -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | import node from "@astrojs/node"; 3 | import react from "@astrojs/react"; 4 | 5 | import tailwind from "@astrojs/tailwind"; 6 | 7 | // https://astro.build/config 8 | export default defineConfig({ 9 | output: "server", 10 | adapter: node({ 11 | mode: "standalone" 12 | }), 13 | integrations: [react(), tailwind()] 14 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "resend-overview", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro check && astro build", 9 | "preview": "astro preview", 10 | "astro": "astro" 11 | }, 12 | "dependencies": { 13 | "@astrojs/check": "^0.3.1", 14 | "@astrojs/node": "^6.0.4", 15 | "@astrojs/react": "^3.0.6", 16 | "@astrojs/tailwind": "^5.0.2", 17 | "@react-email/components": "^0.0.11", 18 | "@react-email/render": "^0.0.9", 19 | "@types/react": "^18.2.38", 20 | "@types/react-dom": "^18.2.17", 21 | "astro": "^3.6.0", 22 | "react": "^18.2.0", 23 | "react-dom": "^18.2.0", 24 | "react-email": "^1.9.5", 25 | "resend": "^2.0.0", 26 | "tailwindcss": "^3.3.5", 27 | "typescript": "^5.3.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/EmailForm.tsx: -------------------------------------------------------------------------------- 1 | import { render } from "@react-email/render"; 2 | import SampleEmail from "../emails/SampleEmail"; 3 | import { useState } from "react"; 4 | 5 | const dbData = [ 6 | { 7 | email: "chris@learnastro.dev", 8 | name: "Chris Pennington", 9 | sent: false, 10 | }, 11 | { 12 | email: "chris@learnastro.dev", 13 | name: "Chris Alternate", 14 | sent: false, 15 | }, 16 | { 17 | email: "chris@learnastro.dev", 18 | name: "Chris Tertiary", 19 | sent: false, 20 | }, 21 | ]; 22 | 23 | const EmailForm = () => { 24 | const [data, setData] = useState(dbData); 25 | 26 | const handleSubmit = async (e: React.FormEvent) => { 27 | e.preventDefault(); 28 | 29 | data.forEach(async (d) => { 30 | const finalHtml = render(, { 31 | pretty: true, 32 | }); 33 | 34 | const finalText = render(, { 35 | plainText: true, 36 | }); 37 | 38 | try { 39 | const res = await fetch("/api/sendEmail.json", { 40 | method: "POST", 41 | headers: { 42 | "Content-Type": "application/json", 43 | }, 44 | body: JSON.stringify({ 45 | from: "chris@learnastro.dev", 46 | to: d.email, 47 | subject: `Hi, ${d.name}`, 48 | html: finalHtml, 49 | text: finalText, 50 | }), 51 | }); 52 | const data = await res.json(); 53 | if (data) { 54 | setData((prev) => { 55 | return [ 56 | ...prev.map((p) => { 57 | if (p.name === d.name) { 58 | return { ...p, sent: true }; 59 | } 60 | return p; 61 | }), 62 | ]; 63 | }); 64 | } 65 | } catch (e) { 66 | console.error(e); 67 | } 68 | }); 69 | }; 70 | 71 | return ( 72 |
73 | {data && ( 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | {data.map((row) => ( 84 | 85 | 86 | 87 | 88 | 89 | ))} 90 | 91 |
EmailNameSent
{row.email}{row.name}{row.sent ? "✅" : "❌"}
92 | )} 93 |
94 | 95 |
96 |
97 | ); 98 | }; 99 | export default EmailForm; 100 | -------------------------------------------------------------------------------- /src/emails/SampleEmail.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Button, 4 | Container, 5 | Head, 6 | Hr, 7 | Html, 8 | Img, 9 | Preview, 10 | Section, 11 | Text, 12 | } from "@react-email/components"; 13 | import * as React from "react"; 14 | 15 | interface SampleEmailProps { 16 | userFirstname: string; 17 | } 18 | 19 | export const SampleEmail = ({ userFirstname = "Zeno" }: SampleEmailProps) => ( 20 | 21 | 22 | 23 | The sales intelligence platform that helps you uncover qualified leads. 24 | 25 | 26 | 27 | Astro Course 34 | Hi {userFirstname}, 35 | 36 | Welcome to Koala, the sales intelligence platform that helps you 37 | uncover qualified leads and close deals faster. 38 | 39 |
40 | 43 |
44 | 45 | Best, 46 |
47 | The Koala team 48 |
49 |
50 | 408 Warren Rd - San Mateo, CA 94402 51 |
52 | 53 | 54 | ); 55 | 56 | export default SampleEmail; 57 | 58 | const main = { 59 | backgroundColor: "#ffffff", 60 | fontFamily: 61 | '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif', 62 | }; 63 | 64 | const container = { 65 | margin: "0 auto", 66 | padding: "20px 0 48px", 67 | }; 68 | 69 | const logo = { 70 | margin: "0 auto", 71 | }; 72 | 73 | const paragraph = { 74 | fontSize: "16px", 75 | lineHeight: "26px", 76 | }; 77 | 78 | const btnContainer = { 79 | textAlign: "center" as const, 80 | }; 81 | 82 | const button = { 83 | backgroundColor: "#5F51E8", 84 | borderRadius: "3px", 85 | color: "#fff", 86 | padding: "12px", 87 | fontSize: "16px", 88 | textDecoration: "none", 89 | textAlign: "center" as const, 90 | display: "block", 91 | }; 92 | 93 | const hr = { 94 | borderColor: "#cccccc", 95 | margin: "20px 0", 96 | }; 97 | 98 | const footer = { 99 | color: "#8898aa", 100 | fontSize: "12px", 101 | }; 102 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/pages/api/sendEmail.json.ts: -------------------------------------------------------------------------------- 1 | import type { APIRoute } from "astro"; 2 | import { Resend } from "resend"; 3 | 4 | const resend = new Resend(import.meta.env.RESEND_API_KEY); 5 | 6 | export const POST: APIRoute = async ({ params, request }) => { 7 | const body = await request.json(); 8 | const { to, from, html, subject, text } = body; 9 | 10 | if (!to || !from || !html || !subject || !text) { 11 | return new Response(null, { 12 | status: 404, 13 | statusText: "Did not provide the right data", 14 | }); 15 | } 16 | 17 | const send = await resend.emails.send({ 18 | from, 19 | to, 20 | subject, 21 | html, 22 | text, 23 | }); 24 | 25 | if (send.data) { 26 | return new Response( 27 | JSON.stringify({ 28 | message: send.data, 29 | }), 30 | { 31 | status: 200, 32 | statusText: "OK", 33 | } 34 | ); 35 | } else { 36 | return new Response( 37 | JSON.stringify({ 38 | message: send.error, 39 | }), 40 | { 41 | status: 500, 42 | statusText: "Internal Server Error", 43 | } 44 | ); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import EmailForm from "../components/EmailForm"; 3 | --- 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Astro 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tailwind.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "jsxImportSource": "react" 6 | } 7 | } --------------------------------------------------------------------------------