This project started as a fun experiment but has grown into something more. Right now, it's a simple mail server for sending and receiving emails. Future updates will add:
- Mail Exchanger (IMAP) – Use Gmail, Outlook, Yahoo, or Hotmail to send and receive emails.
- Extra Features – SMTP relay, IP/domain whitelisting & blacklisting.
- AI Email Optimization – Smart suggestions to improve email performance.
- Security & Anti-Spam – AI-driven spam filtering and fraud detection.
- User Controls – Rate limits, storage alerts, email forwarding, and aliases.
- Calendar Integration – Works with Google Meet, Teams, and Cal.com.
- Developer API – Easy-to-use API for automating emails in your apps.
┌─────────────────────────────────────┐
│ Incoming (Port 25) │
│ IncomingServer → IncomingHandler │
│ ├─ DNS checks (MX, SPF, DMARC) │
│ ├─ DKIM / SPF / DMARC auth │
│ ├─ ARC seal (for forwarding) │
│ ├─ Custom header injection │
│ └─ PGP encryption → Store │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Outgoing (Port 587) │
│ OutgoingServer → OutgoingHandler │
│ ├─ SMTP AUTH (LOGIN) │
│ ├─ Parse email (mailparser) │
│ ├─ Deduplicate recipients │
│ └─ NodeMailerMTA.sendMail() │
│ ├─ streamTransport (RFC5322)│
│ ├─ DKIM signing │
│ ├─ DSN support │
│ ├─ DNS-based MX delivery │
│ └─ Parallel domain delivery │
└─────────────────────────────────────┘
| Component | File | Purpose |
|---|---|---|
| IncomingMailHandler | src/services/IncomingMailHandler.ts |
Handles MAIL FROM, RCPT TO, and DATA for inbound mail. Runs DKIM/SPF/DMARC checks in parallel, applies ARC sealing, and encrypts with PGP before storage. |
| OutgoingMailHandler | src/services/OutgoingMailHandler.ts |
Handles authentication, parses outbound mail, extracts recipients from headers (To/Cc/Bcc), and delivers via the MTA with DKIM and DSN. |
| NodeMailerMTA | src/server/mta/node-mta.ts |
Core mail transfer agent. Composes RFC5322 messages via streamTransport, signs with DKIM, resolves MX records, and delivers to all domains in parallel over single TCP connections per domain. |
| MailAuth | src/server/config/mailauth.ts |
DKIM verification, SPF checks, DMARC policy evaluation, MTA-STS validation, and ARC message sealing. |
| PGPService | src/server/config/encryption/PGPService.ts |
OpenPGP key generation, message encryption, and decryption for at-rest email storage. |
| DNSChecker | src/server/config/DnsChecker.ts |
DNS record resolution (MX, SPF, DKIM, DMARC, TXT), IP resolution, and TCP port connectivity checks. |
| RFC5322MailComposer | src/server/config/mail.composer.ts |
RFC5322-compliant header generation and MIME message composition. |
The NodeMailerMTA class provides two delivery modes:
import { NodeMailerMTA } from "./server/mta/node-mta";
const mta = new NodeMailerMTA();
const results = await mta.sendMail({
from: "[email protected]",
to: ["[email protected]", "[email protected]", "[email protected]"],
subject: "Hello",
html: "<p>Hello World</p>",
// DKIM signing (recommended, prevents rejection)
dkim: {
domainName: "yourdomain.com",
keySelector: "default",
privateKey: process.env.DKIM_PRIVATE_KEY,
},
// DSN (Delivery Status Notification)
dsn: {
notify: ["FAILURE", "DELAY"],
returnHeaders: true, // HDRS only (lighter than FULL)
},
});
// results is DeliveryResult[] — one entry per recipient
for (const r of results) {
console.log(`${r.recipient}: ${r.success ? "delivered" : r.error}`);
// r.dsn.status, r.dsn.action, r.dsn.remoteMta, r.dsn.diagnosticCode
}How it works:
- Composes an RFC5322 message once using nodemailer's
streamTransportwith DKIM signing - Groups recipients by domain
- Resolves MX records for all domains in parallel (with TTL-based caching)
- Probes all ports (25, 587, 465) in parallel per MX host
- Delivers to all domains in parallel, each using a single TCP connection
const results = await mta.sendViaRelay("smtp.relay.com", 587, {
from: "[email protected]",
to: ["[email protected]"],
subject: "Via relay",
text: "Relayed message",
auth: { user: "relay_user", pass: "relay_pass" },
dkim: { domainName: "yourdomain.com", keySelector: "default", privateKey: "..." },
dsn: { notify: ["SUCCESS", "FAILURE"] },
});When an email arrives on port 25:
- MAIL FROM — Validates sender address format, rejects
.temp@addresses - RCPT TO — Resolves sender domain DNS (MX, SPF, DMARC, TXT), rejects if MX or SPF records missing
- DATA — Full email processing:
- Parses raw email with
mailparser - Extracts sender from parsed headers (falls back to envelope)
- Runs DKIM, SPF, DMARC checks in parallel on the original unmodified message
- Rejects if DMARC policy is
rejectand authentication fails - Attaches custom
X-AE-*headers after authentication (preserves DKIM signatures) - Seals with ARC headers (for forwarding scenarios)
- Encrypts the final message with PGP for at-rest storage
- Parses raw email with
When a client submits mail on port 587:
- AUTH — LOGIN authentication required (XOAUTH2 rejected)
- MAIL FROM — Validates sender, checks for relay
- RCPT TO — Logs recipient info
- DATA — Full email processing:
- Parses raw email to extract To/Cc/Bcc recipients
- Deduplicates all recipients, falls back to envelope RCPT TO
- Delivers via
NodeMailerMTA.sendMail()with DKIM signing and DSN - Logs partial delivery failures
# Server
INCOMING_MAIL_HOST=mx.yourdomain.com
OUTGOING_MAIL_HOST=smtp.yourdomain.com
MAIL_SERVER_IP=1.2.3.4
MAX_EMAILS_PER_MINUTE=5
# TLS
TLS_PRIVATE_KEY_PATH="/etc/letsencrypt/live/<OUTGOING_MAIL_HOST>/privkey.pem"
TLS_CERTFICATE_PATH="/etc/letsencrypt/live/<OUTGOING_MAIL_HOST>/fullchain.pem"
# DKIM (for outgoing mail signing)
DKIM_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
DKIM_SELECTOR="default"
# PGP (for incoming mail at-rest encryption)
PGP_PRIVATE_KEY="-----BEGIN PGP PRIVATE KEY BLOCK-----\n..."
PGP_PUBLIC_KEY="-----BEGIN PGP PUBLIC KEY BLOCK-----\n..."
PGP_REVOCATION_CERTIFICATE="-----BEGIN PGP PUBLIC KEY BLOCK-----\n..."
PGP_PASSPHRASE="your_secure_passphrase"-
A registered domain name
-
A server with a static IP address
-
SSL/TLS certificate for secure email transmission
-
Configure your server with a static IP address
-
Ensure your firewall allows SMTP traffic (ports 25, 465, and 587)
-
Set up reverse DNS (PTR record) for your mail server IP (- Most Important)
Use Any Package Manager, I'm just using bun, to install check out bun.sh
-
Install Dependencies First (Recommended)
bun install -
Build the Applications
bun run build, dont usebun build -
Start with command
bun run dev(Development) andbun run startwith root permissions (Incase not able to start then usesudo node ./build/main.js)
You will find two server file config (Incoming and Outgoing) and 2 server listening at 25 and 587.
We can do it one, but we have to manage incoming and outgoing in same file, which is a mess, complicates the code. Moreover you have full controll to run multiple diffrente mail server like mx.domain.com, mx2.domain.com etc and for sending {smtp,mail}.domain.com (which ever is suitable).
Please Setup PTR(Reverse Lookup) and MAIL_HOST, MAIL_SERVER_IP first
-
bash chmod +x run.sh -
bash ./run.sh2 . -
Go to src -> start.ts
-
Open and replace
your_domainwith your domain name
const records = new DNSRecordGenerator("cirrusmail.cloud").generateAllRecords();and Run this File, Records will create in records.json File in root directory.
- A Record
- Create mail.yourdomain.com pointing to your server IP
- Ensure the hostname matches SSL certificate
2 .PTR Record
- Contact your hosting provider to set up reverse DNS
- Should match your mail server's hostname
Share your Mailserver hostname, who ever wants to use your server and tell them to create MX Record pointing your server.
- SPF Record (TXT)
- Add record: v=spf1 ip4:YOUR_IP mx -all
- Prevents email spoofing
- Specifies authorized IPs/servers
- DKIM Record
- Generate DKIM keys
- Add public key to DNS as TXT record
- Format: default._domainkey.yourdomain.com
- DMARC Record
- Add TXT record: _dmarc.yourdomain.com
- Define policy for failed authentications
- Set up reporting (optional)
Port 25 is used for receiving mails and outbound traffice to send mail from your server to another mail server
Port 587 is used for to connection to your mail server and create transport which send mail and process the mail , how it is going to deliver via Direct or Relay
Mail Server SSL Certificate Setup
Simple guide to secure your mail server with Let's Encrypt/ZeroSSL certificates using Certbot. This setup enables encrypted SMTP connections and works with any transport method (relay or direct delivery).
Linux server with root access
Domain with DNS A record pointing to your server
Port 80 temporarily available for verification
Install Certbot:
sudo apt update
sudo apt install certbot
Generate certificate:
sudo certbot certonly --standalone --preferred-challenges http -d mail.domain.com
Certificate locations:
Certificate: /etc/letsencrypt/live/mail.domain.com/fullchain.pem
Private key: /etc/letsencrypt/live/mail.domain.com/privkey.pem
# Server
INCOMING_MAIL_HOST=mx.yourdomain.com
OUTGOING_MAIL_HOST=smtp.yourdomain.com
MAIL_SERVER_IP=1.2.3.4
MAX_EMAILS_PER_MINUTE=5
# TLS
TLS_PRIVATE_KEY_PATH="/etc/letsencrypt/live/<OUTGOING_MAIL_HOST>/privkey.pem"
TLS_CERTFICATE_PATH="/etc/letsencrypt/live/<OUTGOING_MAIL_HOST>/fullchain.pem"
# DKIM (for outgoing mail signing)
DKIM_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
DKIM_SELECTOR="default"
# PGP (for incoming mail at-rest encryption)
PGP_PRIVATE_KEY="-----BEGIN PGP PRIVATE KEY BLOCK-----\n..."
PGP_PUBLIC_KEY="-----BEGIN PGP PUBLIC KEY BLOCK-----\n..."
PGP_REVOCATION_CERTIFICATE="-----BEGIN PGP PUBLIC KEY BLOCK-----\n..."
PGP_PASSPHRASE="your_secure_passphrase"- Run mail server tests:
- Verify all DNS records
- Test SMTP authentication
- Check TLS encryption
- Verify reverse DNS
- Test sending/receiving
Go and Test Your Mail Server here , it should be like in given Image https://mxtoolbox.com/diagnostic.aspx

- Go to the Google Cloud Console.
- Create a new project or select an existing one.
- Enable the Google Calendar API for the project.
- Create OAuth 2.0 credentials or a service account for authentication.
- Download the credentials JSON file.
npm install googleapis nodemailer ics- Google Meet/Calender
- Cal.com
- Zoho Calender
- Zoom Meetings
- https://shadcn-cal-com.vercel.app/?date=2025-03-02
- https://github.com/Mina-Massoud/next-ui-full-calendar
- https://github.com/schedule-x/schedule-x
- https://synergy-platform.vercel.app/calendar
- https://github.com/charlietlamb/calendar
- https://github.com/list-jonas/shadcn-ui-big-calendar
src/
├── main.ts # Entry point
├── start.ts # DNS record generation
├── interfaces/ # TypeScript interfaces
│ ├── dns.interface.ts # DNS record types
│ ├── domain.interface.ts # Domain types
│ ├── mail.interface.ts # Mail data DTOs
│ └── openpgp.interface.ts # PGP key types
├── lib/
│ ├── helpers/index.ts # Email extraction, utilities
│ ├── logs/index.ts # Logging
│ └── types/index.d.ts # SMTP server type extensions
├── server/
│ ├── IncomingServer.ts # Port 25 SMTP server config
│ ├── OutgoingServer.ts # Port 587 SMTP server config
│ ├── config/
│ │ ├── DnsChecker.ts # DNS resolution & connectivity
│ │ ├── DnsRecordGenrator.ts # DNS record generation
│ │ ├── mail.composer.ts # RFC5322 header/MIME composition
│ │ ├── mailauth.ts # DKIM/SPF/DMARC/ARC auth
│ │ ├── MailConfig.ts # Email parsing & transport
│ │ ├── SpamFilteration.ts # Spam filtering
│ │ └── encryption/
│ │ ├── PGPAdapter.ts # OpenPGP encrypt/decrypt
│ │ ├── PGPFactory.ts # Key generation
│ │ └── PGPService.ts # PGP service facade
│ ├── mta/
│ │ └── node-mta.ts # Mail Transfer Agent (DKIM + DSN)
│ └── plugins/
│ ├── FiltersEngine.ts # Email filtering
│ ├── ImageProxyServer.ts # Image proxy for tracking protection
│ ├── MailRateLimiter.ts # Rate limiting
│ └── TrackingProtection.ts # Tracking pixel removal
└── services/
├── IcsEvents.ts # iCalendar event handling
├── IncomingMailHandler.ts # Inbound mail processing
├── MeetingService.ts # Meeting integration
└── OutgoingMailHandler.ts # Outbound mail processing
Each call to mta.sendMail() or mta.sendViaRelay() returns DeliveryResult[]:
interface DeliveryResult {
success: boolean; // true if accepted by remote MX
recipient: string; // email address
response?: string; // SMTP response string
error?: string; // error message if failed
dsn?: {
status: string; // e.g. "2.0.0", "5.1.2", "4.0.0"
action: "delivered" | "failed" | "delayed" | "relayed" | "expanded";
diagnosticCode?: string; // remote server diagnostic
remoteMta?: string; // MX host that handled delivery
finalRecipient: string; // RFC5321 recipient
};
}| Code | Meaning |
|---|---|
2.0.0 |
Successfully delivered |
4.0.0 |
Temporary failure (retry later) |
5.0.0 |
Permanent failure (rejected) |
5.1.2 |
No MX records / unreachable domain |


