Generate and send professional certificates in minutes β mostly in your browser.
Open-source, privacy-first certificate automation for hackathons, educators, and event organizers.
Upload a template β Add participants (CSV or Google Sheets) β Place fields β Generate PDFs β Download ZIP or send via Gmail.
Live site β’ Features β’ Architecture β’ Getting Started β’ Deployment β’ Roadmap
MailMyCertificate is a full certificate workflow: design on a visual canvas, batch-generate PDFs client-side, then optionally send them through your own Gmail account via Google OAuth.
| Layer | What runs where |
|---|---|
| PDF generation | 100% in the browser (Web Worker + pdf-lib) |
| Participant data | Stored in IndexedDB (Dexie) on your device |
| Gmail send + OAuth | Flask API on Vercel (/api/*) β only when you choose to email |
Production: mailmycertificate.tech
Created by Akshat Thakur, a software developer building web applications and tools that solve real problems.
If you like MailMyCertificate, you might also find these useful:
- SafeExam β Online examination platform
- Calcuzy β Productivity and calculation tool
- Priya Sarv Utthan β Platform for a non-profit organization
Connect on GitHub β’ LinkedIn β’ Twitter/X
| Feature | Description |
|---|---|
| Template upload | JPG/PNG templates with dimension detection |
| CSV import | Up to 400 rows, header detection, preview table |
| Google Sheets import | Paste a public sheet URL and sync participant data |
| Visual field editor | Drag-and-drop fields, zoom, properties panel (size, alignment, color) |
| Undo / redo | Field placement history via zundo (Ctrl+Z / Ctrl+Y) |
| Single-certificate preview | Preview modal before batch generation |
| Batch PDF generation | Web Worker + chunked processing; resume after refresh |
| ZIP download | All completed PDFs in one archive |
| Session recovery | Restore in-progress batches from IndexedDB |
| Feature | Description |
|---|---|
| Gmail OAuth | Connect your Google account (Gmail API gmail.send) |
| Personalized compose | Subject/body with {{column}} variables from CSV |
| Attachments | Sends generated PDF per recipient |
| Send tracker | Live progress, failures list, retry-friendly queue |
| Refresh guard | Warns before leaving during an active send |
| Page | Purpose |
|---|---|
/ |
Landing + marketing |
/guide |
How-to, FAQs, OAuth walkthrough |
/about |
Project story |
/contact |
Contact |
/settings |
Local data management (view / delete sessions) |
/privacy-policy, /terms-of-service |
Legal pages |
Also: SEO metadata, JSON-LD, sitemap, PWA manifest, service worker (static assets), Vercel Analytics.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Browser (Next.js 16) β
β /tool β Wizard (Zustand + Dexie IDB) β Web Worker β pdf-lib β ZIP β
β /email β Gmail composer β fetch('/api/...', credentials: 'include') β
βββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββ
β /api/* (Vercel Services)
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Flask backend (api/index.py) β
β GET /api/auth/login β Google OAuth URL β
β GET /api/auth/callback β Exchange code, session + CSRF β
β GET /api/auth/status β Connected Gmail + csrf_token sync β
β POST /api/send-email β Gmail API send (PDF attachment) β
β POST /api/sheets/import β Google Sheets CSV fetch β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Vercel routing (vercel.json): Next.js at /, Flask at /api via experimentalServices.
Local dev: next.config.ts rewrites /api/* β http://localhost:8000 when NODE_ENV=development.
| Category | Technology | Purpose |
|---|---|---|
| Frontend | Next.js 16, React 19, TypeScript 5 | App Router, UI |
| Styling | Tailwind CSS 4 | Design system |
| State | Zustand + zundo + persist |
Wizard state, undo/redo |
| Storage | Dexie (IndexedDB) | Sessions, templates, PDFs, email queue |
| pdf-lib + Web Worker | Client-side generation | |
| ZIP | JSZip | Batch download |
| CSV | PapaParse | File parsing |
| Backend | Flask, google-auth-oauthlib, Gmail API | OAuth + send + Sheets proxy |
| Deploy | Vercel (Next + Python services) | Production hosting |
| Analytics | @vercel/analytics | Usage metrics |
MailMyCertificate/
βββ api/
β βββ index.py # Flask app (OAuth, send-email, sheets)
β βββ site_config.py # APP_URL, CORS, redirect URI helpers
βββ public/
β βββ logo.png # Brand asset
β βββ icon-192.png, icon-512.png, favicon-48.png
β βββ manifest.json # PWA
β βββ sw.js # Service worker (static cache; skips /api)
βββ src/
β βββ app/ # Routes (/, /tool, /email, /guide, β¦)
β βββ views/ # Page-level views
β βββ components/ # UI, wizard, email, session, seo, guide
β βββ core/
β β βββ db/schema.ts # Dexie tables
β β βββ session/ # Session manager + recovery
β β βββ queue/ # Email queue
β β βββ worker/ # PDF generation worker
β βββ services/ # template, csv, email, sheets, zip, pdf
β βββ store/useAppStore.ts # Zustand + temporal + persist
β βββ config/site.ts # Public URL helpers (frontend)
β βββ lib/ # metadata, SEO, structured data
βββ vercel.json # Next + Flask services
βββ requirements.txt # Python dependencies
βββ next.config.ts
βββ .env.example
- Node.js β₯ 18
- Python β₯ 3.10 (for local Gmail API)
- Google Cloud OAuth client (Web) with Gmail API enabled
git clone https://github.com/akshatthakur22/MailMyCertficate.git
cd MailMyCertficate
npm install
pip install -r requirements.txtCopy .env.example β .env.local and fill in:
NEXT_PUBLIC_APP_URL=http://localhost:3000
APP_URL=http://localhost:3000
FLASK_SECRET_KEY=<random-hex>
GOOGLE_CREDENTIALS_JSON={"web":{...,"redirect_uris":["http://localhost:3000/api/auth/callback"]}}In Google Cloud Console β OAuth client β add:
- Authorized JavaScript origins:
http://localhost:3000 - Authorized redirect URIs:
http://localhost:3000/api/auth/callback
For production, also add https://mailmycertificate.tech and
https://mailmycertificate.tech/api/auth/callback.
Terminal A β Flask API (port 8000):
python api/index.pyTerminal B β Next.js:
npm run devOpen http://localhost:3000. Gmail connect and send only work when both are running.
Alternatively, with the Vercel CLI: vercel dev -L runs frontend + backend together.
| Command | Description |
|---|---|
npm run dev |
Next.js dev server |
npm run build |
Production build |
npm run start |
Start production Next server |
npm run lint |
ESLint |
python api/index.py |
Flask API on :8000 (local) |
- Import the GitHub repo on Vercel.
- Set Production environment variables:
| Variable | Example |
|---|---|
APP_URL |
https://mailmycertificate.tech |
NEXT_PUBLIC_APP_URL |
https://mailmycertificate.tech |
FLASK_SECRET_KEY |
long random string |
GOOGLE_CREDENTIALS_JSON |
full OAuth client JSON (single line) |
- Ensure
vercel.jsonusesexperimentalServices(already in repo). - Redeploy after env changes.
Checklist after deploy
GET /api/auth/loginreturns JSON withauthorization_url(not HTML 500).redirect_uriin that URL matches Google Console exactly.- Connect Gmail on
/email, then send a test message.
- User clicks Connect Gmail β
/api/auth/loginβ Google OAuth. - Callback β
/api/auth/callbackstores tokens in an encrypted Flask session cookie. - Frontend syncs CSRF token from redirect URL +
/api/auth/status. - Each send:
POST /api/send-emailwithcredentials: 'include',X-CSRF-Token, and PDF attachment (multipart).
Tokens stay on the server session; certificate bytes are read from IndexedDB and only sent when you hit Send.
- Gmail OAuth + send via Flask on Vercel
- IndexedDB sessions + recovery
- Google Sheets import
- Undo/redo, preview modal, email send UI
- SEO, PWA, settings / legal pages
- Custom font embedding in PDFs (beyond Helvetica)
- Personalized ZIP filenames from CSV columns
- Stronger CSV validation (duplicates, email column hints)
- Dark mode
- Automated tests (unit + E2E)
- Rate-limit UX for large Gmail batches
- Fork the repo
- Create a branch (
git checkout -b feature/your-feature) - Commit and push
- Open a Pull Request
Issues and PRs are welcome.
MIT License β see LICENSE if present in the repo root.
Built with dedication by Akshat Thakur using:
- pdf-lib Β· PapaParse Β· JSZip
- Zustand Β· Dexie Β· Next.js Β· Flask
- Tailwind CSS Β· TypeScript Β· React
Thanks to the open-source community for tools that make this possible.
Built with β€ by Akshat Thakur
If you're looking to fork, customize, or contribute to MailMyCertificate:
- Check out the /for-developers page (coming soon) for architecture deep-dive and customization guide
- Read CONTRIBUTING.md for development workflow, code style, and PR process
- Review GitHub Issues for bugs and feature requests
- Join the discussion on Discussions or email via GitHub profile
Why fork MailMyCertificate?
- You don't need to build certificate generation + Gmail integration from scratch
- The entire workflow is open and transparent β no black box
- Deploy to your own infrastructure (Vercel, self-hosted, Docker)
- Customize fields, styling, or logic to fit your specific workflow