create-ej-app 15.0.0 → 16.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Ekansh Jain
3
+ Copyright (c) 2026 Ekansh Jain
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,5 +1,238 @@
1
1
  # create-ej-app
2
2
 
3
+ A no-bullshit CLI to bootstrap a production-ready **fucking beast** of a app with Next.js, Better Auth, Drizzle ORM, Elysia, Workflow, shadcn/ui, React Email, and deployment wired up so you don’t screw your shit up in production.
4
+
5
+ ```bash
6
+ bunx create-ej-app@latest
3
7
  ```
4
- npx create-ej-app@latest
8
+
9
+ ---
10
+
11
+ ## ✨ Features (aka the good shit)
12
+
13
+ - **Next.js 16** – Powered by React 19, Server Components, App Router. Fast as hell.
14
+ - **Better Auth** – Auth that doesn’t suck:
15
+ - Email/Password
16
+ - Magic link (because passwords are annoying as fuck)
17
+ - OAuth with GitHub and Google
18
+ - Account linking
19
+ - Admin powers
20
+
21
+ - **Drizzle ORM** – Type-safe SQL over PostgreSQL without the usual ORM garbage.
22
+ - **Elysia** – Built for Bun. Minimal, fast, no nonsense.
23
+ - **Workflow** – Durable jobs so your background tasks don’t randomly die like weak shit.
24
+ - **React Email** – Build transactional emails using React components instead of ugly HTML nightmares.
25
+ - **shadcn/ui** – Clean, accessible components built on Radix UI.
26
+ - **TailwindCSS 4** – Utility-first styling with zero CSS drama.
27
+ - **TypeScript** – Strict types so you don’t ship dumb bugs.
28
+ - **TanStack Query** – Server state that doesn’t implode.
29
+ - **Lucide React** – Icons that don’t look like 2009 garbage.
30
+ - **Docker + Terraform** – Ship this shit properly.
31
+ - Terraform setup for Google Cloud Platform so you don’t YOLO infra.
32
+
33
+ ---
34
+
35
+ ## 🚀 Quick Start (Let’s fucking go)
36
+
37
+ ```bash
38
+ bunx create-ej-app@latest
5
39
  ```
