365 lines
11 KiB
Markdown
365 lines
11 KiB
Markdown
# 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 <protocol@secret.edut.ai>"
|
|
- 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 <protocol@secret.edut.ai>
|
|
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=<sending API key for secret.edut.ai>
|
|
MAILGUN_DOMAIN=secret.edut.ai
|
|
MAILGUN_SIGNING_KEY=<from Mailgun account settings, for webhook verification>
|
|
SECRETS_DB_PATH=/var/lib/edut/secrets.db
|
|
AUTH_SALT=<random 32+ character string for hashing auth tokens>
|
|
```
|
|
|
|
---
|
|
|
|
## 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 |
|