Developing a production-ready contact system means handling form submissions, managing conversation threads, enabling token-based customer replies, and integrating email notifications. From CAPTCHA protection to email templates and admin dashboard integration, creating a complete contact system requires weeks of careful development. Fastack comes with a complete contact system that handles form submissions, admin replies, email notifications, and conversation threading—saving you weeks of development time.
Contact system flow
1. Customer submits contact form
Customer fills out contact form with name, email, and message. CAPTCHA verification protects against spam.
2. Conversation created in database
System creates a conversation record and stores the initial message. Conversation status is set to "new".
3. Admin receives email notification
Admin receives an email notification with the customer's message and a link to the admin dashboard.
4. Admin replies via dashboard
Admin views the conversation in the dashboard and sends a reply. System generates a secure reply token.
5. Customer receives reply email
Customer receives an email with the admin's reply and a secure token-based link to continue the conversation.
6. Customer replies via token link
Customer clicks the token link and can reply directly via email. Conversation thread is maintained in the database.
What you get with Fastack
Fastack includes a complete contact system with admin replies and email notifications:
Contact form handling
Fastack provides a complete contact form API handler:
import { NextResponse } from 'next/server';
import { z } from 'zod';
import { prisma } from '@/lib/db';
import { createApiResponse } from '@saas/utils';
import { createLogger } from '@saas/monitoring/server';
import { auth } from '@/lib/auth-config';
import { verifyTurnstileToken } from '@saas/captcha';
import { sendEmail } from '@saas/email';
import { getContactNotificationEmail } from '../emails';
const contactFormSchema = z.object({
name: z.string().min(1, 'Name is required').max(255, 'Name is too long'),
email: z.string().email('Invalid email address'),
message: z.string().min(1, 'Message is required').max(5000, 'Message is too long'),
turnstileToken: z.string().optional(),
locale: z.string().optional().default('en'),
});
export async function contactHandler(request: Request) {
// ... logging, auth, validation, CAPTCHA verification
const conversation = await prisma.conversation.create({
data: {
status: 'new',
locale: locale || 'en',
messages: {
create: {
name,
email,
message,
userId: session?.user?.id || undefined,
role: userRole,
},
},
},
include: {
messages: {
orderBy: { createdAt: 'asc' },
take: 1,
},
},
});
// ... admin email notification logic
}The contact form handler validates input with Zod, verifies CAPTCHA, creates a conversation with the initial message, and sends email notifications to admins. The conversation is stored with locale information for proper email localization.
Admin reply handling
Fastack provides an admin API handler for replying to contact messages:
import { NextResponse } from 'next/server';
import { z } from 'zod';
import { randomBytes } from 'crypto';
import { prisma } from '@/lib/db';
import { createApiResponse } from '@saas/utils';
import { createLogger } from '@saas/monitoring/server';
import { auth } from '@/lib/auth-config';
import { sendEmail } from '@saas/email';
import { getContactReplyEmail, getConversationThread } from '../../emails';
const replySchema = z.object({
messageId: z.string().min(1, 'Message ID is required'),
subject: z.string().min(1, 'Subject is required').max(255, 'Subject is too long'),
replyMessage: z.string().min(1, 'Reply message is required').max(5000, 'Reply message is too long'),
});
export async function replyToContactMessageHandler(
request: Request,
getTranslations: GetContactReplyTranslations
) {
// ... logging, auth, admin role check, validation
const conversation = contactMessage.conversation;
// ... token generation, reply URL creation, conversation thread fetching
const adminMessage = await prisma.contactMessage.create({
data: {
conversationId: conversation.id,
name: session.user.name || 'Support Team',
email: session.user.email,
message: replyMessage,
userId: session.user.id,
role: 'admin',
},
});
// ... update conversation status and reply token
}The admin reply handler verifies admin permissions, generates a secure reply token, creates the admin message in the database, and sends an email to the customer with the reply and a token-based link to continue the conversation.
Token-based customer replies
Fastack allows customers to reply via secure token links:
import { NextResponse } from 'next/server';
import { z } from 'zod';
import { prisma } from '@/lib/db';
import { createApiResponse } from '@saas/utils';
import { createLogger } from '@saas/monitoring/server';
import { sendEmail } from '@saas/email';
import { getContactNotificationEmail, getConversationThread } from '../emails';
import { verifyTurnstileToken } from '@saas/captcha';
const customerReplySchema = z.object({
token: z.string().min(1, 'Reply token is required'),
message: z.string().min(1, 'Message is required').max(5000, 'Message is too long'),
turnstileToken: z.string().optional(),
});
export async function customerReplyHandler(request: Request) {
// ... logging, validation, token lookup, expiration check, CAPTCHA verification
const newMessage = await prisma.contactMessage.create({
data: {
conversationId: conversation.id,
name: firstMessage.name,
email: firstMessage.email,
message,
userId: firstMessage.userId,
role: userRole,
},
});
// ... admin notification email logic
}The customer reply handler validates the token, checks expiration, verifies CAPTCHA, creates the customer message, and sends an email notification to admins. The conversation thread is maintained in the database with proper message ordering.
Email templates with conversation threading
Fastack includes email templates that display conversation threads:
import { getBaseTemplate } from '@saas/email/templates';
import type { ConversationMessage } from './contact-thread';
export interface ContactReplyEmailData {
recipientName: string;
originalMessage: string;
replyMessage: string;
adminName?: string;
replyUrl?: string;
translations: ContactReplyEmailTranslations;
conversationThread?: ConversationMessage[];
}
export function getContactReplyEmail(data: ContactReplyEmailData): { html: string; text: string; subject: string } {
const { recipientName, originalMessage, replyMessage, adminName, replyUrl, translations, conversationThread } = data;
// ... greeting, signature, conversation formatting logic
const html = getBaseTemplate(
`
<p style="margin: 0 0 16px 0; font-size: 16px; line-height: 1.5; color: #1a1a1a;">
${greeting}
</p>
// ... rest of HTML email content with conversation thread
`,
translations.replySubjectDefault
);
// ... text email content
return { html, text, subject: emailSubject };
}Email templates format conversation threads with proper styling, showing admin and customer messages with timestamps. The thread is displayed in chronological order, making it easy for customers to follow the conversation.
React hook for contact form
Fastack provides a React hook for submitting contact forms:
'use client';
import { useMutation } from '@tanstack/react-query';
interface ContactFormInput {
name: string;
email: string;
message: string;
turnstileToken?: string;
locale?: string;
}
interface UseContactOptions {
onSuccess?: () => void;
onError?: (error: Error) => void;
}
export function useContact(options: UseContactOptions = {}) {
const { onSuccess, onError } = options;
return useMutation({
mutationFn: async (data: ContactFormInput) => {
const response = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.error || result.message || 'Failed to send message');
}
return result;
},
onSuccess: () => {
onSuccess?.();
},
onError: (error: Error) => {
onError?.(error);
},
});
}The useContact hook provides a simple API for submitting contact forms with React Query integration, handling loading states, errors, and success callbacks.
Time saved: 25+ hours
Building a complete contact system from scratch typically requires:
- Implementing contact form API (3-4 hours)
- Creating conversation threading system (4-5 hours)
- Building admin reply functionality (4-5 hours)
- Implementing token-based customer replies (3-4 hours)
- Creating email templates (3-4 hours)
- Integrating CAPTCHA protection (2-3 hours)
- Building React hooks for forms (2-3 hours)
- Testing contact system (3-4 hours)
Total: 24-32 hours of development time that you save with Fastack.
Best practices included
Fastack follows contact system best practices:
- CAPTCHA protection - Prevents spam submissions
- Conversation threading - Maintains message history
- Token-based replies - Secure customer reply links
- Email notifications - Admin and customer notifications
- Locale support - Multi-language email templates
Conclusion
A complete contact system is essential for SaaS applications, providing customers with a way to reach support and enabling admins to manage conversations efficiently. Fastack provides a production-ready contact system that saves you weeks of development time while ensuring security, reliability, and excellent user experience.
With Fastack, you get:
- Contact form handling with CAPTCHA protection
- Conversation threading for message history
- Admin reply functionality with secure token generation
- Token-based customer replies via email links
- Email templates with conversation thread display
- React hooks for easy form integration
- Production-ready contact system built-in from day one
Ready to build your SaaS faster?
Get our scalable, production-ready boilerplate to save endless hours of development and setup
