web/docs/secret-system-spec.md

11 KiB

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

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:

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:

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

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

[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