NeoMail — a MERN email client
A self-contained MERN email client built as a deeper learning exercise on the auth and state patterns that real production apps use. The interesting parts: JWT in HTTP-only cookies (not localStorage), MongoDB TTL indexes for auto-expiring messages, and Redux Toolkit with normalised entity state instead of an ad-hoc store. Live demo →
Stack
- Frontend: React + Tailwind, Redux Toolkit + RTK Query for server state, Vite build
- Backend: Node.js + Express, JWT in HTTP-only cookies (refresh + access pair), bcrypt for password hashes
- Database: MongoDB Atlas with a TTL index on the
messagescollection'sexpiresAtfield for automatic message expiry - Deployment: Frontend on Vercel, backend on Render, MongoDB Atlas free tier
Why these three decisions matter
1. JWT in HTTP-only cookies, not localStorage
The common React tutorial pattern is to store the JWT in localStorage and attach it to every request as a Bearer token. That makes XSS catastrophic — any injected script can read the token and impersonate the user. NeoMail stores the JWT in an HTTP-only cookie with SameSite=Lax. The cookie is unreadable from JavaScript; the browser attaches it automatically; the only attack surface that remains is CSRF, which is mitigated by SameSite and a separate CSRF token on state-changing routes.
2. MongoDB TTL indexes for auto-expiry
NeoMail messages auto-expire after a configurable interval. Instead of writing a cron job that scans for expired documents, the messages collection has a TTL index on expiresAt. MongoDB's background process deletes documents when the field's time is reached. One index declaration, zero cron, predictable behaviour. The trade-off is that TTL runs every 60 seconds — fine for an email client, not fine for sub-second expiry needs.
3. Redux Toolkit with normalised entities
Threads, messages, and folders are stored as normalised entity tables keyed by ID, not as nested arrays. Selectors compose from these tables. When a message updates, every view that derives from it updates with no manual prop drilling. This is the boring, correct way to use Redux Toolkit and it is the pattern I reach for on every dashboard now.
What it does
- Inbox, sent, drafts, trash with virtualised lists
- Compose with autosave drafts every 5 seconds
- Search across subjects and bodies (Mongo text index)
- Folder management (create, rename, move messages)
- Auto-expire messages after configurable interval
- JWT refresh on token expiry with no user-visible re-login
Live and source
- Live demo: neomail-frontend.vercel.app
- Source: github.com/tgaurav1k (look for the NeoMail repos — frontend and backend are separate)
Related
Want a similar MERN product built? Gauravthakur1k@gmail.com