# Edut Secret System — Deployment Spec ## Overview The Edut secret system is an organic waitlist that runs on `edut.ai`. Visitors click the landing page, their email client opens with a pre-filled classified-looking message, they send it, and an auto-reply confirms their designation. No forms, no signups — they email you, you reply. --- ## Architecture ``` Visitor clicks orb on edut.ai ↓ Email client opens: mailto:0217073045482@secret.edut.ai ↓ Person sends email ↓ Mailgun receives on catch-all (*@secret.edut.ai) ↓ Mailgun POSTs to https://api.edut.ai/mailgun/inbound ↓ Webhook parses email, stores in SQLite, sends classified reply ↓ Reply threads under original email in sender's inbox ↓ Copy of inbound + reply also forwarded to j@edut.ai (Gmail archive) ``` --- ## Infrastructure (Already Configured) | Service | Domain | Purpose | |---------|--------|---------| | Google Workspace | edut.ai | Business email (j@edut.ai), OAuth | | Mailgun (Edut account) | secret.edut.ai | Catch-all inbound, API sending | | Cloudflare | api.edut.ai → 89.167.8.148 | Webhook endpoint | | Hetzner | 89.167.8.148 | Server running webhook + nginx | ### Mailgun Configuration - Domain: `secret.edut.ai` (verified, green) - Wildcard: **On** - Tracking (click/open/unsubscribe): **All off** - Sending API key: stored securely (ask Joshua) - Inbound route: `match_recipient(".*@secret.edut.ai")` → `https://api.edut.ai/mailgun/inbound` (Forward + Stop, priority 0) - Route also forwards to `j@edut.ai` for Gmail archive --- ## Component 1: Nginx Configuration ### File: `/etc/nginx/sites-available/api.edut.ai` ```nginx server { listen 80; server_name api.edut.ai; location /mailgun/inbound { proxy_pass http://127.0.0.1:3847; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ``` SSL is handled by Cloudflare (proxied). Nginx listens on 80, Cloudflare terminates TLS. Port `3847` is arbitrary — the webhook service listens here. --- ## Component 2: Webhook Service ### Language Go or Python. Single file, minimal dependencies. Must be production-ready from first deploy. ### Endpoint: `POST /mailgun/inbound` Mailgun sends a multipart form POST with these fields (among others): | Field | Description | |-------|-------------| | `sender` | Email address of the person who sent the email | | `recipient` | The timestamp address (e.g., `0217073045482@secret.edut.ai`) | | `subject` | `EDUT-0217073045482` | | `body-plain` | Plain text body (the classified access request) | | `Message-Id` | Original message ID (needed for threading reply) | | `Date` | Email date header | ### Processing Logic ``` 1. Parse the recipient local-part to extract the code - Recipient format: "{code}@secret.edut.ai" where code is MMDDHHmmssmmm (13 digits) - Extract code from `recipient` first; use subject parsing only as fallback validation 2. Parse sender email address 3. Store in SQLite: - id (auto-increment — this is their sequential designation number) - code (the timestamp code from subject) - email (sender address) - recipient (the full recipient address) - message_id (Message-Id header for threading) - created_at (server timestamp) 4. Format the token for display: - code "0217073045482" → token "0217-0730-4548-2" - Slice: [0:4]-[4:8]-[8:12]-[12:] 5. Generate auth token: - Hash the code with a secret salt → first 16 chars of hex digest - Format as: xxxx-xxxx-xxxx-xxxx 6. Get the sequential designation number (the SQLite auto-increment id) - Pad to 4 digits: id 47 → "0047" 7. Build the classified reply (see template below) 8. Send reply via Mailgun API: - From: "EDUT " - To: sender's email - Subject: "Re: EDUT-{code}" - In-Reply-To: original Message-Id header - References: original Message-Id header - Body: plain text classified reply - Use the sending API key for secret.edut.ai domain ``` ### Mailgun Signature Verification Every inbound POST from Mailgun includes a signature. **Verify it** to prevent spoofing: - `timestamp` — Unix timestamp - `token` — Random string - `signature` — HMAC-SHA256 of `timestamp + token` using your Mailgun webhook signing key Reject any request that fails verification. ### Error Handling - If recipient/subject does not match expected format, return `200` and drop as noise. - Use `Message-Id` (or Mailgun event id) as idempotency key for inbound processing. - If SQLite write fails due to transient failure, return `5xx` so Mailgun retries. - If Mailgun send fails, retry with bounded backoff; if still failing, return `5xx`. - Return `2xx` only after persistence succeeds and outbound reply is accepted or deterministically deduped. --- ## Component 3: Auto-Reply Email Template ### Plain text body: ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ EDUT GOVERNANCE PROTOCOL ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ACCESS REGISTRATION CONFIRMED Designation: #{designation_number} Auth Token: {auth_token} Classification: OBSERVER Status: ACKNOWLEDGED Timestamp: {iso_timestamp} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ This designation is permanent and non-transferable. You will be notified when your access level changes. Do not reply to this message. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Edut LLC. All rights reserved. Development and licensing of deterministic governance systems. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ``` ### Variable Substitutions | Variable | Source | Example | |----------|--------|---------| | `{designation_number}` | SQLite auto-increment id, zero-padded to 4 | `0047` | | `{auth_token}` | HMAC-SHA256 hash of code, formatted xxxx-xxxx-xxxx-xxxx | `e7d2-4f1a-9bc3-a210` | | `{iso_timestamp}` | Server UTC time in ISO 8601 | `2026-02-17T07:30:45Z` | ### Email Headers ``` From: EDUT To: {sender_email} Subject: Re: EDUT-{code} In-Reply-To: {original_message_id} References: {original_message_id} Content-Type: text/plain; charset=utf-8 ``` The `In-Reply-To` and `References` headers ensure the reply threads under the original email in Gmail/Outlook/Apple Mail. --- ## Component 4: Landing Page Deployment ### Files - `public/index.html` — the Three.js globe landing page - `public/privacy/index.html` — privacy policy - `public/terms/index.html` — terms of use - `translations/*.json` — locale bundles for localized landing content ### Deploy to both domains: - `edut.ai` — primary landing page - `edut.dev` — same page for now ### Nginx for landing pages: ```nginx server { listen 80; server_name edut.ai www.edut.ai; root /var/www/edut.ai; index index.html; } server { listen 80; server_name edut.dev www.edut.dev; root /var/www/edut.dev; index index.html; } ``` Copy `public/index.html` to `/var/www/edut.ai/index.html` and `/var/www/edut.dev/index.html`. Serve `translations/` at `/translations` so locale files can be loaded by the landing page. --- ## Component 5: SQLite Database ### File: `/var/lib/edut/secrets.db` ### Schema: ```sql CREATE TABLE IF NOT EXISTS designations ( id INTEGER PRIMARY KEY AUTOINCREMENT, code TEXT NOT NULL UNIQUE, email TEXT NOT NULL, recipient TEXT NOT NULL, message_id TEXT, auth_token TEXT NOT NULL, created_at DATETIME DEFAULT (datetime('now')), replied_at DATETIME, reply_status TEXT DEFAULT 'pending' ); CREATE INDEX idx_designations_email ON designations(email); CREATE INDEX idx_designations_code ON designations(code); CREATE INDEX idx_designations_created ON designations(created_at); ``` --- ## Component 6: Launch Day Email When the platform launches, query all designations and send the access level change email through Mailgun: ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ EDUT GOVERNANCE PROTOCOL ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ACCESS LEVEL CHANGE Designation: #{designation_number} Auth Token: {auth_token} Classification: OPERATOR Status: ACTIVE Your system is ready for deployment. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Edut LLC. All rights reserved. Development and licensing of deterministic governance systems. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ``` Same format as the access confirmation. Same auth token. Classification changes from OBSERVER to OPERATOR. Status changes from ACKNOWLEDGED to ACTIVE. --- ## Environment Variables ```bash MAILGUN_API_KEY= MAILGUN_DOMAIN=secret.edut.ai MAILGUN_SIGNING_KEY= SECRETS_DB_PATH=/var/lib/edut/secrets.db AUTH_SALT= ``` --- ## Systemd Service ### File: `/etc/systemd/system/edut-secret.service` ```ini [Unit] Description=Edut Secret Webhook After=network.target [Service] Type=simple User=www-data WorkingDirectory=/opt/edut/secret ExecStart=/opt/edut/secret/secret-server EnvironmentFile=/opt/edut/secret/.env Restart=always RestartSec=5 [Install] WantedBy=multi-user.target ``` --- ## Testing 1. Send an email to `test0217120000000@secret.edut.ai` 2. Verify webhook receives the POST 3. Verify SQLite entry created with designation #1 4. Verify classified auto-reply received and threads correctly 5. Verify copy arrives in `j@edut.ai` Gmail 6. Send a second email to verify designation number increments to #2 7. Clear test data before going live --- ## Security Notes - Verify Mailgun webhook signatures on every request - Rate limit the endpoint (prevent abuse if someone discovers the URL) - The auth token is a one-way hash — it cannot be reversed to reveal the code or email - SQLite file should be backed up regularly (it's the designation list) - The sending API key must never be exposed in code or logs --- ## Domain Separation | Domain | Purpose | |--------|---------| | edut.ai | Brand, landing page, business email, public identity | | secret.edut.ai | Catch-all email for secret system (Mailgun) | | api.edut.ai | Webhook endpoint (Hetzner via Cloudflare) | | edut.dev | Product, developer docs, technical infrastructure |