Using Google Sheets as a Wait List Form with Next.js and Resend.
When making any form in a very lean way, we can use google sheets as a way of storing all that data. This may not be the best solution when it comes to handling user data or data you are repetively fetching, there you would want to use a postgreSQL database. But lets say you are storing data that only gets pushed to and never read to like a waitlist or inquiry form, then using google sheets might be the solution for you then follow up with sending an email with the resend api.
If you want to just see the code heres a repo for all about it: https://github.com/lorenzejay/nextjs-gsheets-resend
When To Use Google Sheets
- Only making write requests like a google sheet
Don't use it as a way to store user data
Instead ompt for traditional databases like postgreSQL or mongoDB.
Here's a step by step guide:
1. Scaffold a next.js app
npx create-next-app@latest my-app --typescript --tailwind --eslint
Also install npm i googleapis resend
2. Create a actions.ts file in your /src
directory
'use server'
import { google } from 'googleapis'
import { revalidatePath } from 'next/cache'
import { Resend } from 'resend'
function getCurrentDateTime(): string {
const now = new Date()
const year = now.getFullYear()
const month = (now.getMonth() + 1).toString().padStart(2, '0') // Months are 0-based in JavaScript, so add 1
const day = now.getDate().toString().padStart(2, '0')
const hours = now.getHours().toString().padStart(2, '0')
const minutes = now.getMinutes().toString().padStart(2, '0')
const seconds = now.getSeconds().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
export async function addToContactListAction(_: any, formData: FormData) {
const Name = formData.get('name')
const Email = formData.get('email')
const Company = formData.get('company')
const Mobile_Phone = formData.get('phone')
const Message = formData.get('message')
const Signup_Date = getCurrentDateTime()
if (!Email || !Name || !Company || !Mobile_Phone || !Signup_Date)
throw new Error('Missing form entries')
const private_key = process.env.GOOGLE_PRIVATE_KEY?.replace(/['"]/g, '')
if (!private_key) throw new Error('no key')
try {
const auth = await google.auth.getClient({
projectId: process.env.GOOGLE_PROJECT_ID,
credentials: {
type: 'service_account',
private_key: private_key?.replace(/\\n/g, '\n'),
client_email: process.env.GOOGLE_CLIENT_EMAIL,
client_id: process.env.GOOGLE_CLIENT_ID,
token_url: process.env.GOOGLE_TOKEN_URL,
universe_domain: 'googleapis.com',
},
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
// keyFile: 'path-to-your-service-account-key.json', // Replace with the path to your service account key file
// scopes: 'https://www.googleapis.com/auth/spreadsheets',
})
const sheets = google.sheets({ version: 'v4', auth })
const spreadsheetId = process.env.GOOGLE_SHEETS_ID
// const { data } = req.body
await sheets.spreadsheets.values.append({
spreadsheetId,
range: '[your sheet]',
valueInputOption: 'USER_ENTERED',
requestBody: {
values: [[Email, Name, Company, Mobile_Phone, Message, Signup_Date]], // Data must be an array of arrays (each inner array represents a row)
},
})
const resend = new Resend(process.env.RESEND_API_KEY)
await resend.emails.send({
from: 'hello@yourdomain.com',
to: Email as string,
subject: 'We recieved your contact and will get to you within 24 Hours.',
html: `
<p>Hi, ${Name}.</p>
<p>Thank you for reaching out to us. We appreciate your inquiry and value the opportunity to connect with you.</p>
<p>We’ve received your message and will get back to you within 24 hours.</p>
<br/>
<p>Best Regards,</p>
<p>Lorenze</p>
`,
})
// then notiffy me
await resend.emails.send({
from: 'alert@yourdomain.com',
to: 'lorenze@yourdomain.com',
subject: `New Contact Inquiry from ${Name}`,
html: `
Details:
<b>Name:</b> ${Name}
<br />
<b>Email:</b> ${Email}
<br />
<b>Company:</b> ${Company}
<br />
<b>Phone Number:</b> ${Mobile_Phone}
<br />
<b>Message:</b> ${Message}
`,
})
revalidatePath('/')
return { message: 'Successfully signed up' }
} catch (error: any) {
// errors could be from resend or from googleapis
console.log('contact submit error:', error)
return { message: 'Failed contact' }
}
}
Use your server action in a client component
'use client'
import { useId, useRef, useState } from 'react'
import { addToContactListAction } from '@/app/actions'
import FormSubmitButton from './FormSubmitButton'
const initialState = {
message: null,
}
const ContactForm = () => {
const ref = useRef<HTMLFormElement>(null)
const [state, setState] = useState<{ message: string } | null>()
return (
<div className="lg:order-last">
<form
ref={ref}
action={async (formData) => {
const res = await addToContactListAction(initialState, formData)
setState(res)
ref?.current?.reset()
}}
>
<h2 className="font-display text-base font-semibold text-neutral-950">
Work inquiries
</h2>
<div className="isolate mt-6 -space-y-px rounded-2xl bg-white/50">
<TextInput label="Name" name="name" autoComplete="name" />
<TextInput
label="Email"
type="email"
name="email"
autoComplete="email"
/>
<TextInput
label="Company"
name="company"
autoComplete="organization"
/>
<TextInput label="Phone" type="tel" name="phone" autoComplete="tel" />
<TextInput label="Message" name="message" />
</div>
<FormSubmitButton />
</form>
{state?.message !== null && (
<p className="mt-4 text-center text-green-500">{state?.message}</p>
)}
</div>
)
}
export default ContactForm
Should look like this:
And if its successful you should have a new row on your sheet.
And on resend it should send an email to the email put on
Conclusion
You can use a 2 awesome tools to make a free waitlist service for your products landing page with next.js. Especially with Next.js server actions you can integrate resend and googleapis easily and securely running your calls on the server.
If you want to clone a repo, you can do so here: https://github.com/lorenzejay/nextjs-gsheets-resend