Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ MAIL_HOST=127.0.0.1
MAIL_PORT=1025
MAIL_USER=dev@local.stack
MAIL_PASS=devpassword
IMAP_PORT=1143

# Seed Admin
SUPER_ADMIN_EMAIL=admin@local.stack
Expand Down
212 changes: 59 additions & 153 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,181 +1,87 @@
# Local Mail Stack

📧 **Local Mail Server for Development & Testing**
📧 **End-to-End Local Email Environment for Professional Development**

## One-liner

A fully local email system that supports sending, receiving, storing, and reading emails using real SMTP and IMAP protocols — built for developers to test email flows without external services.

## 🎯 Problem This Solves

Developers usually rely on:

- **Gmail**
- **Third-party tools**

This project:

- Runs 100% locally
- Uses real protocols
- **Gives full control + visibility** over internal mail flow
- **Perfect for testing transactional emails**

## 🧱 Core Stack

| Layer | Tech |
| :---------------- | :------------ |
| **SMTP Server** | `smtp-server` |
| **Email Sending** | `nodemailer` |
| **Email Parsing** | `mailparser` |
| **IMAP Server** | `ImapFlow` |
| **Backend API** | NestJS |
| **Storage** | PostgreSQL |
| **Deployment** | Docker |

## 🧩 High-Level Architecture

```text
┌────────────┐
│ App / API │
└─────┬──────┘
│ SMTP
┌──────────────┐
│ SMTP Server │
└─────┬────────┘
│ raw email
┌──────────────┐
│ Mail Parser │
└─────┬────────┘
│ parsed email
┌──────────────┐
│ Database │
└─────┬────────┘
│ IMAP
┌──────────────┐
│ IMAP Server │
└─────┬────────┘
┌──────────────┐
│ Email Client │
└──────────────┘
```
A self-hosted, professional-grade email system that supports sending, receiving, storing, and managing emails using real SMTP and IMAP protocols — featuring a premium Web UI for a complete "Mini Gmail" experience locally.

## 📊 Database Schema

```mermaid
erDiagram
User ||--o{ Mailbox : "owns"
User ||--o{ RefreshToken : "has"
User ||--o{ UserOtp : "has"
User ||--o| FileInstance : "has profile picture"

Mailbox ||--o{ Email : "contains"

Email ||--o{ EmailRecipient : "has"
Email ||--o{ FileInstance : "has attachments"

EmailRecipient {
string address
string name
enum role
}

Email {
string subject
string bodyText
string bodyHtml
string messageId
int size
datetime date
enum flags
}

Mailbox {
string name
enum type
int uidNext
int uidValidity
}
```
---

## 🔁 Email Flow
## 🎯 The Ultimate Dev Tool

1. **Sending Email**
- App uses `Nodemailer`
- Connects to `localhost:2525`
- Sends email via SMTP
2. **Receiving Email**
- `smtp-server` receives raw email
- Passes stream to `mailparser`
- Extracts: `From` / `To`, `Subject`, `Text` / `HTML`, `Attachments`
3. **Storage**
- Emails stored in DB
- Attachments saved to filesystem
- Mailboxes: `INBOX`, `Sent`, `Drafts`
4. **Reading Email**
- IMAP server exposes mailboxes
- Email clients connect via IMAP
- Read, search, mark read/unread
Stop relying on third-party sandbox services or real Gmail accounts for development. **Local Mail Stack** gives you:

## ✨ Core Features
- **100% Privacy**: No data leaves your machine.
- **Protocol Accuracy**: Uses real SMTP (port 1025) and IMAP (port 1143) protocols.
- **Visual Excellence**: A premium, interactive Web UI to monitor and manage your mail flow.
- **Zero Latency**: Instant delivery for testing transactional flows and bulk notifications.

### SMTP
---

- Accept incoming emails
- Support multiple recipients
- Handle attachments
- Optional SMTP AUTH
## 🧱 Technology Stack

### IMAP
| Layer | Tech |
| :-------------------- | :-------------------------------------------- |
| **SMTP Server** | `smtp-server` (TCP Port 1025) |
| **IMAP Server** | Custom Node.js TCP Implementation (Port 1143) |
| **Web Interface** | Handlebars + Vanilla CSS (Gmail-inspired) |
| **Email Parsing** | `mailparser` (RFC compliant) |
| **Backend Framework** | NestJS |
| **Database & ORM** | PostgreSQL + Prisma |
| **Queue Management** | BullMQ + Redis |

- `INBOX` support
- Fetch emails
- Flags
- Pagination
---

## 🚀 Key Features

### Backend
### 📬 Dual-Protocol Support

- Email persistence
- Mailbox management
- Email persistence
- Mailbox management
- User accounts ([View Auth Flow](./docs/auth-flow.md))
- **SMTP**: Accept connections from any app. Support for attachments, multiple recipients (To/Cc/Bcc), and raw data streams.
- **IMAP**: Connect your favorite mail client (Thunderbird, Apple Mail). Supports folder listing, fetching, and flag (Read/Unread) updates.

## 🚀 Advanced Features
### 🎨 Premium Dev Mailbox (Web UI)

