Back to Blog

Building a complete contact system: admin replies and email notifications

Forms
Email
Next.js
TypeScript
Integration
Notifications

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:

packages/contact/api/contact.ts
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:

packages/contact/api/admin/contact-reply.ts
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:

packages/contact/api/contact-reply.ts
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:

packages/contact/emails/contact-reply.ts
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:

packages/contact/hooks/use-contact.ts
'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

Building a complete contact system: admin replies and email notifications - Fastack