FeaturesTemplatesShowcaseTownie
AI
BlogDocsPricing
Log inSign up
petermillspaugh
petermillspaughgarden
https://petemillspaugh.com
Public
Like
1
garden
Home
Code
6
jsx
4
sqlite
3
README.md
H
main.ts
send.ts
test.ts
Branches
1
Pull requests
Remixes
1
History
Environment variables
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
README.md
Code
/
README.md
Search
7/16/2025
README.md

Email subscriptions

Handles email subscription signup, verification, and sending.

Steps

  1. Run init.ts to create SQLite tables for subscribers, newsletters, and logs (to record sent newsletters)
  2. Update main.ts for your use case (e.g. confirmation link, email args, form fields, etc.)
  3. Add an HTML form to your frontend that calls /send-verification, or just use / to return a simple HTML form
  4. Add a confirmation page to your frontend that calls /confirm-verification
  5. When you're ready to send email, create a template and individual newsletters

Frontend form

You should have a form that hits the /send-verification API endpoint on submit. Remember to adjust the endpoint URL to that of your fork, otherwise you'll be signing people up for my garden! As a simple alternative, you could use the / handler of this Val, which returns a simple HTML form. Here's a simple example using React:

Create val
const EmailSignupForm = () => { const [name, setName] = useState(""); const [email, setEmail] = useState(""); async function handleSubmit(e) { e.preventDefault(); setName(""); setEmail(""); const formData = new FormData(); formData.append("name", name); formData.append("email", email); await fetch("https://garden.val.run/send-verification", { method: "POST", body: formData, }); } return ( <form onSubmit={handleSubmit}> <label htmlFor="name">First name</label> <input id="name" value={name} onChange={(e) => setName(e.target.value)} type="text" required={true} /> <label htmlFor="email">Email</label> <input id="email" value={email} onChange={(e) => setEmail(e.target.value)} type="email" required={true} /> <button type="submit">Subscribe</button> </form> ); };

You can see a full example on petemillspaugh.com: signup in the footer and code on github.

You can add/remove input fields as you wish, of course (e.g. maybe you don't need a name, or maybe you want a how'd-you-hear-about-us field). Just adjust the SQL and frontend implementation accordingly.

Frontend confirmation page

Create a confirmation page that accepts an email and token as query params and calls the /confirm-verification endpoint. Simple example using React (and Next.js /page directory):

Create val
const EmailConfirmationPage = () => { const router = useRouter(); const { email, token } = router.query; const [isConfirmed, setIsConfirmed] = useState(false); useEffect(() => { async function confirmEmail() { if (!email || !token) return; const response = await fetch(`https://garden.val.run/confirm-verification?email=${email}&token=${token}`, { method: "PUT", }); const { confirmed } = await response.json(); if (confirmed) setIsConfirmed(true); } confirmEmail(); }, [email, token]); if (!isConfirmed) return null; return ( <main> <h1>You’re all set!</h1> </main> ); };

Full example is here and code is here.

As an alternative, you could make /confirm-verification a GET route and have your email confirmation link sent by the first route be https://garden.val.run/confirm-verification?email=${email}&token=${token} (swapping in your namespace). That would be marginally faster, probably, but you'd still need some way to convey confirmation to the user (e.g. add some "You're all set" message to the return). Plus, the route writes to the subscribers table, so a PUT feels more appropriate.

Notes

Sending emails to people other than yourself on Val Town is a paid feature—if you want to stay on the free plan, you could go with a package like nodemailer or @sendgrid/mail, or an API like Resend.

Go to top
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Product
FeaturesPricing
Developers
DocsStatusAPI ExamplesNPM Package Examples
Explore
ShowcaseTemplatesNewest ValsTrending ValsNewsletter
Company
AboutBlogCareersBrandhi@val.town
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.