---
description: Handle user registration with automated welcome emails and email verification using secure tokens.
title: User signup flow
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/email-service/llms.txt  
> Use this file to discover all available pages before exploring further.

# User signup flow

Send welcome and verification emails during user signup with secure token-based email verification.

Last updated Jun 9, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/email-service/examples/email-sending/signup-flow/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

This example demonstrates a complete user registration system with welcome emails and email verification.

Configure the email binding and KV namespaces in your Wrangler configuration file:

```jsonc
{
	"send_email": [{ "name": "EMAIL" }],
	"kv_namespaces": [
		{ "binding": "USERS", "id": "<USERS_KV_NAMESPACE_ID>" },
		{
			"binding": "VERIFICATION_TOKENS",
			"id": "<VERIFICATION_TOKENS_KV_NAMESPACE_ID>",
		},
	],
	"vars": {
		"DOMAIN": "yourdomain.com",
		"COMPANY_NAME": "Acme Inc.",
	},
}
```

```toml
[[send_email]]
name = "EMAIL"

[[kv_namespaces]]
binding = "USERS"
id = "<USERS_KV_NAMESPACE_ID>"

[[kv_namespaces]]
binding = "VERIFICATION_TOKENS"
id = "<VERIFICATION_TOKENS_KV_NAMESPACE_ID>"

[vars]
DOMAIN = "yourdomain.com"
COMPANY_NAME = "Acme Inc."
```

The Worker exposes two routes: `POST /signup` creates a user, stores it in KV, and sends both a welcome email and a verification email containing a tokenized link; `GET /verify` validates that token and marks the user verified. The following code implements both handlers.

```typescript
interface Env {
	EMAIL: SendEmail;
	USERS: KVNamespace;
	VERIFICATION_TOKENS: KVNamespace;
	DOMAIN: string;
	COMPANY_NAME: string;
}

interface User {
	id: string;
	email: string;
	firstName: string;
	verified: boolean;
	createdAt: string;
}

export default {
	async fetch(request: Request, env: Env): Promise<Response> {
		const url = new URL(request.url);

		if (url.pathname === "/signup" && request.method === "POST") {
			return handleSignup(request, env);
		}

		if (url.pathname === "/verify" && request.method === "GET") {
			return handleVerification(request, env);
		}

		return new Response("Not Found", { status: 404 });
	},
};

async function handleSignup(request: Request, env: Env): Promise<Response> {
	const { email, firstName } = await request.json();

	// Validate and check for existing user
	if (!email || !firstName || !isValidEmail(email)) {
		return new Response(JSON.stringify({ error: "Invalid input" }), {
			status: 400,
		});
	}

	if (await env.USERS.get(email)) {
		return new Response(JSON.stringify({ error: "User already exists" }), {
			status: 409,
		});
	}

	// Create user
	const userId = crypto.randomUUID();
	const user: User = {
		id: userId,
		email,
		firstName,
		verified: false,
		createdAt: new Date().toISOString(),
	};

	await env.USERS.put(email, JSON.stringify(user));

	// Generate verification token (expires in 1 hour)
	const verificationToken = crypto.randomUUID();
	await env.VERIFICATION_TOKENS.put(verificationToken, email, {
		expirationTtl: 3600,
	});

	// Send welcome email
	await env.EMAIL.send({
		to: email,
		from: `noreply@${env.DOMAIN}`,
		subject: `Welcome to ${env.COMPANY_NAME}!`,
		html: `
      <h1>Welcome ${firstName}!</h1>
      <p>Thanks for joining ${env.COMPANY_NAME}. Please verify your email to get started.</p>
    `,
	});

	// Send verification email
	const verificationUrl = `https://${env.DOMAIN}/verify?token=${verificationToken}`;
	await env.EMAIL.send({
		to: email,
		from: `noreply@${env.DOMAIN}`,
		subject: "Please verify your email address",
		html: `
      <h1>Verify Your Email</h1>
      <p>Hi ${firstName}! Click the link below to verify your email:</p>
      <a href="https://developers.cloudflare.com/email-service/examples/email-sending/signup-flow/$%7B%3C/span%3E%3Cspan%20class="nb-shiki-140thh">verificationUrl}" style="background: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px;">Verify Email</a>
      <p>This link expires in 1 hour.</p>
    `,
	});

	return new Response(
		JSON.stringify({
			success: true,
			message: "Account created. Please check your email.",
			userId,
		}),
		{ status: 201 },
	);
}

async function handleVerification(
	request: Request,
	env: Env,
): Promise<Response> {
	const url = new URL(request.url);
	const token = url.searchParams.get("token");

	if (!token) {
		return new Response("Missing token", { status: 400 });
	}

	// Verify token
	const userEmail = await env.VERIFICATION_TOKENS.get(token);
	if (!userEmail) {
		return new Response("Invalid or expired token", { status: 400 });
	}

	// Get and update user
	const userData = await env.USERS.get(userEmail);
	if (!userData) {
		return new Response("User not found", { status: 404 });
	}

	const user: User = JSON.parse(userData);
	user.verified = true;

	await env.USERS.put(user.email, JSON.stringify(user));
	await env.VERIFICATION_TOKENS.delete(token);

	return new Response(
		`
    <html>
      <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
        <h1>Email Verified!</h1>
        <p>Welcome ${user.firstName}! Your account is now active.</p>
        <a href="https://developers.cloudflare.com/" style="background: #28a745; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px;">Continue</a>
      </body>
    </html>
  `,
		{
			headers: { "Content-Type": "text/html" },
		},
	);
}

function isValidEmail(email: string): boolean {
	return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
```

## Next steps

* [Magic link authentication](https://developers.cloudflare.com/email-service/examples/email-sending/magic-link/) — passwordless login flow using a similar token pattern.
* [Send method](https://developers.cloudflare.com/email-service/api/send-emails/workers-api/) — full reference for the `send()` method.
* [Deliverability](https://developers.cloudflare.com/email-service/concepts/deliverability/) — keep verification emails out of spam folders.

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/email-service/examples/email-sending/signup-flow/#page","headline":"User signup flow · Cloudflare Email Service docs","description":"Handle user registration with automated welcome emails and email verification using secure tokens.","url":"https://developers.cloudflare.com/email-service/examples/email-sending/signup-flow/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-06-09","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```
