vibe-ship-it 1.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.
Files changed (29) hide show
  1. package/README.md +133 -0
  2. package/bin/init.js +135 -0
  3. package/package.json +30 -0
  4. package/template/CLAUDE.md +71 -0
  5. package/template/_claude/commands/check.md +6 -0
  6. package/template/_claude/commands/save.md +9 -0
  7. package/template/_claude/commands/ship.md +8 -0
  8. package/template/_claude/commands/stuck.md +10 -0
  9. package/template/_claude/commands/wow.md +10 -0
  10. package/template/_github/agents/assistant.agent.md +60 -0
  11. package/template/_github/agents/checker.agent.md +93 -0
  12. package/template/_github/agents/investigator.agent.md +196 -0
  13. package/template/_github/agents/shipper.agent.md +180 -0
  14. package/template/_github/copilot-instructions.md +60 -0
  15. package/template/skills/add-login/SKILL.md +204 -0
  16. package/template/skills/before-you-ship/SKILL.md +132 -0
  17. package/template/skills/build-page/SKILL.md +137 -0
  18. package/template/skills/make-it-wow/SKILL.md +139 -0
  19. package/template/skills/noob-mode/SKILL.md +135 -0
  20. package/template/skills/platforms/web-nextjs/SKILL.md +160 -0
  21. package/template/skills/platforms/web-nextjs/references/tailwind-shortcuts.md +97 -0
  22. package/template/skills/quick-check/SKILL.md +89 -0
  23. package/template/skills/save-data/SKILL.md +155 -0
  24. package/template/skills/send-email/SKILL.md +164 -0
  25. package/template/skills/show-data/SKILL.md +209 -0
  26. package/template/skills/unstuck/SKILL.md +105 -0
  27. package/template/skills/upload-file/SKILL.md +188 -0
  28. package/template/skills/what-am-i-building/SKILL.md +129 -0
  29. package/template/skills/what-am-i-building/references/platform-routing.md +66 -0