- STARTTLS
- SMTP AUTH
- Multiple users & mailboxes
- IMAP IDLE
- **Real-time Event Triggers**
- Search
- Export `.eml`
- **"Mini Gmail" Experience**: A state-of-the-art interface with folder navigation, search, and bulk actions.
- **Live Composition**: Compose and send emails directly between local accounts via the Web UI.
- **Powerful Search**: Server-side full-text search across subjects, bodies, and recipients.
- **Action Toolbar**: Archive, Delete, and Mark as Read/Unread with one click.
- **Attachment Preview**: Integrated handling and downloading of email attachments.

## 🔒 Security Scope
### 🛠️ Developer-First Architecture

This project is local-only by design:
- **Refactored Module Structure**: Clean separation between `auth` and `web` modules with dedicated services and DTOs.
- **Global Types & Interfaces**: Centralized `@common` layer for shared domain logic.
- **Signed URLs**: Secure access to development mailboxes via cryptographically signed links.

- No public email sending
- No DKIM / SPF / DMARC
- No spam filtering
- No open relay
---

## 🔄 How it Works

1. **Transport**: Your application sends mail via SMTP to `localhost:1025`.
2. **Ingestion**: `SmtpService` parses the raw stream into structured database records.
3. **Organization**: `MailboxSetupWorker` ensures users have standard folders (Inbox, Sent, Trash, etc.).
4. **Access**:
- **API**: Use the `/dev` endpoints to manage mail programmatically.
- **Web**: Access the **Local Mail Stack UI** for a visual overview.
- **IMAP**: Connect an external client to `localhost:1143`.

---

## 🏷️ Resume-Ready Description

> Built a local email server supporting SMTP and IMAP protocols using Node.js. Implemented email parsing, storage, and mailbox access using `Nodemailer`, `smtp-server`, `mailparser`, and `ImapFlow`. Designed for local development and testing of transactional email workflows.
> Engineered a comprehensive local email ecosystem using NestJS, featuring custom-built SMTP and IMAP protocol handlers. Developed a high-performance web interface using Handlebars and Vanilla CSS, enabling professional-grade email testing with functional composition, server-side search, and bulk management. Leveraged PostgreSQL for persistent storage and Redis/BullMQ for background mailbox orchestration.

---

### ✅ Final Check
### 🚀 Quick Start

```bash
# Start the infrastructure (DB, Redis)
sudo make local-up

- `nodemailer` → client
- `smtp-server` → receive emails
- `mailparser` → parse raw emails
- `ImapFlow` → read emails via IMAP
# Start the application
pnpm dev
```

_This is a real mail system, not a mock._
_This is a professional mail system, designed for serious developers._
2 changes: 2 additions & 0 deletions compose.dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ services:
PORT: 3000
ports:
- '3000:3000'
- '1025:1025'
- '143:143'
volumes:
# Mount source code for live reload
- ./src:/usr/src/app/src:cached
Expand Down
2 changes: 2 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ services:
PORT: 3000
ports:
- '3000:3000'
- '1025:1025'
- '143:143'
depends_on:
db:
condition: service_healthy
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"@types/bcrypt": "^6.0.0",
"@types/body-parser": "^1.19.6",
"@types/express": "^5.0.6",
"@types/hbs": "^4.0.5",
"@types/he": "^1.2.3",
"@types/imapflow": "^1.0.189",
"@types/jsonwebtoken": "^9.0.10",
Expand Down
10 changes: 10 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/common/enum/env.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum ENVEnum {
MAIL_PORT = 'MAIL_PORT',
MAIL_USER = 'MAIL_USER',
MAIL_PASS = 'MAIL_PASS',
IMAP_PORT = 'IMAP_PORT',

REDIS_HOST = 'REDIS_HOST',
REDIS_PORT = 'REDIS_PORT',
Expand Down
17 changes: 17 additions & 0 deletions src/common/interface/auth.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Request } from 'express';

export interface JWTPayload {
sub: string;
email: string;
role: string;
}

export interface RequestWithUser extends Request {
user?: JWTPayload;
}

export type TokenPair = {
accessToken: string;
refreshToken: string;
refreshTokenExpiresAt: Date;
};
10 changes: 10 additions & 0 deletions src/common/interface/file.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { FileType } from '@prisma';

export interface MultipleFileOptions {
destinationFolder: string;
prefix: string;
fileType?: FileType;
fileSizeLimit?: number;
maxFileCount?: number;
customMimeTypes?: string[];
}
4 changes: 4 additions & 0 deletions src/common/interface/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './auth.interface';
export * from './file.interface';
export * from './mailbox.interface';
export * from './queue.interface';
17 changes: 17 additions & 0 deletions src/common/interface/mailbox.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export interface MailboxMessage {
id: string;
subject: string;
text: string;
html: string;
date: string;
from: string;
to: string;
isRead: boolean;
attachments?: {
id: string;
filename: string;
url: string;
size: number;
mimeType: string;
}[];
}
26 changes: 26 additions & 0 deletions src/common/interface/queue.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { QueueEventsEnum } from '../enum/queue-events.enum';

export interface MailboxSetupPayload {
userId: string;
email: string;
}

export interface Meta {
performedBy: string; // System or any user
recordType: string; // Prisma model
recordId: string; // Prisma model ID
others?: Record<string, any>; // Additional data
[key: string]: any; // Allow extra fields
}

export interface NotificationPayload {
type: QueueEventsEnum;
title: string;
message: string;
createdAt: Date;
meta: Meta;
}

export interface QueuePayload extends NotificationPayload {
recipients: { id: string }[];
}
1 change: 1 addition & 0 deletions src/common/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './response.type';
Loading