InMailer is a full-stack mail merge platform for sending personalized outreach emails at scale. It combines a React frontend, a Flask backend, Google OAuth for authentication, Gmail API based sending, and a SQLAlchemy-backed database for templates, users, and email activity.
This repository is suitable for two use cases:
- Product use: sign in with Google, create templates, upload a CSV, preview personalized emails, and send them in bulk.
- Engineering/demo use: inspect a clean example of a React + Flask + SQLAlchemy application with OAuth, session auth, file uploads, and email workflow orchestration.
- What the Project Does
- Core Capabilities
- How the Product Works
- Architecture Overview
- Tech Stack
- Repository Structure
- Frontend Overview
- Backend Overview
- Database Model
- API Overview
- Local Development Setup
- Environment Variables
- Deployment Notes
- Presentation Notes
- Known Limitations and Risks
InMailer helps a user send customized emails to many recipients without manually editing each message.
The user workflow is:
- Sign in with Google.
- Create an email template with placeholders such as
${First_Name}or${Company}. - Optionally attach a file such as a resume or portfolio.
- Upload a CSV file containing recipient data.
- Preview how the subject and body render for each contact.
- Send emails through the user’s Gmail account.
- Track sent and failed emails through dashboard statistics and logs.
This makes the project especially useful for:
- Job outreach campaigns
- Sales or partnership outreach
- Event invitations
- Personalized follow-up emails
- Any workflow where message structure is fixed but recipient details vary
- Google OAuth sign-in and cookie-based session authentication
- User-specific template management
- Dynamic variable insertion inside subjects and email bodies
- CSV upload and parsing for contact lists
- Email preview before sending
- Bulk send through Gmail API
- Single test email screen for configuration validation
- Optional file attachment support on templates
- Dashboard statistics for templates and sent emails
- Email activity logging in the database
- User limit control for gated/free access
- Legacy mail merge CLI utilities in
Backend/mail_merge.py
The app uses Google OAuth to authenticate users. When a user signs in:
- the backend starts an OAuth flow using Google credentials
- Google returns an authorization code to the backend callback
- the backend exchanges the code for tokens
- user identity is fetched from Google
- the session stores the authenticated user and OAuth credentials
- the user record is created or updated in the database
The frontend then uses session-based requests with credentials enabled, so protected pages can load user-specific data.
Users create templates from the frontend template editor. A template contains:
- a template name
- a subject line
- a body
- a list of dynamic variables
- an optional attachment
Variables are inserted using placeholder syntax such as:
Hi ${First_Name},
I wanted to reach out regarding opportunities at ${Company}.The backend stores templates per user, so each user only sees and manages their own templates.
In the mail merge screen, the user uploads a CSV file of contacts. The frontend parses the file for quick display, and the backend also processes it for preview generation.
The preview step is important because it lets the user verify:
- the correct template is selected
- variables are being substituted properly
- the resulting subject line looks correct
- the message body reads naturally for each contact
- an attachment is associated if expected
The backend contains flexible variable replacement logic that attempts to handle multiple naming styles, including:
First_Namefirst_nameFirst NamefirstNameCompanycompany_name
That normalization logic reduces CSV formatting friction during demos and real usage.
When the user confirms the bulk send:
- the frontend submits the selected template and contact data
- the backend rebuilds Gmail credentials from the session
- each contact receives a rendered subject and body
- the Gmail API sends the email
- attachment metadata from the template is included when available
- each send attempt is logged as sent or failed
There is also a separate test-email page for sending one email manually before a campaign.
The dashboard presents operational visibility:
- number of templates
- number of emails sent
- orphaned email count
Orphaned emails are emails that still exist in logs even if the original template has been deleted. The code preserves those logs intentionally so history is not lost.
At a high level, the system is split into two layers.
The React frontend is responsible for:
- routing
- authentication state management
- template creation UI
- CSV upload UI
- preview rendering
- triggering email sends
- showing dashboard stats
The Flask backend is responsible for:
- Google OAuth flow
- session management
- template CRUD APIs
- file upload handling
- CSV processing
- variable replacement
- Gmail API integration
- database persistence
- stats and email history endpoints
SQLAlchemy models define and persist:
- users
- templates
- email logs
SQLite is used by default for local development, while PostgreSQL can be used in deployment via DATABASE_URL.
- React 19
- TypeScript
- React Router
- Axios
- Tailwind CSS
- Lucide React icons
- Vercel Analytics
- Python
- Flask
- Flask-CORS
- SQLAlchemy
- Alembic
- python-dotenv
- Google Auth libraries
- Google Gmail API client
- Requests
- Gunicorn
- Google OAuth
- Gmail API
- Render for backend deployment
- Vercel-oriented frontend config patterns
mail_merge_kit/
├── Backend/
│ ├── app_db.py # Main Flask application with routes and business logic
│ ├── mail_merge.py # CLI-style mail merge utilities and template rendering helpers
│ ├── start_server_db.py # Backend startup entrypoint for local dev
│ ├── wsgi.py # WSGI entry for deployment
│ ├── requirements.txt # Python dependencies
│ ├── render.yaml # Render deployment config
│ ├── lib/
│ │ ├── app_factory.py # Flask app configuration and CORS/session setup
│ │ └── oauth.py # OAuth helper logic
│ └── db/
│ ├── config.py # Database engine/session configuration
│ ├── init_db.py # Database initialization helpers
│ ├── models.py # SQLAlchemy models
│ └── services.py # Service layer for users, templates, and email logs
├── frontend/
│ ├── package.json # Frontend dependencies and scripts
│ ├── public/ # Static assets and legal pages
│ └── src/
│ ├── App.tsx # App routes
│ ├── config.ts # Frontend API config
│ ├── contexts/
│ │ └── AuthContext.tsx # Session-aware auth state
│ ├── components/
│ │ └── ProtectedRoute.tsx
│ └── pages/
│ ├── LandingPage.tsx
│ ├── SignIn.tsx
│ ├── SignUp.tsx
│ ├── AuthSuccess.tsx
│ ├── Dashboard.tsx
│ ├── TemplateCreator.tsx
│ ├── MailMerge.tsx
│ └── TestEmail.tsx
├── Templates/ # Template/attachment storage support
├── start_local.bat # Windows launcher for local setup
├── PRIVACY_POLICY.md
├── TERMS_OF_SERVICE.md
└── README.mdThe frontend routes defined in frontend/src/App.tsx include:
/- landing page/signin- sign-in page/signup- sign-up page/auth/success- OAuth success redirect landing/dashboard- protected dashboard/templates- protected template creator/editor/merge- protected mail merge workflow/test-email- protected test email screen
Protected pages are wrapped in ProtectedRoute, which relies on AuthContext.
frontend/src/contexts/AuthContext.tsx:
- stores the current user in React state
- checks
/auth/useron startup - enables
withCredentialson Axios requests - supports sign-out via
/auth/logout
The dashboard provides:
- template count
- sent email count
- orphaned email count
- links to the main actions in the app
The template editor supports:
- create and edit modes
- dynamic variable management
- inline variable insertion into content
- optional attachment upload
- attachment preservation when editing
The mail merge page supports:
- CSV upload
- sample CSV download
- template selection
- preview generation
- template edit/delete shortcuts
- bulk send trigger
The test-email page supports:
- recipient input
- subject/body customization
- optional attachment path
- direct send request to the backend
The main backend implementation lives in Backend/app_db.py. It contains route handlers and much of the application business logic.
- initialize database and migrate legacy templates
- configure OAuth scopes and credentials
- normalize and replace template variables
- handle template CRUD
- process CSVs for preview generation
- send emails via Gmail API
- store and retrieve email logs
- expose health and debug endpoints
- enforce a max free-user limit
Backend/lib/app_factory.py configures:
- Flask app creation
- secret key and session lifetime
- secure cookie settings for production
- local cookie settings for development
- CORS for frontend origins
There are two template-related paths in the backend:
Backend/mail_merge.pycontains generic parsing/rendering helpers based on Pythonstring.TemplateBackend/app_db.pycontains broader replacement logic used by the web app, including normalization and semantic alias matching
This dual approach reflects the project’s evolution from a script-based mail merge utility into a database-backed web application.
The primary send path uses the Gmail API. The code also includes SMTP and optional SendGrid helpers in Backend/mail_merge.py, which are useful as legacy or alternate sending utilities.
The database has three core entities.
idemailnamepassword_hashis_google_useroauth_credentials- timestamps
iduser_idnamesubjectcontentvariablesattachment_pathattachment_name- timestamps
iduser_idtemplate_idrecipient_emailsubjectstatuserror_messagegmail_message_idsent_at
- A user owns many templates.
- A user owns many email logs.
- A template can have many email logs.
- When a template is deleted, related email logs are preserved and
template_idis set toNULL.
That last rule is important because it preserves historical analytics even after cleanup.
The backend exposes both product endpoints and debug/admin-style endpoints.
GET /auth/google- start Google OAuth flowGET /auth/google/callback- Google OAuth callbackGET /auth/user- fetch authenticated user from sessionGET /auth/logout- clear sessionGET /auth/force-reauth- force reauthentication flowGET /auth/validate-credentials- validate stored Gmail credentialsGET /auth/debug-session- inspect session state for debugging
GET /api/templates- list current user templatesPOST /api/templates- create templatePUT /api/templates/<id>- update templateDELETE /api/templates/<id>- delete templatePOST /api/template-attachment- handle attachment-specific upload flow
POST /api/mail-merge- generate preview output from template + CSVPOST /api/send-emails- send bulk emailsPOST /api/send-gmail- send a single test email
GET /api/user/stats- user-level email statisticsGET /api/dashboard/stats- dashboard summaryGET /api/user-limit-status- registration capacity infoGET /api/health- application/database health check
GET /api/debug/databasePOST /api/debug-csvGET /api/debug/credentialsGET /api/admin/users
These are useful in development and demos, but some would need stricter access control before production hardening.
- Python 3.10+ recommended
- Node.js 18+ recommended
- npm
- A Google Cloud OAuth client configured for the app
cd Backend
pip install -r requirements.txt
python start_server_db.pyThe backend runs on:
http://localhost:5000
Health check:
http://localhost:5000/api/health
cd frontend
npm install
npm startThe frontend runs on:
http://localhost:3001
From the repository root:
start_local.batThis script:
- installs backend dependencies
- runs a backend local test script
- starts the backend in a new command window
- installs frontend dependencies
- starts the frontend in a new command window
Place a .env file inside Backend/.
Required for the main web app:
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
GOOGLE_REDIRECT_URI=http://localhost:5000/auth/google/callback
FRONTEND_URL=http://localhost:3001
SECRET_KEY=replace_with_a_real_secretDatabase configuration:
DATABASE_URL=sqlite:///inmailer.dbOptional application controls:
FLASK_ENV=development
MAX_FREE_USERS=50Optional SMTP-style settings used by legacy/alternate send helpers:
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USER=your_email@example.com
EMAIL_PASSWORD=your_app_password
EMAIL_USE_SSL=false
EMAIL_USE_STARTTLS=true
SENDGRID_API_KEY=optional
RATE_LIMIT_SECONDS=2.0Place a .env.local file inside frontend/.
REACT_APP_API_URL=http://localhost:5000
REACT_APP_GOOGLE_CLIENT_ID=your_google_client_idThe repository already includes backend deployment hints.
- WSGI entrypoint:
Backend/wsgi.py - Render config:
Backend/render.yaml - Start command:
gunicorn app_db:app --bind 0.0.0.0:$PORT --workers 1 --timeout 120The frontend is configured in a way that fits deployment behind a public API URL such as:
https://inmailer.onrender.comfor backend- a Vercel-hosted frontend origin
Production CORS and cookie behavior are handled in the Flask app factory.
If you need to explain the project in a meeting, this is a strong structure to use.
"Sending personalized emails manually is slow and error-prone. InMailer solves that by letting users define one template and automatically personalize it for many recipients from CSV data."
- saves time on repetitive outreach
- keeps personalization at scale
- centralizes templates and send history
- allows users to review output before sending
- uses Google authentication and Gmail sending instead of asking users to trust a separate mail server
- full-stack architecture with React and Flask
- secure Google OAuth login
- session-based protected routes
- SQLAlchemy persistence layer
- attachment-aware email templates
- preview-before-send workflow
- Gmail API integration instead of only SMTP
- logging and dashboard analytics
- Sign in with Google.
- Open the dashboard.
- Create a template with variables like
${First_Name}and${Company}. - Attach a sample file.
- Upload a CSV.
- Generate previews to show real personalization.
- Send a test email or a bulk set.
- Return to the dashboard and show updated stats.
- clear separation between frontend, backend, and database layers
- reusable service layer in
Backend/db/services.py - environment-driven configuration
- deployment entrypoints already present
- health and debug endpoints for troubleshooting
- preserved audit trail through
EmailLog
These are worth acknowledging if asked during the meeting.
Backend/app_db.pyis large and centralizes many responsibilities; future refactoring into blueprints/services would improve maintainability.- Several debug/admin endpoints appear intended for development and would need stronger access control for production.
- Some frontend text and console output suggest the project is still in active refinement rather than fully product-polished.
- CSV parsing on the frontend is basic and may not handle every quoting/escaping edge case.
- Gmail/API credential handling is functional, but OAuth/session behavior is an area that typically needs careful production testing.
- Attachment handling and file storage strategy may need hardening depending on deployment scale.
InMailer is a practical, end-to-end personalized email automation platform. From a presentation perspective, its strength is that it is not just a UI prototype: it includes real authentication, persistent storage, template management, CSV-driven personalization, Gmail-based sending, and measurable activity tracking.
That makes it a strong project to present both as a usable product and as a full-stack engineering build.