Engineering Note
Full-Stack

Designing a Contact Email Pipeline

Reliability in Form Submissions

10 min read
IntermediateFull-Stack

Introduction

Sending emails in a contact form looks simple. You receive a request, call Nodemailer, and send the message. But in production, this approach quickly breaks down.

Email handling should be treated as a pipeline with validation, processing, and delivery steps. This ensures reliability and scalability as usage grows.

The Problem

A common implementation sends emails directly inside the API request.


await transporter.sendMail(mailOptions);

  • Slow responses due to blocking email sending
  • Failures directly impact user requests
  • No retry mechanism for failed deliveries
  • No protection against spam or abuse

This works in development but fails under real usage.

System Design / Approach

A better approach is to design an email pipeline with clear stages.

  • Validate input data
  • Store or queue the request
  • Process email sending asynchronously
  • Handle failures and retries

This decouples user requests from email delivery.

Implementation

Step 1: Setup Nodemailer


import nodemailer from "nodemailer";

const transporter = nodemailer.createTransport({
  service: "gmail",
  auth: {
    user: process.env.EMAIL,
    pass: process.env.PASSWORD
  }
});

This configures the email transport.

Step 2: Validate Input


if (!email || !message) {
  throw new Error("Invalid input");
}

Validation prevents bad data and spam.

Step 3: Send Email


await transporter.sendMail({
  from: email,
  to: "your@email.com",
  subject: "Contact Form",
  text: message
});

Basic email sending using Nodemailer.

Step 4: Decouple with Queue

Instead of sending emails directly, push them to a queue.


await queue.add("send-email", { email, message });

This improves reliability and scalability.

Trade-offs

Approach Benefit Cost
Direct sending Simple Blocking and unreliable
Queued pipeline Reliable and scalable More infrastructure

Real-World Impact

  • Faster API response times
  • Improved email delivery success
  • Better protection against spam
  • More reliable communication system

Key Takeaways

Email sending should be treated as a pipeline, not a simple function call

Decoupling email logic from request handling improves reliability

Validation and rate limiting are critical to prevent abuse

Retries and fallback strategies improve delivery success

Monitoring email failures is essential for production systems

Future Improvements

Move email sending to background jobs using queues

Add retry mechanisms with exponential backoff

Implement rate limiting to prevent spam

Track delivery status using logs or external providers

Add email templates and structured formatting

Designing a Contact Email Pipeline | Tushar Kanti Dey