⚠️ Disclaimer: This project is for educational and demonstration purposes only.
Nexatech Mail Platform is a school-oriented mailbox system that combines:
- a public university website served as static pages from the repository root
- a Next.js 15 mailbox and admin portal under
mail-app/ - Supabase for authentication and data storage
- a Cloudflare Email Worker for inbound mail ingestion
Students sign in from the public website via a login modal and are redirected into the /mail application to read messages and change passwords. Administrators can provision and manage mailbox accounts from the admin area.
Live demo: nexatech.edu.kg
This repository has two main parts:
-
Root static website
index.html,programs.html,admissions.html,campus.html,faculty.html,news.html,contact.htmlcss/style.cssjs/student-login.jsimages/
-
mail-app/Next.js application- student inbox and password management
- admin login, dashboard, mailbox management, and retention cleanup
- Supabase-backed authentication and mailbox storage
- Cloudflare Email Worker and operational scripts
mail-app/
├── app/ # Next.js App Router pages
│ ├── layout.tsx
│ ├── login/page.tsx # Student login page
│ ├── inbox/page.tsx # Inbox list (protected)
│ ├── inbox/[messageId]/page.tsx # Message detail (protected)
│ └── settings/password/page.tsx # Password change (protected)
├── components/
│ ├── ui/ # shadcn/ui primitives
│ ├── mail-layout.tsx # Sidebar layout for authenticated pages
│ ├── inbox-list.tsx # Inbox message list
│ └── message-view.tsx # Message detail view
├── lib/
│ ├── supabase/client.ts # Browser Supabase client
│ ├── supabase/server.ts # Server Supabase client
│ ├── auth/require-session.ts # Auth guard helper
│ ├── mail/sanitize-message.ts # Email HTML sanitizer
│ └── utils.ts # cn() utility
├── scripts/
│ ├── create-student-account.ts # Admin: create single student
│ └── import-students-csv.ts # Admin: bulk import from CSV
├── supabase/migrations/ # DB schema + reliability migrations
├── worker/
│ ├── email-ingest.ts # Cloudflare Email Worker + scheduled replay/cleanup
│ └── wrangler.toml # Worker configuration + cron triggers
├── middleware.ts # Next.js auth middleware
└── .env.example # Environment variable template
- Sign in with administrator-provisioned credentials
- View inbox messages
- Open message details
- Change password
- Admin login flow
- Dashboard metrics for mailbox health and recent activity
- Create mailbox accounts
- List/search mailboxes
- Suspend/reactivate mailboxes
- Reset mailbox passwords
- Delete mailbox accounts
- Run manual retention cleanup
- Cloudflare Email Routing → Worker → Supabase pipeline
- Duplicate protection for replayed emails
- Retry queue for transient delivery failures
- Retention cleanup cron jobs
- Plain-text storage/display strategy for HTML-only emails
- Frontend / App: Next.js 15, React 19, Tailwind CSS 4
- Auth + Database: Supabase
- Inbound email: Cloudflare Email Routing + Cloudflare Worker
- Testing: Vitest, Playwright, k6
- Deployment: Vercel for the web app, Cloudflare for email/DNS/worker
Most app development happens inside mail-app/.
- Node.js 18+
- npm
- A Supabase project
- A Cloudflare account if you want to test the worker deployment
cd mail-app
npm installCreate a local env file from the example:
cp .env.example .env.localRequired variables in mail-app/.env.example:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEYMAIL_DOMAINSUPABASE_URL
npm run devBecause the app uses basePath: "/mail", the mailbox routes are served under:
http://localhost:3000/mail/loginhttp://localhost:3000/mail/inboxhttp://localhost:3000/mail/admin/login
Run these from mail-app/.
npm run build
npm run startnpm run lintnpm testRun one test file:
npm test -- app/api/auth/login/route.test.tsnpx playwright testRun one spec:
npx playwright test ./tests/e2e/health.spec.tsNote: Playwright is configured to target https://www.nexatech.edu.kg, not localhost, so review before running production-facing E2E tests.
npm run create-student -- <emailPrefix> <password> <studentId>
npm run import-csv -- <csvFile>
npm run migrate-mail-domain -- --dry-run
npm run migrate-mail-domain
npm run export-load-test-fixtures
npm run send-load-test-mailsnpm run load:web:health
npm run load:web:login
npm run load:web:inbox
npm run load:web:message-detail
npm run load:web:admin-dashboard
npm run load:web:prod-mixedA production deployment has three major pieces:
- Supabase project for auth + mailbox data
- Vercel deployment for the website/app
- Cloudflare Email Routing + Worker for inbound mail delivery
- Create a new Supabase project.
- Run the SQL migrations in
mail-app/supabase/migrations/. Recommended order:001_initial_schema.sql002_login_rate_limits.sql002_mail_message_idempotency.sql003_reliability_foundation.sql
- In Supabase Auth settings, disable public self-signup if you want the mailbox system to stay admin-provisioned only.
- Collect these values from Supabase:
- Project URL
- anon key
- service role key
These values are used by the Next app and operational scripts.
The mail-app/vercel.json file rewrites top-level website routes to the static assets served under /mail/..., allowing the public site and mailbox app to live together in one deployment.
- Push this repository to GitHub.
- Import the repository into Vercel.
- Set the project root to
mail-app/if required by your Vercel setup. - Add the required environment variables in Vercel:
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Public Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Public anon key |
SUPABASE_SERVICE_ROLE_KEY |
Server-side admin access |
SUPABASE_URL |
Supabase project URL for server/admin code |
MAIL_DOMAIN |
Mail domain, e.g. nexatech.edu.kg |
- Add your production domain, for example
www.nexatech.edu.kg. - Deploy.
- The app uses
basePath: "/mail"inmail-app/next.config.ts. mail-app/middleware.tsenforces the canonical host and protects mailbox/admin routes.- The public website files are also duplicated in
mail-app/public/so the Vercel deployment can serve them through rewrites.
- Move the mail domain DNS to Cloudflare if it is not already there.
- Add the required MX records for Cloudflare Email Routing.
- Enable Email Routing in the Cloudflare dashboard.
- Route inbound addresses for your school domain to the Worker you will deploy in the next step.
If you want a catch-all mailbox pipeline, configure your Email Routing rules accordingly.
The worker lives in mail-app/worker/ and is responsible for:
- parsing inbound email
- validating recipient/domain
- storing mail in Supabase
- recording delivery events/failures
- replaying transient failures on cron
- running retention cleanup
cd mail-app/worker
wrangler login
wrangler deploy --env productionSet the service role key in Cloudflare:
wrangler secret put SUPABASE_SERVICE_KEY --env productionEdit mail-app/worker/wrangler.toml and set:
MAIL_DOMAINSUPABASE_URL
The cron triggers are already defined:
7 * * * *— replay transient ingestion failures17 3 * * *— clean up retained data older than 30 days
- The worker rejects mail for non-matching domains, unknown recipients, and suspended mailboxes.
- Transient account lookup and storage failures are persisted in
mail_ingestion_failuresfor scheduled replay. - Replayed deliveries with the same recipient and resolved message identifier are classified as
duplicateinstead of inserting a second message. - Messages without a
Message-IDreceive a deterministic synthetic identifier before storage so replayed deliveries still deduplicate. - HTML-only messages are stored as plain text fallback so the inbox stays text-only.
- Daily retention cleanup removes
mail_messages,mail_ingestion_events,mail_ingestion_failures, andmail_job_runsentries older than 30 days.
After Supabase and the app are live, create student mailboxes from mail-app/.
SUPABASE_URL=... SUPABASE_SERVICE_ROLE_KEY=... \
npm run create-student -- student001 SecurePass1! S-2025-001SUPABASE_URL=... SUPABASE_SERVICE_ROLE_KEY=... \
npm run import-csv -- students.csvCSV format:
email_prefix,password,student_id
student001,SecurePass1!,S-2025-001
student002,SecurePass2!,S-2025-002SUPABASE_URL=... SUPABASE_SERVICE_ROLE_KEY=... MAIL_DOMAIN=nexatech.edu.kg \
npm run migrate-mail-domain -- --dry-runSUPABASE_URL=... SUPABASE_SERVICE_ROLE_KEY=... MAIL_DOMAIN=nexatech.edu.kg \
npm run migrate-mail-domain- Open the public website.
- Click Student Login.
- Confirm the login modal appears.
- Sign in with a provisioned student account.
- Confirm redirect into
/mail/inbox.
- Send a test email from an external mailbox to an active student mailbox.
- Confirm it appears in the student inbox.
- Sign in at
/mail/admin/login. - Confirm the dashboard loads.
- Verify mailbox management and retention cleanup actions.
- Check
/mail/api/health - Confirm Supabase connectivity is healthy.
- Students cannot self-register.
- Row Level Security (RLS) isolates mailbox data.
- Server-side admin actions use the service role key only on the backend.
- HTML email is converted to plain text for safe display.
- Student and admin login endpoints include origin checks and rate limiting.
- No outbound mail feature is included.
Use the Supabase SQL editor or psql to inspect recent delivery state.
Recent ingestion events:
select recipient_address, resolved_message_id, stage, status, error_category, error_message, created_at
from mail_ingestion_events
order by created_at desc
limit 50;Pending or recent replay failures:
select recipient_address, resolved_message_id, stage, status, retry_count, next_retry_at, last_error, created_at
from mail_ingestion_failures
order by created_at desc
limit 50;Recent replay / cleanup job runs:
select job_name, status, metadata_json, error_message, started_at, finished_at
from mail_job_runs
order by started_at desc
limit 20;For agent guidance and development conventions, see CLAUDE.md.
This repository is licensed under the MIT License.