40
+
41
+ You’ll be prompted for:
42
+
43
+ - Project name
44
+ - Description
45
+ - Template
46
+ - Git init (because not using git in 2026 is wild)
47
+
48
+ Example:
49
+
50
+ ```bash
51
+ ? Enter the project name: badass-app
52
+ ? Enter a description: My savage Next.js monster
53
+ ? Select a template: base
54
+ ? Initialize a git repository? yes
55
+ ```
56
+
57
+ Boom. Done.
58
+
59
+ ---
60
+
61
+ ## 📦 What You Actually Get
62
+
63
+ ### Tech Stack (The real weapons)
64
+
65
+ - **Framework**: Next.js
66
+ - **Runtime**: Bun (fast as shit)
67
+ - **Backend**: Elysia APIs
68
+ - **Database**: PostgreSQL + Drizzle
69
+ - **Auth**: Better Auth
70
+ - **Emails**: React Email (transactional + auth templates ready)
71
+ - **Background Jobs**: Workflow
72
+ - **UI**: shadcn/ui
73
+ - **Styling**: TailwindCSS
74
+ - **State**: TanStack Query
75
+ - **Forms**: react-hook-form + Zod
76
+ - **Theme**: Dark mode out of the box
77
+
78
+ ---
79
+
80
+ ## 🛠 Setup & Development
81
+
82
+ ### Prerequisites
83
+
84
+ - Node 24+ or Bun
85
+ - PostgreSQL running
86
+ - OAuth creds if you want GitHub/Google login
87
+ - Email provider (Resend, SES, Postmark, etc.)
88
+
89
+ ### Install
90
+
91
+ ```bash
92
+ cd my-app
93
+ bun install
94
+ # or npm / pnpm / yarn if that’s your thing
95
+ ```
96
+
97
+ ---
98
+
99
+ ### Environment Variables
100
+
101
+ Copy `.env.example` → `.env` and fill your secrets like a responsible adult.
102
+
103
+ Don’t commit this crap. Seriously.
104
+
105
+ ---
106
+
107
+ ## 💻 Development
108
+
109
+ ```bash
110
+ bun run dev
111
+ ```
112
+
113
+ Open `http://localhost:3000` and admire your badass setup.
114
+
115
+ ---
116
+
117
+ ## 🐳 Docker (Ship it like a pro)
118
+
119
+ ```bash
120
+ docker build -t my-app .
121
+ docker run -p 3000:3000 my-app
122
+ ```
123
+
124
+ No “works on my machine” bullshit anymore.
125
+
126
+ ---
127
+
128
+ ## ☁️ Infrastructure (The Cloud Shit)
129
+
130
+ Terraform config included for:
131
+
132
+ - Cloud Run
133
+ - Cloud SQL
134
+ - Artifact Registry
135
+ - Cloud Build
136
+ - Secret Manager
137
+ - Load Balancer
138
+ - VPC
139
+
140
+ Deploy it:
141
+
142
+ ```bash
143
+ cd terraform
144
+ terraform init
145
+ terraform apply
146
+ ```
147
+
148
+ And now you’re running production infra like a grown-ass engineer.
149
+
150
+ ---
151
+
152
+ ## 🔐 Authentication
153
+
154
+ Better Auth gives you:
155
+
156
+ - Email/Password login
157
+ - Magic links (no password drama)
158
+ - OAuth (GitHub, Google)
159
+ - Admin panel
160
+ - User impersonation
161
+
162
+ Secure, extensible, no duct-taped auth spaghetti.
163
+
164
+ ---
165
+
166
+ ## 🎨 UI Components
167
+
168
+ Preinstalled:
169
+
170
+ - Button
171
+ - Card
172
+ - Form
173
+ - Input
174
+ - Toast notifications
175
+
176
+ Need more?
177
+
178
+ ```bash
179
+ bunx shadcn@latest add [component-name]
180
+ ```
181
+
182
+ Ship beautiful shit without reinventing buttons for the 900th time.
183
+
184
+ ---
185
+
186
+ ## 🎯 Route Groups
187
+
188
+ Organized like a sane human:
189
+
190
+ - `(admin)` – Admin dashboard
191
+ - `(app)` – Main app (auth required)
192
+ - `(auth)` – Login/signup
193
+ - `(marketing)` – Public pages
194
+
195
+ No messy folder hell.
196
+
197
+ ---
198
+
199
+ ## 🔧 Customization
200
+
201
+ Edit:
202
+
203
+ - `siteConfig.ts` – change name/description
204
+ - `schema.ts` – change DB schema
205
+ - `auth.ts` – tweak providers
206
+
207
+ Then push schema and move on with your life.
208
+
209
+ ---
210
+
211
+ ## 🤝 Contributing
212
+
213
+ PRs welcome. Don’t submit half-broken shit.
214
+
215
+ ---
216
+
217
+ ## 📄 License
218
+
219
+ MIT. Do whatever the hell you want, just don’t blame us if you screw it up.
220
+
221
+ ---
222
+
223
+ ## 🔗 Links
224
+
225
+ - Repository: [https://github.com/ejekanshjain/create-ej-app](https://github.com/ejekanshjain/create-ej-app)
226
+ - Issues: [https://github.com/ejekanshjain/create-ej-app/issues](https://github.com/ejekanshjain/create-ej-app/issues)
227
+ - Docs:
228
+ - [Next.js](https://nextjs.org/docs)
229
+ - [Better Auth](https://better-auth.com)
230
+ - [Drizzle ORM](https://orm.drizzle.team)
231
+ - [Elysia](https://elysiajs.com)
232
+ - [Workflow](https://useworkflow.dev)
233
+ - [shadcn/ui](https://ui.shadcn.com)
234
+ - [React Email](https://react.email)
235
+
236
+ ---
237
+
238
+ Now go build some badass production-ready shit instead of another half-baked side project. 🚀
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ej-app",
3
- "version": "15.0.0",
3
+ "version": "16.0.0",
4
4
  "description": "Bootstrap a next.js app with my preferred setup",
5
5
  "main": "cli.js",
6
6
  "scripts": {
@@ -5,6 +5,9 @@
5
5
  "private": true,
6
6
  "scripts": {
7
7
  "dev": "next dev",
8
+ "email:dev": "email dev --dir src/emails",
9
+ "email:test": "bun ./src/scripts/test-mail.ts",
10
+ "workflow:inspect": "workflow inspect runs --web",
8
11
  "build": "bun --bun next build",
9
12
  "build:docker": "docker build -t my-app .",
10
13
  "analyze": "ANALYZE=true bun --bun next build --webpack",
@@ -17,19 +20,23 @@
17
20
  "gen:auth": "bunx @better-auth/cli generate"
18
21
  },
19
22
  "dependencies": {
20
- "@ejekanshjain/simple-email-validator": "1.0.2",
23
+ "@ejekanshjain/simple-email-validator": "1.0.3",
24
+ "@elysiajs/eden": "1.4.8",
21
25
  "@hookform/resolvers": "5.2.2",
22
26
  "@next/third-parties": "16.1.6",
23
27
  "@paralleldrive/cuid2": "3.3.0",
24
28
  "@radix-ui/react-label": "2.1.8",
25
29
  "@radix-ui/react-separator": "1.1.8",
26
30
  "@radix-ui/react-slot": "1.2.4",
31
+ "@react-email/components": "1.0.7",
32
+ "@react-email/render": "2.0.4",
27
33
  "@t3-oss/env-nextjs": "0.13.10",
28
34
  "@tanstack/react-query": "5.90.21",
29
35
  "better-auth": "1.4.18",
30
36
  "class-variance-authority": "0.7.1",
31
37
  "clsx": "2.1.1",
32
38
  "drizzle-orm": "0.45.1",
39
+ "elysia": "1.4.25",
33
40
  "lucide-react": "0.563.0",
34
41
  "next": "16.1.6",
35
42
  "next-safe-action": "8.0.11",
@@ -43,11 +50,12 @@
43
50
  "sonner": "2.0.7",
44
51
  "tailwind-merge": "3.4.0",
45
52
  "tw-animate-css": "1.4.0",
46
- "workflow": "4.1.0-beta.55",
53
+ "workflow": "4.1.0-beta.56",
47
54
  "zod": "4.3.6"
48
55
  },
49
56
  "devDependencies": {
50
57
  "@next/bundle-analyzer": "16.1.6",
58
+ "@react-email/preview-server": "5.2.8",
51
59
  "@tailwindcss/postcss": "4.1.18",
52
60
  "@types/bun": "1.3.9",
53
61
  "@types/nodemailer": "7.0.9",
@@ -60,7 +68,16 @@
60
68
  "prettier": "3.8.1",
61
69
  "prettier-plugin-organize-imports": "4.3.0",
62
70
  "prettier-plugin-tailwindcss": "0.7.2",
71
+ "react-email": "5.2.8",
63
72
  "tailwindcss": "4.1.18",
64
73
  "typescript": "5.9.3"
65
- }
74
+ },
75
+ "trustedDependencies": [
76
+ "@nestjs/core",
77
+ "@swc/core",
78
+ "cbor-extract",
79
+ "esbuild",
80
+ "sharp",
81
+ "unrs-resolver"
82
+ ]
66
83
  }
@@ -0,0 +1,3 @@
1
+ export default async function AdminPage() {
2
+ return <div>Hello Admin</div>
3
+ }
@@ -0,0 +1,25 @@
1
+ import { Elysia } from 'elysia'
2
+ import z from 'zod'
3
+
4
+ export const elysiaApi = new Elysia({ prefix: '/api' })
5
+ .get('/', 'Hello from Elysia')
6
+ .post(
7
+ '/',
8
+ ({ body }) => {
9
+ return `Hello, ${body.name}!`
10
+ },
11
+ {
12
+ body: z.object({
13
+ name: z.string().trim().min(1).describe('Name of the user')
14
+ }),
15
+ detail: {
16
+ description: 'Greet a user by name'
17
+ }
18
+ }
19
+ )
20
+
21
+ export const GET = elysiaApi.fetch
22
+ export const POST = elysiaApi.fetch
23
+ export const PUT = elysiaApi.fetch
24
+ export const PATCH = elysiaApi.fetch
25
+ export const DELETE = elysiaApi.fetch
@@ -0,0 +1,63 @@
1
+ import {
2
+ Body,
3
+ Container,
4
+ Head,
5
+ Hr,
6
+ Html,
7
+ pixelBasedPreset,
8
+ Preview,
9
+ Section,
10
+ Tailwind,
11
+ Text
12
+ } from '@react-email/components'
13
+
14
+ interface EmailLayoutProps {
15
+ previewText: string
16
+ companyName: string
17
+ children: React.ReactNode
18
+ }
19
+
20
+ export default function EmailLayout({
21
+ previewText,
22
+ companyName,
23
+ children
24
+ }: EmailLayoutProps) {
25
+ return (
26
+ <Html>
27
+ <Head />
28
+ <Preview>{previewText}</Preview>
29
+ <Tailwind
30
+ config={{
31
+ presets: [pixelBasedPreset]
32
+ }}
33
+ >
34
+ <Body className="bg-gray-100 font-sans">
35
+ <Container className="mx-auto my-8 max-w-xl rounded-lg bg-white shadow-sm">
36
+ {/* Header */}
37
+ <Section className="rounded-t-lg bg-black px-8 py-6">
38
+ <Text className="m-0 text-center text-2xl font-bold text-white">
39
+ {companyName}
40
+ </Text>
41
+ </Section>
42
+
43
+ {/* Content */}
44
+ <Section className="px-8 py-6">{children}</Section>
45
+
46
+ {/* Footer */}
47
+ <Section className="rounded-b-lg bg-gray-50 px-8 py-6">
48
+ <Hr className="my-4 border-gray-200" />
49
+ <Text className="m-0 text-center text-sm text-gray-500">
50
+ &copy; {new Date().getUTCFullYear()} {companyName}. All rights
51
+ reserved.
52
+ </Text>
53
+ <Text className="m-0 mt-2 text-center text-xs text-gray-400">
54
+ This is an automated email. Please do not reply directly to this
55
+ message.
56
+ </Text>
57
+ </Section>
58
+ </Container>
59
+ </Body>
60
+ </Tailwind>
61
+ </Html>
62
+ )
63
+ }
@@ -0,0 +1,49 @@
1
+ import { Button, Heading, Text } from '@react-email/components'
2
+ import EmailLayout from './components/email-layout'
3
+
4
+ export interface MagicLinkEmailProps {
5
+ magicLink: string
6
+ companyName: string
7
+ }
8
+
9
+ export default function MagicLinkEmail({
10
+ magicLink,
11
+ companyName
12
+ }: MagicLinkEmailProps) {
13
+ const previewText = `Your magic link to sign in to ${companyName}`
14
+
15
+ return (
16
+ <EmailLayout previewText={previewText} companyName={companyName}>
17
+ <Heading className="mb-4 text-2xl font-bold text-gray-900">
18
+ Sign in to {companyName}
19
+ </Heading>
20
+
21
+ <Text className="mb-4 text-base text-gray-700">
22
+ Click the button below to securely sign in to your account. This link
23
+ will expire in 5 minutes.
24
+ </Text>
25
+
26
+ <Button
27
+ href={magicLink}
28
+ className="mb-4 rounded-lg bg-black px-6 py-3 text-center text-base font-semibold text-white no-underline"
29
+ >
30
+ Sign In
31
+ </Button>
32
+
33
+ <Text className="mb-4 text-sm text-gray-600">
34
+ Or copy and paste this link into your browser:
35
+ </Text>
36
+
37
+ <Text className="mb-4 text-sm break-all text-blue-600">{magicLink}</Text>
38
+
39
+ <Text className="text-sm text-gray-500">
40
+ If you didn't request this email, you can safely ignore it.
41
+ </Text>
42
+ </EmailLayout>
43
+ )
44
+ }
45
+
46
+ MagicLinkEmail.PreviewProps = {
47
+ magicLink: 'https://example.com/magic-link?token=abc123',
48
+ companyName: 'Example Site'
49
+ } satisfies MagicLinkEmailProps
@@ -0,0 +1,32 @@
1
+ import { Button, Heading, Text } from '@react-email/components'
2
+ import EmailLayout from './components/email-layout'
3
+
4
+ interface TestEmailProps {
5
+ name: string
6
+ }
7
+
8
+ export default function TestEmail({ name }: TestEmailProps) {
9
+ return (
10
+ <EmailLayout previewText="This is a test email" companyName="Test">
11
+ <Heading className="mb-4 text-2xl font-bold text-gray-900">
12
+ Test Email for {name}
13
+ </Heading>
14
+ <Text className="mb-4 text-base text-gray-700">
15
+ This is a test email template to verify email functionality.
16
+ </Text>
17
+ <Text className="mb-6 text-base text-gray-700">
18
+ If you're seeing this, the email system is working correctly.
19
+ </Text>
20
+ <Button
21
+ href="https://example.com"
22
+ className="rounded-md bg-black px-5 py-3 text-center text-sm font-medium text-white"
23
+ >
24
+ Test Button
25
+ </Button>
26
+ </EmailLayout>
27
+ )
28
+ }
29
+
30
+ TestEmail.PreviewProps = {
31
+ name: 'John Doe'
32
+ }
@@ -0,0 +1,34 @@
1
+ import { Heading, Text } from '@react-email/components'
2
+ import EmailLayout from './components/email-layout'
3
+
4
+ export interface WelcomeEmailProps {
5
+ userName: string
6
+ companyName: string
7
+ }
8
+
9
+ export default function WelcomeEmail({
10
+ userName,
11
+ companyName
12
+ }: WelcomeEmailProps) {
13
+ const previewText = `Welcome to ${companyName}! Your account has been created successfully.`
14
+
15
+ return (
16
+ <EmailLayout previewText={previewText} companyName={companyName}>
17
+ <Heading className="mb-4 text-2xl font-bold text-gray-900">
18
+ Welcome to {companyName}! 🎉
19
+ </Heading>
20
+
21
+ <Text className="mb-4 text-base text-gray-700">Hi {userName},</Text>
22
+
23
+ <Text className="mb-4 text-base text-gray-700">
24
+ Thank you for creating an account with us. We're thrilled to have you as
25
+ part of our community!
26
+ </Text>
27
+ </EmailLayout>
28
+ )
29
+ }
30
+
31
+ WelcomeEmail.PreviewProps = {
32
+ userName: 'John Doe',
33
+ companyName: 'Example Site'
34
+ } satisfies WelcomeEmailProps
@@ -10,6 +10,7 @@ import {
10
10
  } from 'better-auth/plugins'
11
11
  import { headers } from 'next/headers'
12
12
  import { cache } from 'react'
13
+ import { start } from 'workflow/api'
13
14
  import { db } from '~/db'
14
15
  import {
15
16
  accountsTable,
@@ -18,6 +19,8 @@ import {
18
19
  verificationsTable
19
20
  } from '~/db/schema'
20
21
  import { env } from '~/env'
22
+ import { sendMagicLinkEmail, sendWelcomeEmail } from './email-service'
23
+ import { siteConfig } from './siteConfig'
21
24
 
22
25
  export const auth = betterAuth({
23
26
  database: drizzleAdapter(db, {
@@ -51,14 +54,39 @@ export const auth = betterAuth({
51
54
  clientSecret: env.BETTER_AUTH_GOOGLE_SECRET
52
55
  }
53
56
  },
57
+ databaseHooks: {
58
+ user: {
59
+ create: {
60
+ after: async user => {
61
+ try {
62
+ await start(sendWelcomeEmail, [
63
+ user.email,
64
+ {
65
+ userName: user.name,
66
+ companyName: siteConfig.name
67
+ }
68
+ ])
69
+ } catch (error) {
70
+ console.error('Failed to send welcome email:', error)
71
+ }
72
+ }
73
+ }
74
+ }
75
+ },
54
76
  plugins: [
55
77
  magicLink({
56
- sendMagicLink: async ({ email, token, url }) => {
57
- console.warn('TODO: send email', {
58
- email,
59
- token,
60
- url
61
- })
78
+ sendMagicLink: async ({ email, url }) => {
79
+ try {
80
+ await start(sendMagicLinkEmail, [
81
+ email,
82
+ {
83
+ magicLink: url,
84
+ companyName: siteConfig.name
85
+ }
86
+ ])
87
+ } catch (error) {
88
+ console.error('Failed to send magic link email:', error)
89
+ }
62
90
  }
63
91
  }),
64
92
  admin(),
@@ -66,10 +94,7 @@ export const auth = betterAuth({
66
94
  ],
67
95
  hooks: {
68
96
  before: createAuthMiddleware(async ctx => {
69
- if (
70
- ctx.path === '/sign-in/magic-link' &&
71
- ctx.body?.email
72
- ) {
97
+ if (ctx.path === '/sign-in/magic-link' && ctx.body?.email) {
73
98
  try {
74
99
  const validation = await validateEmail({
75
100
  email: ctx.body.email
@@ -0,0 +1,7 @@
1
+ import { treaty } from '@elysiajs/eden'
2
+ import { elysiaApi } from '~/app/api/[[...slugs]]/route'
3
+
4
+ export const api =
5
+ typeof process !== 'undefined'
6
+ ? treaty(elysiaApi).api
7
+ : treaty<typeof elysiaApi>(window.location.host).api
@@ -0,0 +1,70 @@
1
+ import { render, toPlainText } from '@react-email/render'
2
+ import MagicLinkEmail, { MagicLinkEmailProps } from '~/emails/magic-link'
3
+ import WelcomeEmail, { WelcomeEmailProps } from '~/emails/welcome'
4
+ import { sendEmail } from './nodemailer'
5
+
6
+ /**
7
+ * Mapping of template types to their corresponding props
8
+ */
9
+ type EmailTemplateMap = {
10
+ welcome: WelcomeEmailProps
11
+ magicLink: MagicLinkEmailProps
12
+ }
13
+
14
+ type EmailTemplateType = keyof EmailTemplateMap
15
+
16
+ // Map of email template components with proper type safety
17
+ const componentMap: Record<
18
+ EmailTemplateType,
19
+ (props: EmailTemplateMap[EmailTemplateType]) => React.ReactElement
20
+ > = {
21
+ welcome: props => WelcomeEmail(props as WelcomeEmailProps),
22
+ magicLink: props => MagicLinkEmail(props as MagicLinkEmailProps)
23
+ }
24
+
25
+ async function renderEmailTemplate<T extends EmailTemplateType>(
26
+ templateType: T,
27
+ props: EmailTemplateMap[T]
28
+ ): Promise<{ html: string; text: string }>
29
+
30
+ async function renderEmailTemplate(
31
+ templateType: EmailTemplateType,
32
+ props: EmailTemplateMap[EmailTemplateType]
33
+ ) {
34
+ 'use step'
35
+
36
+ const emailComponent = componentMap[templateType](props)
37
+
38
+ const html = await render(emailComponent)
39
+ const text = toPlainText(html)
40
+ return { html, text }
41
+ }
42
+
43
+ export async function sendWelcomeEmail(to: string, props: WelcomeEmailProps) {
44
+ 'use workflow'
45
+
46
+ const { html, text } = await renderEmailTemplate('welcome', props)
47
+
48
+ return await sendEmail({
49
+ to,
50
+ subject: `Welcome to ${props.companyName}! 🎉`,
51
+ html,
52
+ text
53
+ })
54
+ }
55
+
56
+ export async function sendMagicLinkEmail(
57
+ to: string,
58
+ props: MagicLinkEmailProps
59
+ ) {
60
+ 'use workflow'
61
+
62
+ const { html, text } = await renderEmailTemplate('magicLink', props)
63
+
64
+ return await sendEmail({
65
+ to,
66
+ subject: `Sign in to ${props.companyName}`,
67
+ html,
68
+ text
69
+ })
70
+ }
@@ -0,0 +1,43 @@
1
+ import nodemailer, { SentMessageInfo } from 'nodemailer'
2
+ import { env } from '~/env'
3
+
4
+ const transporter = nodemailer.createTransport({
5
+ host: env.EMAIL_SERVER_HOST,
6
+ port: parseInt(env.EMAIL_SERVER_PORT),
7
+ secure: parseInt(env.EMAIL_SERVER_PORT) === 465, // Use secure for port 465, false for other ports
8
+ auth: {
9
+ user: env.EMAIL_SERVER_USER,
10
+ pass: env.EMAIL_SERVER_PASSWORD
11
+ }
12
+ })
13
+
14
+ interface SendEmailParams {
15
+ to: string
16
+ subject: string
17
+ text?: string
18
+ html?: string
19
+ }
20
+
21
+ export const sendEmail = async ({
22
+ to,
23
+ subject,
24
+ text,
25
+ html
26
+ }: SendEmailParams): Promise<SentMessageInfo> => {
27
+ 'use step'
28
+
29
+ try {
30
+ const info = await transporter.sendMail({
31
+ from: env.EMAIL_FROM,
32
+ to,
33
+ subject,
34
+ text,
35
+ html
36
+ })
37
+
38
+ return info
39
+ } catch (error) {
40
+ console.error('Error sending email:', error)
41
+ throw new Error('Failed to send email')
42
+ }
43
+ }
@@ -0,0 +1,29 @@
1
+ import { render, toPlainText } from '@react-email/render'
2
+ import TestEmail from '~/emails/test'
3
+ import { sendEmail } from '~/lib/nodemailer'
4
+
5
+ async function main() {
6
+ const html = await render(
7
+ TestEmail({
8
+ name: 'Ekansh Jain'
9
+ }),
10
+ {
11
+ pretty: true
12
+ }
13
+ )
14
+
15
+ const text = toPlainText(html)
16
+
17
+ const result = await sendEmail({
18
+ to: 'john@example.com',
19
+ subject: 'Test Email',
20
+ text,
21
+ html
22
+ })
23
+
24
+ console.info('Test email sent successfully', result)
25
+
26
+ process.exit()
27
+ }
28
+
29
+ main()
@@ -33,7 +33,7 @@ resource "google_cloud_run_v2_service" "my_app_prod" {
33
33
  cpu = "1000m"
34
34
  memory = "1Gi"
35
35
  }
36
- cpu_idle = true
36
+ cpu_idle = true # Set to false when using workflow
37
37
  startup_cpu_boost = true
38
38
  }
39
39