@@ -0,0 +1,160 @@
1
+ ---
2
+ name: web-nextjs
3
+ description: "Next.js + Tailwind + Supabase + Vercel conventions. Loaded automatically when building a web project."
4
+ ---
5
+
6
+ # Web Platform: Next.js
7
+
8
+ The default web stack. Next.js App Router + Tailwind CSS + Supabase + Vercel.
9
+
10
+ ## Project Structure
11
+
12
+ Explain to the designer as:
13
+ > "Your project is organized into folders. Think of each folder as a room with a specific purpose."
14
+
15
+ ```
16
+ src/
17
+ app/ ← Pages (each folder = a page on your site)
18
+ page.tsx ← Home page (yourdomain.com/)
19
+ layout.tsx ← The wrapper around every page (nav, footer)
20
+ about/
21
+ page.tsx ← About page (yourdomain.com/about)
22
+ contact/
23
+ page.tsx ← Contact page (yourdomain.com/contact)
24
+ dashboard/
25
+ page.tsx ← Dashboard (yourdomain.com/dashboard)
26
+ login/
27
+ page.tsx ← Login page (yourdomain.com/login)
28
+ actions.ts ← Server actions (code that saves data)
29
+ components/ ← Reusable pieces (navigation, footer, cards)
30
+ utils/
31
+ supabase/
32
+ server.ts ← Server-side database connection
33
+ client.ts ← Browser-side database connection
34
+ middleware.ts ← Page protection (bouncer for private pages)
35
+
36
+ public/ ← Static files (images, favicon)
37
+ .env.local ← Secret settings (database keys, API keys)
38
+ ```
39
+
40
+ ## Key Conventions
41
+
42
+ ### Server vs Client Components
43
+
44
+ Don't explain "server components" and "client components" in those terms. Say:
45
+
46
+ - **Default (server):** "This code runs on the server before the page is sent to the visitor. Good for loading data from the database."
47
+ - **`'use client'`:** "This code runs in the visitor's browser. Needed when things are interactive — forms, buttons that do stuff, anything that changes on screen."
48
+
49
+ Rule of thumb:
50
+ - Page shows data from database → server component (default, no directive)
51
+ - Page has interactive elements (forms, modals, toggles) → add `'use client'` at the top
52
+
53
+ ### Server Actions
54
+
55
+ Explain as: "Code that runs when someone submits a form."
56
+
57
+ ```typescript
58
+ 'use server'
59
+
60
+ export async function doSomething(formData: FormData) {
61
+ // This runs on the server, not in the visitor's browser
62
+ // Safe to access the database here
63
+ }
64
+ ```
65
+
66
+ Connect to a form:
67
+ ```tsx
68
+ <form action={doSomething}>
69
+ ```
70
+
71
+ ### Routing
72
+
73
+ - Each folder in `app/` = a page
74
+ - `page.tsx` = what shows at that URL
75
+ - `layout.tsx` = wrapper (navigation, footer) that stays the same across pages
76
+ - `loading.tsx` = what shows while the page is loading (optional)
77
+ - `error.tsx` = what shows if something goes wrong (optional)
78
+
79
+ ### Styling (Tailwind)
80
+
81
+ Don't explain Tailwind utility names. Just use them. If they ask:
82
+ > "Tailwind uses short class names instead of writing CSS. `text-lg` means larger text, `bg-blue-500` means blue background. You don't need to memorize these — I'll pick the right ones."
83
+
84
+ ### Images
85
+
86
+ Use Next.js Image component for optimization:
87
+ ```tsx
88
+ import Image from 'next/image'
89
+
90
+ <Image
91
+ src="/photo.jpg"
92
+ alt="Description"
93
+ width={800}
94
+ height={600}
95
+ className="rounded-lg"
96
+ />
97
+ ```
98
+
99
+ For external images (Supabase Storage, Unsplash), add the domain to `next.config.ts`:
100
+ ```typescript
101
+ const nextConfig = {
102
+ images: {
103
+ remotePatterns: [
104
+ { hostname: '*.supabase.co' },
105
+ ],
106
+ },
107
+ }
108
+ ```
109
+
110
+ ### Environment Variables
111
+
112
+ `.env.local` — local development only, never committed to git:
113
+ ```
114
+ NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
115
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
116
+ RESEND_API_KEY=re_xxx
117
+ ```
118
+
119
+ - `NEXT_PUBLIC_` prefix = accessible in browser code
120
+ - No prefix = server-only (for secrets like API keys)
121
+
122
+ When deploying, these must be set on Vercel too.
123
+
124
+ ## Common Commands
125
+
126
+ | Command | What it does |
127
+ |---|---|
128
+ | `npm run dev` | Start the development server (preview in browser) |
129
+ | `npm run build` | Build for production (check for errors) |
130
+ | `npx vercel` | Preview deployment |
131
+ | `npx vercel --prod` | Production deployment |
132
+
133
+ ## Packages to Install as Needed
134
+
135
+ | When | Package | Install |
136
+ |---|---|---|
137
+ | Database/Auth/Storage | Supabase | `npm install @supabase/supabase-js @supabase/ssr` |
138
+ | Animations | Framer Motion | `npm install framer-motion` |
139
+ | Email | Resend | `npm install resend` |
140
+ | Icons | Lucide React | `npm install lucide-react` |
141
+ | Date formatting | date-fns | `npm install date-fns` |
142
+
143
+ Don't pre-install everything. Install when first needed.
144
+
145
+ ## Supabase Setup (One Time)
146
+
147
+ 1. Go to supabase.com and create a free project
148
+ 2. Go to Settings → API
149
+ 3. Copy the URL and anon key
150
+ 4. Create `.env.local` with those values
151
+
152
+ > "You need a free Supabase account — that's where your data, logins, and file uploads live. Takes 2 minutes to set up."
153
+
154
+ ## Vercel Deployment
155
+
156
+ 1. `npx vercel` (first time: link to Vercel account)
157
+ 2. Set environment variables on Vercel dashboard
158
+ 3. `npx vercel --prod` for production
159
+
160
+ > "When you want to put it online, I'll run one command. First time it'll ask you to connect your Vercel account — just follow the link."
@@ -0,0 +1,97 @@
1
+ # Tailwind CSS Quick Reference
2
+
3
+ Common patterns for designers. Organized by what you want to achieve, not by CSS property name.
4
+
5
+ ## Spacing
6
+
7
+ | Want | Class | What it does |
8
+ |---|---|---|
9
+ | Space between sections | `space-y-4` / `space-y-8` | Vertical gap between children |
10
+ | Space inside a box | `p-4` / `p-8` / `px-4 py-8` | Padding (inside cushion) |
11
+ | Space outside a box | `m-4` / `mx-auto` | Margin (outside spacing) |
12
+ | Page side margins | `px-4 md:px-8` | Horizontal padding, wider on desktop |
13
+ | Center content | `max-w-7xl mx-auto` | Max width + auto horizontal margins |
14
+ | Section breathing room | `py-16 md:py-24` | Generous vertical padding |
15
+
16
+ ## Typography
17
+
18
+ | Want | Class |
19
+ |---|---|
20
+ | Giant headline | `text-5xl md:text-7xl font-bold tracking-tight` |
21
+ | Section heading | `text-3xl font-semibold` |
22
+ | Subheading | `text-xl font-medium text-gray-600` |
23
+ | Body text | `text-base leading-relaxed` |
24
+ | Large body | `text-lg leading-relaxed` |
25
+ | Small/caption | `text-sm text-gray-500` |
26
+ | Light text | `text-gray-400` |
27
+ | Bold | `font-bold` or `font-semibold` |
28
+
29
+ ## Layout
30
+
31
+ | Want | Class |
32
+ |---|---|
33
+ | Side by side (2 cols) | `grid grid-cols-1 md:grid-cols-2 gap-8` |
34
+ | Three columns | `grid grid-cols-1 md:grid-cols-3 gap-6` |
35
+ | Flexbox row | `flex items-center gap-4` |
36
+ | Flexbox column | `flex flex-col gap-4` |
37
+ | Full screen height | `min-h-screen` |
38
+ | Full width | `w-full` |
39
+ | Centered everything | `flex items-center justify-center min-h-screen` |
40
+ | Sticky header | `sticky top-0 z-50 bg-white/80 backdrop-blur` |
41
+
42
+ ## Colors
43
+
44
+ | Want | Class |
45
+ |---|---|
46
+ | Background | `bg-white` / `bg-gray-50` / `bg-black` |
47
+ | Text color | `text-gray-900` / `text-white` / `text-blue-600` |
48
+ | Accent background | `bg-blue-500` / `bg-indigo-600` (adjust hue) |
49
+ | Subtle card bg | `bg-gray-50` or `bg-white` |
50
+ | Dark section | `bg-gray-900 text-white` |
51
+
52
+ ## Cards & Containers
53
+
54
+ | Want | Class |
55
+ |---|---|
56
+ | Basic card | `bg-white rounded-xl p-6 shadow-sm border` |
57
+ | Elevated card | `bg-white rounded-2xl p-8 shadow-lg` |
58
+ | Glass effect | `bg-white/80 backdrop-blur rounded-xl border border-white/20` |
59
+ | Outlined card | `border rounded-xl p-6` |
60
+
61
+ ## Buttons
62
+
63
+ | Want | Class |
64
+ |---|---|
65
+ | Primary | `bg-black text-white px-6 py-2 rounded-lg hover:bg-gray-800 transition-colors` |
66
+ | Secondary | `border px-6 py-2 rounded-lg hover:bg-gray-50 transition-colors` |
67
+ | With hover lift | Add `hover:scale-[1.02] hover:shadow-lg transition-all` |
68
+ | Disabled | Add `disabled:opacity-50 disabled:cursor-not-allowed` |
69
+
70
+ ## Images
71
+
72
+ | Want | Class |
73
+ |---|---|
74
+ | Rounded | `rounded-lg` or `rounded-2xl` |
75
+ | Circle (profile) | `rounded-full w-12 h-12 object-cover` |
76
+ | Cover container | `w-full h-64 object-cover` |
77
+ | Aspect ratio | `aspect-square` / `aspect-video` / `aspect-[4/3]` |
78
+
79
+ ## Responsive
80
+
81
+ All classes can be prefixed for different screen sizes:
82
+ - No prefix = mobile (default)
83
+ - `md:` = tablet and up (768px+)
84
+ - `lg:` = desktop (1024px+)
85
+
86
+ Example: `text-2xl md:text-4xl lg:text-6xl` — heading grows on bigger screens.
87
+
88
+ ## Animation
89
+
90
+ | Want | Class |
91
+ |---|---|
92
+ | Smooth transitions | `transition-all duration-300` |
93
+ | Hover scale | `hover:scale-105 transition-transform` |
94
+ | Hover lift | `hover:-translate-y-1 hover:shadow-xl transition-all` |
95
+ | Fade in (custom) | See make-it-wow skill |
96
+ | Spin | `animate-spin` (for loading indicators) |
97
+ | Pulse | `animate-pulse` (for skeleton loading) |
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: quick-check
3
+ description: "Fast 3-item quality check. Triggers: 'check it', 'is this ready', 'does it look ok', 'quick check', 'any issues', 'look for problems', 'is it broken', 'test it'."
4
+ ---
5
+
6
+ # Quick Check
7
+
8
+ The fast quality check. 3 items. Takes under a minute. Good enough for "can I show my friend?"
9
+
10
+ ## The 3 Checks
11
+
12
+ ### 1. Does It Load?
13
+
14
+ Open every page in the browser. Check:
15
+ - No blank/white pages
16
+ - No console errors (open dev tools → Console)
17
+ - No visible "Error" or "500" or "404" on the page
18
+ - Content actually renders (not just a loading spinner forever)
19
+
20
+ ✅ Pass: all pages show content
21
+ ❌ Fail: any page shows an error, stays blank, or shows a crash screen
22
+
23
+ ### 2. Does The Main Thing Work?
24
+
25
+ Identify the PRIMARY user action (form submit, button click, navigation flow) and do it:
26
+ - If there's a form → fill it out and submit. Does it save? Does it show confirmation?
27
+ - If there's a CTA button → click it. Does it go somewhere?
28
+ - If there's a login → try logging in. Does it work?
29
+ - If it's a portfolio → do images load? Can you navigate between pages?
30
+
31
+ ✅ Pass: the main action completes successfully
32
+ ❌ Fail: the main action breaks, shows an error, or does nothing
33
+
34
+ ### 3. Mobile?
35
+
36
+ Resize the browser to 375px width (iPhone SE). Check:
37
+ - Text is readable (not overflowing or cut off)
38
+ - Navigation is usable (hamburger menu works, or links are tappable)
39
+ - No horizontal scroll
40
+ - Content isn't overlapping
41
+
42
+ ✅ Pass: looks usable on a phone
43
+ ❌ Fail: layout is broken, text overflows, navigation is unusable
44
+
45
+ ## Report Format
46
+
47
+ ### All Good
48
+ ```
49
+ ✅ Quick check — looks good.
50
+
51
+ 1. ✅ All pages load fine
52
+ 2. ✅ Form submits and saves correctly
53
+ 3. ✅ Looks good on mobile
54
+
55
+ Ready to show someone!
56
+ ```
57
+
58
+ ### Issues Found
59
+ ```
60
+ Quick check — found 1 thing:
61
+
62
+ 1. ✅ All pages load fine
63
+ 2. ✅ Form submits correctly
64
+ 3. ⚠️ Mobile: the headline text is too wide and gets cut off on phones
65
+
66
+ This won't break anything, but it'll look off on smaller screens.
67
+ Want me to fix it?
68
+ ```
69
+
70
+ ### Serious Issue
71
+ ```
72
+ Quick check — found a problem:
73
+
74
+ 1. ✅ All pages load
75
+ 2. ❌ The contact form submits but nothing happens — no confirmation,
76
+ no data saved. The server action might not be connected.
77
+ 3. ✅ Mobile looks fine
78
+
79
+ This means the main feature doesn't work yet.
80
+ Want me to fix it?
81
+ ```
82
+
83
+ ## Rules
84
+
85
+ - NEVER skip a check
86
+ - NEVER sugarcoat a broken main feature — be direct
87
+ - DO minimize minor issues — "the footer spacing is 4px off" is not worth mentioning in a quick check
88
+ - Always end with "Want me to fix it?" if issues are found
89
+ - If everything passes, encourage: "Ready to show someone!" or "Looks good for a demo!"
@@ -0,0 +1,155 @@
1
+ ---
2
+ name: save-data
3
+ description: "Saves form data to a database. Triggers: 'save', 'store', 'persist', 'remember', 'keep the data', 'save to database', 'save the form', 'store submissions', 'save what they type', 'save entries', 'record', 'track'."
4
+ ---
5
+
6
+ # Save Data
7
+
8
+ Makes forms actually save data. Handles the complete flow: create the database table, write the save function, connect it to the form, and test it.
9
+
10
+ ## The Spreadsheet Analogy
11
+
12
+ Before creating anything, show the designer what their "spreadsheet" will look like:
13
+
14
+ > "Here's what your data will look like — think of it as a spreadsheet:
15
+ >
16
+ > | name | email | message | submitted at |
17
+ > |---|---|---|---|
18
+ > | Jane Smith | jane@email.com | Hello! I'd like... | Apr 3, 2026 |
19
+ > | Alex Kim | alex@email.com | Question about... | Apr 3, 2026 |
20
+ >
21
+ > Each time someone fills out the form, a new line gets added here."
22
+
23
+ ## Implementation Steps
24
+
25
+ ### Step 1: Create the Table
26
+
27
+ Generate SQL for Supabase:
28
+
29
+ ```sql
30
+ create table [table_name] (
31
+ id uuid default gen_random_uuid() primary key,
32
+ -- columns match the form fields
33
+ created_at timestamptz default now()
34
+ );
35
+
36
+ -- Enable Row Level Security
37
+ alter table [table_name] enable row level security;
38
+
39
+ -- Policy: anyone can insert (for public forms)
40
+ create policy "Anyone can submit" on [table_name]
41
+ for insert with check (true);
42
+
43
+ -- Policy: only authenticated users can read (for admin)
44
+ create policy "Authenticated users can read" on [table_name]
45
+ for select using (auth.role() = 'authenticated');
46
+ ```
47
+
48
+ Tell the designer:
49
+ > "Go to your Supabase dashboard → SQL Editor → paste this and run it. That creates your spreadsheet."
50
+
51
+ Or if Supabase CLI is set up, run it directly.
52
+
53
+ ### Step 2: Set Up Supabase Client
54
+
55
+ If not already set up, create the Supabase client utilities:
56
+
57
+ **`src/utils/supabase/server.ts`** — for server-side operations:
58
+ ```typescript
59
+ import { createServerClient } from '@supabase/ssr'
60
+ import { cookies } from 'next/headers'
61
+
62
+ export async function createClient() {
63
+ const cookieStore = await cookies()
64
+ return createServerClient(
65
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
66
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
67
+ {
68
+ cookies: {
69
+ getAll() { return cookieStore.getAll() },
70
+ setAll(cookiesToSet) {
71
+ cookiesToSet.forEach(({ name, value, options }) =>
72
+ cookieStore.set(name, value, options)
73
+ )
74
+ },
75
+ },
76
+ }
77
+ )
78
+ }
79
+ ```
80
+
81
+ **`.env.local`** — environment variables:
82
+ ```
83
+ NEXT_PUBLIC_SUPABASE_URL=your-project-url
84
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
85
+ ```
86
+
87
+ Tell the designer:
88
+ > "I need two settings from your Supabase dashboard — go to Settings → API and copy the URL and the anon key."
89
+
90
+ ### Step 3: Create the Server Action
91
+
92
+ ```typescript
93
+ 'use server'
94
+
95
+ import { createClient } from '@/utils/supabase/server'
96
+
97
+ export async function save[Name](formData: FormData) {
98
+ const supabase = await createClient()
99
+
100
+ const { error } = await supabase.from('[table_name]').insert({
101
+ // map form fields to columns
102
+ })
103
+
104
+ if (error) {
105
+ return { error: 'Something went wrong. Try again.' }
106
+ }
107
+
108
+ return { success: true }
109
+ }
110
+ ```
111
+
112
+ ### Step 4: Connect to Form
113
+
114
+ Add the action to the existing form:
115
+ ```tsx
116
+ <form action={save[Name]}>
117
+ {/* existing fields */}
118
+ <button type="submit">Send</button>
119
+ </form>
120
+ ```
121
+
122
+ Add a success message:
123
+ ```tsx
124
+ // Show confirmation after submit
125
+ "Thanks! Your message was sent."
126
+ ```
127
+
128
+ ### Step 5: Test It
129
+
130
+ > "Try filling out the form and clicking Send. Then check your Supabase dashboard — go to Table Editor → [table_name]. You should see your entry."
131
+
132
+ ## Platform Adaptations
133
+
134
+ | Platform | Storage | Approach |
135
+ |---|---|---|
136
+ | Web (Next.js) | Supabase | Server actions + Supabase client (above) |
137
+ | Mobile (Expo) | Supabase | Supabase JS client directly |
138
+ | Prototype / quick test | localStorage | Just save to browser storage (no server needed) |
139
+ | WordPress | WP REST API | Custom endpoint + database |
140
+
141
+ ## Multiple Forms
142
+
143
+ If the project already has other saved data:
144
+ - Reuse the existing Supabase client (don't create another one)
145
+ - Create a new table for each distinct type of data
146
+ - Create a new server action for each form
147
+
148
+ ## Common Issues
149
+
150
+ | Problem | Fix |
151
+ |---|---|
152
+ | Form submits but no data appears | Check Supabase RLS policies — insert policy might be missing |
153
+ | "Missing environment variable" | Check `.env.local` has the Supabase URL and key |
154
+ | Data saves but page doesn't show confirmation | Add success state handling to the form |
155
+ | Duplicate entries | Add a loading/disabled state to the submit button |
@@ -0,0 +1,164 @@
1
+ ---
2
+ name: send-email
3
+ description: "Sends emails from your app. Triggers: 'send email', 'email notification', 'notify me', 'confirmation email', 'send a notification', 'email when', 'alert me', 'send message', 'email the user', 'welcome email', 'thank you email'."
4
+ ---
5
+
6
+ # Send Email
7
+
8
+ Sends emails when things happen — confirmations, notifications, welcome messages.
9
+
10
+ ## Default: Resend
11
+
12
+ Simplest email API. Generous free tier (100 emails/day). Beautiful templates with React Email.
13
+
14
+ ### Step 1: Install
15
+
16
+ ```bash
17
+ npm install resend
18
+ ```
19
+
20
+ ### Step 2: Get API Key
21
+
22
+ > "Go to resend.com, create a free account, and copy your API key. I'll store it securely."
23
+
24
+ Add to `.env.local`:
25
+ ```
26
+ RESEND_API_KEY=re_your_api_key_here
27
+ ```
28
+
29
+ ### Step 3: Create Send Function
30
+
31
+ `src/utils/email.ts`:
32
+ ```typescript
33
+ import { Resend } from 'resend'
34
+
35
+ const resend = new Resend(process.env.RESEND_API_KEY)
36
+
37
+ export async function sendEmail({
38
+ to,
39
+ subject,
40
+ html,
41
+ }: {
42
+ to: string
43
+ subject: string
44
+ html: string
45
+ }) {
46
+ const { error } = await resend.emails.send({
47
+ from: 'Your App <onboarding@resend.dev>',
48
+ to,
49
+ subject,
50
+ html,
51
+ })
52
+
53
+ if (error) {
54
+ console.error('Email failed:', error)
55
+ return { success: false }
56
+ }
57
+
58
+ return { success: true }
59
+ }
60
+ ```
61
+
62
+ Note: `onboarding@resend.dev` is Resend's free testing address. For production, connect a custom domain in Resend dashboard.
63
+
64
+ ### Step 4: Connect to an Event
65
+
66
+ Common patterns:
67
+
68
+ **After form submission (notify the designer):**
69
+ ```typescript
70
+ 'use server'
71
+
72
+ import { createClient } from '@/utils/supabase/server'
73
+ import { sendEmail } from '@/utils/email'
74
+
75
+ export async function saveContact(formData: FormData) {
76
+ const supabase = await createClient()
77
+ const name = formData.get('name') as string
78
+ const email = formData.get('email') as string
79
+ const message = formData.get('message') as string
80
+
81
+ // Save to database
82
+ await supabase.from('contacts').insert({ name, email, message })
83
+
84
+ // Notify you
85
+ await sendEmail({
86
+ to: 'you@youremail.com',
87
+ subject: `New message from ${name}`,
88
+ html: `
89
+ <h2>New contact form submission</h2>
90
+ <p><strong>From:</strong> ${name} (${email})</p>
91
+ <p><strong>Message:</strong></p>
92
+ <p>${message}</p>
93
+ `,
94
+ })
95
+
96
+ return { success: true }
97
+ }
98
+ ```
99
+
100
+ **Confirmation to the user:**
101
+ ```typescript
102
+ // After saving, also email the person who submitted
103
+ await sendEmail({
104
+ to: email,
105
+ subject: 'Thanks for reaching out!',
106
+ html: `
107
+ <h2>Got your message!</h2>
108
+ <p>Hi ${name}, thanks for getting in touch.
109
+ I'll get back to you within 24 hours.</p>
110
+ `,
111
+ })
112
+ ```
113
+
114
+ ### Step 5: Test It
115
+
116
+ > "Submit your contact form. Check two things:
117
+ > 1. You should get a notification email
118
+ > 2. The person who submitted should get a confirmation
119
+ >
120
+ > Note: With the free Resend account, emails might go to spam at first. That's normal for testing."
121
+
122
+ ## Email Templates
123
+
124
+ Keep emails simple. Designers can style them later.
125
+
126
+ **Minimal template:**
127
+ ```html
128
+ <div style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
129
+ <h2 style="color: #333;">Subject Here</h2>
130
+ <p style="color: #555; line-height: 1.6;">Content here.</p>
131
+ <hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;" />
132
+ <p style="color: #999; font-size: 12px;">Sent from Your App</p>
133
+ </div>
134
+ ```
135
+
136
+ ## Common Patterns
137
+
138
+ | When | Email | To |
139
+ |---|---|---|
140
+ | Contact form submitted | "New message from [name]" | Designer (admin) |
141
+ | Contact form submitted | "Thanks for reaching out!" | The visitor |
142
+ | Booking created | "New booking: [date/time]" | Designer (admin) |
143
+ | Booking created | "Your booking is confirmed" | The customer |
144
+ | Order placed | "New order #[id]" | Designer (admin) |
145
+ | Account created | "Welcome!" | New user |
146
+
147
+ ## Custom Domain (Production)
148
+
149
+ For emails to not go to spam:
150
+ 1. Go to Resend dashboard → Domains
151
+ 2. Add your domain (e.g., yourbrand.com)
152
+ 3. Add the DNS records Resend gives you
153
+ 4. Update the `from` address: `'Your Name <hello@yourbrand.com>'`
154
+
155
+ > "Right now emails come from a test address. When you're ready to go live, we'll connect your domain so emails come from you@yourbrand.com."
156
+
157
+ ## Common Issues
158
+
159
+ | Problem | Fix |
160
+ |---|---|
161
+ | Email not received | Check spam folder. With free tier, emails often land in spam |
162
+ | "API key invalid" | Check `.env.local` — the key should start with `re_` |
163
+ | Email sends but looks ugly | Use the HTML template above instead of plain text |
164
+ | Rate limited | Free tier is 100/day. Upgrade if needed |