Skip to main content

new-product-website

End-to-end workflow for launching a new product landing page — scaffolding, theming, analytics, waitlist, domain, SEO, and deployment.

Jump to install

Source facts

Repository
mediar-ai/skillhubz
Last source activity
April 16, 2026 at 03:19
Detected SKILL.md language
English
Stars
7
Forks
4

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
new-product-website
description
End-to-end workflow for launching a new product landing page — scaffolding, theming, analytics, waitlist, domain, SEO, and deployment.
# New Product Website Automates the full launch workflow for a new product landing page: scaffold Next.js app, configure theme, wire analytics + waitlist, deploy to Google Cloud Run, configure domain, and register with Google Search Console. ## Arguments Provide the product name, domain, and a brief description. Example: `"MyApp at myapp.com — AI-powered task management"` ## Prerequisites - **Google Cloud** project under the m13v.com org (or create a new one) - **Domain** purchased (managed via Google Cloud DNS) - **PostHog** account (US or EU instance) - **Resend** account for transactional/waitlist emails (shared account across projects) - **Neon** Postgres database for email storage - **Google Search Console** access - **GitHub** org or personal account ## Stack - Next.js 15 (App Router) + React 19 + TypeScript - Tailwind CSS 3.4 + Framer Motion - PostHog for analytics - Resend for outbound emails, inbound receiving, and waitlist - Neon Postgres for email storage (`{product}_emails` table) - Google Cloud Run for hosting (with HTTPS Load Balancer + Certificate Manager) - Google Search Console for SEO ## Workflow ### 1. Scaffold Project ```bash mkdir ~/PROJECT_NAME && cd ~/PROJECT_NAME ``` Create these files manually (don't use `create-next-app` — it hangs on interactive prompts): | File | Purpose | |------|---------| | `package.json` | next 15, react 19, framer-motion, lucide-react, posthog-js, @neondatabase/serverless | | `tsconfig.json` | Standard Next.js TS config with `@/*` path alias | | `next.config.ts` | `output: "standalone"` for Cloud Run Docker builds | | `postcss.config.mjs` | tailwindcss + autoprefixer | | `tailwind.config.ts` | Custom theme (accent color, fonts, animations) | | `src/app/globals.css` | Dark theme, gradient-text, noise-overlay, grid-bg | | `src/app/layout.tsx` | Root layout with metadata, OG tags, PostHogProvider | | `src/app/page.tsx` | Compose all sections | ### 2. Build Sections Standard landing page sections (adapt content per product): 1. **Navbar** — Sticky, backdrop blur, logo + nav links + CTA button 2. **Hero** — Headline + subheadline + terminal/demo animation + waitlist CTA 3. **Stats** — Animated counters with real metrics 4. **How It Works** — 3-step flow (no decorative icons) 5. **Results/Social Proof** — Real examples styled as platform cards 6. **Features** — 6-card grid (no decorative icons) 7. **FAQ** — Accordion with AnimatePresence 8. **CTA** — Email capture form + "No spam" disclaimer 9. **Footer** — Logo + nav links ### 3. Wire PostHog 1. Log in to your PostHog instance 2. Create new project named after the product 3. Copy the project API key (starts with `phc_`) 4. Note the PostHog host URL (e.g., `https://us.i.posthog.com` or `https://eu.i.posthog.com`) 5. Create `src/components/posthog-provider.tsx`: ```tsx "use client"; import posthog from "posthog-js"; import { PostHogProvider as PHProvider } from "posthog-js/react"; import { useEffect } from "react"; const POSTHOG_KEY = process.env.NEXT_PUBLIC_POSTHOG_KEY; const POSTHOG_HOST = process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://us.i.posthog.com"; export function PostHogProvider({ children }: { children: React.ReactNode }) { useEffect(() => { if (POSTHOG_KEY && typeof window !== "undefined") { posthog.init(POSTHOG_KEY, { api_host: POSTHOG_HOST, person_profiles: "identified_only", capture_pageview: true, capture_pageleave: true, }); } }, []); if (!POSTHOG_KEY) return <>{children}</>; return <PHProvider client={posthog}>{children}</PHProvider>; } export { posthog }; ``` 6. Wrap children in `layout.tsx` with `<PostHogProvider>` ### 4. Wire Resend (Domain + Waitlist + Inbound) #### 4a. Add & Verify Domain in Resend 1. Log in to Resend > Domains > Add domain > enter `DOMAIN` 2. Resend will show DNS records (DKIM TXT, SPF MX + TXT). Add them via Google Cloud DNS: ```bash # Find the managed zone for your domain gcloud dns managed-zones list --project=GCP_PROJECT_ID # DKIM gcloud dns record-sets create resend._domainkey.DOMAIN. --type=TXT --ttl=300 \ --rrdatas='"DKIM_VALUE"' --zone=DNS_ZONE --project=GCP_PROJECT_ID # SPF gcloud dns record-sets create send.DOMAIN. --type=MX --ttl=300 \ --rrdatas='10 feedback-smtp.us-east-1.amazonses.com.' --zone=DNS_ZONE --project=GCP_PROJECT_ID gcloud dns record-sets create send.DOMAIN. --type=TXT --ttl=300 \ --rrdatas='"v=spf1 include:amazonses.com ~all"' --zone=DNS_ZONE --project=GCP_PROJECT_ID # DMARC (deliverability + anti-spoofing) gcloud dns record-sets create _dmarc.DOMAIN. --type=TXT --ttl=300 \ --rrdatas='"v=DMARC1; p=none;"' --zone=DNS_ZONE --project=GCP_PROJECT_ID ``` 3. Wait for Resend to verify (usually < 5 min) #### 4b. Enable Inbound Receiving 1. In Resend > Domains > click your domain > Records tab 2. Toggle **"Enable Receiving"** ON 3. Resend shows an MX record for `@`. Add it: ```bash gcloud dns record-sets create DOMAIN. --type=MX --ttl=300 \ --rrdatas='10 inbound-smtp.us-east-1.amazonaws.com.' --zone=DNS_ZONE --project=GCP_PROJECT_ID ``` 4. Verify with `dig MX DOMAIN +short` — should show `10 inbound-smtp.us-east-1.amazonaws.com.` 5. Wait for Resend to verify the MX record #### 4c. Create Email Storage Table Create a `{product}_emails` table in the project's Neon database: ```sql CREATE TABLE IF NOT EXISTS {product}_emails ( id SERIAL PRIMARY KEY, resend_id TEXT, direction TEXT NOT NULL DEFAULT 'inbound', from_email TEXT, to_email TEXT, subject TEXT, body_text TEXT, body_html TEXT, status TEXT DEFAULT 'received', created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_{product}_emails_created_at ON {product}_emails(created_at DESC); ``` #### 4d. Create Inbound Webhook Create `src/app/api/webhooks/resend/route.ts`: ```tsx import { NextResponse } from "next/server"; import { neon } from "@neondatabase/serverless"; interface ResendWebhookPayload { type: string; created_at: string; data: { email_id: string; from: string; to: string[]; subject: string; text?: string; html?: string; }; } async function fetchInboundContent(emailId: string) { const apiKey = process.env.RESEND_API_KEY; if (!apiKey) return null; try { const res = await fetch( `https://api.resend.com/emails/receiving/${emailId}`, { headers: { Authorization: `Bearer ${apiKey}` } } ); if (!res.ok) return null; const data = await res.json(); return { text: data?.text, html: data?.html }; } catch { return null; } } export async function POST(request: Request) { try { const payload: ResendWebhookPayload = await request.json(); console.log("[PRODUCT Webhook]", payload.type, payload.data.email_id); if (payload.type !== "email.received") { return NextResponse.json({ success: true, message: "ignored" }); } const { data } = payload; // IMPORTANT: Only process emails addressed to @DOMAIN (shared Resend account) const isForUs = data.to.some((addr) => addr.endsWith("@DOMAIN")); if (!isForUs) { return NextResponse.json({ success: true, message: "not for DOMAIN" }); } const content = await fetchInboundContent(data.email_id); const sql = neon(process.env.DATABASE_URL!); await sql` INSERT INTO {product}_emails (resend_id, direction, from_email, to_email, subject, body_text, body_html, status) VALUES (${data.email_id}, 'inbound', ${data.from}, ${data.to[0] || ""}, ${data.subject || ""}, ${content?.text || data.text || null}, ${content?.html || data.html || null}, 'received') `; // Forward to inbox const apiKey = process.env.RESEND_API_KEY; if (apiKey) { await fetch("https://api.resend.com/emails", { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ from: "PRODUCT Inbound <matt@DOMAIN>", to: "your-email@domain.com", subject: `[PRODUCT Inbound] ${data.subject || "(no subject)"}`, text: `From: ${data.from}\nTo: ${data.to.join(", ")}\n\n${content?.text || data.text || "(no body)"}`, }), }); } return NextResponse.json({ success: true }); } catch (error) { console.error("[PRODUCT Webhook] Error:", error); return NextResponse.json({ error: "Internal error" }, { status: 500 }); } } export async function GET() { return NextResponse.json({ status: "ok" }); } ``` **Replace** `PRODUCT`, `DOMAIN`, and `{product}` with actual values. #### 4e. Register Webhook with Resend After deploying (step 5), register the webhook: ```bash curl -X POST "https://api.resend.com/webhooks" \ -H "Authorization: Bearer $RESEND_API_KEY" \ -H "Content-Type: application/json" \ -d '{"endpoint": "https://DOMAIN/api/webhooks/resend", "events": ["email.received"]}' ``` #### 4f. Create Waitlist/Audience 1. In Resend > Audience > create "{Product} Waitlist" 2. Copy the audience ID 3. Create `src/app/api/waitlist/route.ts`: ```tsx import { NextResponse } from "next/server"; import { neon } from "@neondatabase/serverless"; export async function POST(req: Request) { try { const { email } = await req.json(); if (!email || !email.includes("@")) return NextResponse.json({ error: "Invalid email" }, { status: 400 }); const RESEND_API_KEY = process.env.RESEND_API_KEY; const RESEND_AUDIENCE_ID = process.env.RESEND_AUDIENCE_ID; if (!RESEND_API_KEY || !RESEND_AUDIENCE_ID) return NextResponse.json({ error: "Server config error" }, { status: 500 }); // Add contact to audience const audienceRes = await fetch( `https://api.resend.com/audiences/${RESEND_AUDIENCE_ID}/contacts`, { method: "POST", headers: { Authorization: `Bearer ${RESEND_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ email, unsubscribed: false }), } ); if (!audienceRes.ok) return NextResponse.json({ error: "Failed to subscribe" }, { status: 500 }); // Send welcome email const emailRes = await fetch("https://api.resend.com/emails", { method: "POST", headers: { Authorization: `Bearer ${RESEND_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ from: "Matt <matt@DOMAIN>", to: [email], subject: "You're in — PRODUCT access request received", html: `<!-- Customize welcome email HTML here -->`, }), }); // Log outbound email to DB try { const emailData = await emailRes.json().catch(() => null); const sql = neon(process.env.DATABASE_URL!); await sql` INSERT INTO {product}_emails (resend_id, direction, from_email, to_email, subject, status)
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub