MERN Mail is a MERN email campaign manager I built in TypeScript for lead generation outreach. Users connect their own SMTP accounts with app passwords, write reusable templates and send through Nodemailer, while every email is written to an audit log. Admins oversee all users and the global log. It runs on React, Node.js and MongoDB with JWT and bcrypt authentication.
The problem
Cold outreach for lead generation is repetitive work done by hand in an ordinary inbox: the same introduction pasted again and again, sent from whichever account happens to be signed in, with no shared record of who was contacted and when. Once more than one person does it, nobody can say with confidence what has already gone out.
Bulk-mail services solve the volume but send from their own infrastructure, which is not what outreach from a person's real mailbox needs. The tool I wanted kept mail going out through the sender's own accounts, made templates reusable, and gave whoever ran the operation a single view of every message sent by every user.
My role
I built MERN Mail across the stack in TypeScript: the React interface with Tailwind CSS and shadcn/ui, the Node.js API, the MongoDB data model, authentication and password reset, the Nodemailer sending pipeline, and the statistics dashboards for users and admins.
Architecture and tech stack
Nodemailer on the user's own SMTP accounts
Sending through each user's SMTP account means the email arrives from a real mailbox the recipient can reply to, and the user's sent folder stays a true record. A user can connect several accounts and choose one per send. Accounts are added with app passwords, so the main account password never enters the app and an app password can be revoked from the provider without touching anything else.
Two roles with different scopes
The access model is deliberately small: a User manages their own SMTP accounts, templates and sent mail, and an Admin can see every user and the global email log. Two levels covered what the product needed, and every API route checks the role from the token rather than trusting what the interface chooses to show. For a three-tier version of the same idea, see the Team Management System, which separates Admin, Manager and Team Member.
An audit log written on every send
Each email the app sends is recorded with the sender and account it went through. Users read their own entries, and admins read all of them. The dashboard statistics are built from the same log, so the charts and the history can never disagree about how much mail went out.
TypeScript on both sides
Templates, accounts, log entries and users pass between React and the API many times in a single session. Writing both ends in TypeScript caught mismatched fields while I was building, before they could reach a send.
Key features
- Multi-account SMTP: connect several sending accounts with app passwords and pick one per email.
- Template management for reusable outreach messages.
- Sending through Nodemailer from the selected account.
- Audit logs for every email, per user and globally for admins.
- Dashboards that chart send statistics.
- Admin and User roles with JWT authentication and bcrypt-hashed passwords.
- Profile management and a crypto-secured forgot-password flow.
Challenges and how I solved them
Holding credentials for someone else's mailbox
- Problem
- To send as a user, the server needs SMTP credentials for that user's account. Asking people for their real email password would be a serious risk for them and an obvious reason not to use the tool.
- Solution
- Accounts are connected with app passwords instead. An app password only works for mail access, is generated by the email provider, and can be revoked there at any moment, so a user stays in control of what the app can do with their mailbox.
A password reset that cannot be guessed
- Problem
- Forgot-password links are a common way into an account. A reset link built from anything predictable, such as a user ID or a timestamp, can be forged by someone who never had access to the inbox.
- Solution
- Reset tokens are generated with Node's crypto module, so the link carries a random value that cannot be derived from anything about the user. Passwords themselves are stored only as bcrypt hashes.
One log, two audiences
- Problem
- Users need to see what they sent, and admins need to see everything. Two separate logs would drift apart, and a single log returned in full to everyone would expose each user's outreach to the others.
- Solution
- There is one log collection, and the API scopes every query by role: a User's request is filtered to their own entries, an Admin's is not. The same scoping feeds the dashboard charts, so each person sees statistics only for the mail they are allowed to see.
Results
MERN Mail turns outreach into a repeatable process: write a template once, send it from the right account, and find every message afterwards in a log that an admin can review across the whole team. It shares its stack with my Transaction Tracker finance app, but the hard parts here were different: credentials for third-party mailboxes, delivery through external SMTP servers, and an audit trail that had to hold up for oversight.
What's next
Ideas for a next version include scheduled sends and per-recipient personalisation fields in templates, which would need to be confirmed before they are described as planned.
