8.8 KiB
Edut Secret System - Two-Factor Designation Spec
Overview
The Edut designation flow is a two-factor protocol:
- Phone verification by SMS (Twilio).
- Email verification by classified mailto request (Mailgun).
Both factors bind to the same designation code and auth token. This is not a throwaway waitlist. It is the pre-launch identity envelope that can transition into launch activation workflows.
End-to-End Flow
Visitor clicks orb on edut.ai
-> phone input appears
-> user submits phone
POST /secret/initiate
-> code issued
-> row created/updated in SQLite
-> SMS sent via Twilio
User receives SMS and replies: CONFIRM
Twilio POST /twilio/inbound
-> signature verified
-> pending designation matched
-> phone_verified_at set
Landing page polls /secret/status/{code}
-> sees phone_verified=true
-> opens mailto:{code}@secret.edut.ai with protocol body
User sends email
Mailgun POST /mailgun/inbound
-> signature verified
-> code parsed from recipient local-part
-> email_verified_at set
-> classified confirmation reply sent
Landing page stores acknowledged token
Infrastructure
| Service | Domain / System | Purpose |
|---|---|---|
| Landing UI | edut.ai, edut.dev |
Orb, phone capture, mailto initiation |
| Twilio | Toll-free SMS number | Outbound designation SMS + inbound CONFIRM replies |
| Mailgun | secret.edut.ai |
Inbound email processing + confirmation replies |
| API host | api.edut.ai |
/secret/*, /twilio/inbound, /mailgun/inbound |
| Database | SQLite (/var/lib/edut/secrets.db) |
Durable designation state |
Twilio Channel Design
Number strategy
- Use a toll-free SMS number.
- Voice is disabled or rejected.
- Complete toll-free verification before production traffic.
Outbound SMS template
━━━━━━━━━━━━━━━━━
EDUT GOVERNANCE PROTOCOL
Designation: {token}
Reply CONFIRM to proceed.
━━━━━━━━━━━━━━━━━
Where {token} is formatted from the code:
- code:
0217073045482 - token:
0217-0730-4548-2
API Surface
1) Initiate
POST /secret/initiate
Request JSON:
{
"phone": "+12065550123",
"origin": "https://edut.ai"
}
Behavior:
- Normalize phone to E.164.
- Rate-limit by IP, phone, and rolling time window.
- Generate designation code (
MMDDHHmmssmmm) and auth token. - Insert or upsert designation row with
phoneandcreated_at. - Send SMS via Twilio.
- Return
200with minimal response:
{
"code": "0217073045482",
"token": "0217-0730-4548-2",
"status": "sms_sent"
}
Notes:
- Do not expose internal row id.
- Never return auth token in API responses.
2) Status
GET /secret/status/{code}
Response JSON:
{
"code": "0217073045482",
"phone_verified": true,
"email_verified": false,
"status": "phone_verified"
}
Rules:
- Polling endpoint is read-only.
- Response is minimal and does not expose phone/email values.
- Apply rate limits to prevent enumeration.
3) Twilio inbound
POST /twilio/inbound
Expected Twilio fields include:
From(sender phone)To(Edut toll-free number)Body(message body)MessageSid
Verification:
- Validate
X-Twilio-Signatureagainst Twilio auth token. - Reject unsigned or invalid payloads.
Processing:
- Normalize sender phone.
- Accept
CONFIRM(case-insensitive, trim whitespace). - Find latest pending designation for that phone within validity window.
- Idempotency key:
MessageSid. - Mark
phone_verified_atand set statephone_verified. - Return
2xxonly after persistence.
4) Mailgun inbound
POST /mailgun/inbound
Expected Mailgun fields include:
recipient({code}@secret.edut.ai)senderMessage-Idsubject
Verification:
- Verify Mailgun webhook signature using Mailgun signing key.
Processing:
- Parse
codefrom recipient local-part. - Require existing designation row with
phone_verified_at IS NOT NULL. - Record
email,message_id, andemail_verified_at. - Idempotency key:
Message-Id(or Mailgun event id fallback). - Send confirmation reply through Mailgun.
Confirmation Email Template
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
EDUT GOVERNANCE PROTOCOL
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ACCESS REGISTRATION CONFIRMED
Designation: #{designation_number}
Auth Token: {auth_token}
Classification: OBSERVER
Status: ACKNOWLEDGED
Phone: VERIFIED
Email: VERIFIED
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.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SQLite Schema
File: /var/lib/edut/secrets.db
CREATE TABLE IF NOT EXISTS designations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT NOT NULL UNIQUE,
phone TEXT,
phone_verified_at DATETIME,
email TEXT,
email_verified_at DATETIME,
recipient TEXT,
message_id TEXT,
auth_token TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending_phone',
created_at DATETIME DEFAULT (datetime('now')),
replied_at DATETIME,
reply_status TEXT DEFAULT 'pending'
);
CREATE INDEX idx_designations_code ON designations(code);
CREATE INDEX idx_designations_phone ON designations(phone);
CREATE INDEX idx_designations_email ON designations(email);
CREATE INDEX idx_designations_status ON designations(status);
CREATE INDEX idx_designations_created ON designations(created_at);
CREATE TABLE IF NOT EXISTS twilio_inbound_events (
message_sid TEXT PRIMARY KEY,
from_phone TEXT NOT NULL,
body TEXT,
received_at DATETIME DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS mailgun_inbound_events (
message_id TEXT PRIMARY KEY,
sender TEXT,
recipient TEXT,
received_at DATETIME DEFAULT (datetime('now'))
);
Nginx Routing
File: /etc/nginx/sites-available/api.edut.ai
server {
listen 80;
server_name api.edut.ai;
location /secret/ {
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;
}
location /twilio/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;
}
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;
}
}
Environment Variables
SECRETS_DB_PATH=/var/lib/edut/secrets.db
AUTH_SALT=<random 32+ char>
# Twilio
TWILIO_ACCOUNT_SID=<sid>
TWILIO_AUTH_TOKEN=<auth token>
TWILIO_FROM_NUMBER=<+1 toll-free number>
# Mailgun
MAILGUN_API_KEY=<sending api key>
MAILGUN_DOMAIN=secret.edut.ai
MAILGUN_SIGNING_KEY=<webhook signing key>
Error Handling and Idempotency
- Twilio signature failure: return
403. - Mailgun signature failure: return
403. - Duplicate inbound event id: treat as idempotent success.
- Transient DB/send failure: return
5xxso provider retries. - Unknown or expired confirmation attempts: return
200and no state mutation.
Security Controls
- Signature verification on both inbound providers.
- Per-IP and per-phone initiate throttles.
- Per-code polling throttles.
- Do not expose auth token except in confirmation email.
- Back up SQLite and protect file permissions.
Launch Evolution
At launch, each fully verified designation (phone_verified_at + email_verified_at) is eligible for activation messaging:
- Classification can transition from
OBSERVERtoOPERATOR. - Auth token remains stable.
- Message can include deployment handoff link or activation instructions.
Important Boundary
This two-factor designation flow is a pre-launch identity bootstrap. It does not replace runtime device trust anchors, workspace isolation controls, or offline license enforcement in the product runtime.