Scaffold tier 2 and 3, the schema, the website, and self-hosted-first
Store: the full Postgres schema as an embedded migration. UUIDv7 keys so `ORDER BY id` is a free chronological index; raw MIME and attachments live in object storage with only a key in the row; `pods` present from day one because retrofitting tenancy costs more than an unused column. API keys are stored as a SHA-256 hash — a database dump must not be a set of live credentials. API: the v0 route table, including `ingest`, which closes the receive→thread→extract loop with zero mail infrastructure and is what makes the agent layer testable in CI. Scopes are a closed enum rather than strings, so "can send mail" and "can mint keys" are not one typo apart. Internal errors are logged in full and reported as a bare string. MCP: the tool catalogue, six tools. Adding a row here is the only way an agent gains a capability — a new REST route is invisible until someone opts it in. Three tests guard the rule that no tool can ever reach key management; CI fails rather than production. ADR 0006: enterprise self-hosted first. A hosted offering comes only after we have run this ourselves long enough to have a deliverability record worth selling. `pods` stays in the schema as the thing that keeps that path open — do not remove it as dead code. Also: multi-stage Dockerfile running as a non-root system user with no shell in the runtime image, and the openmail.karti.ai static page. 17 tests, zero clippy warnings, fmt clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JkyvfNJGTshJNE9FtwPLk7
This commit is contained in:
co-authored by
Claude Opus 5
parent
36b15ddcaf
commit
a42b798a0e
@@ -1,31 +1,21 @@
|
||||
//! The v0 REST API.
|
||||
//!
|
||||
//! Bearer auth, agent-shaped resources. Paths are kept close to the shape
|
||||
//! existing agent-mail tooling expects, so a client can be pointed at a
|
||||
//! self-hosted `OpenMail` with a base-URL swap. Where compatibility and a clean
|
||||
//! native shape conflict, the native shape wins and the difference is
|
||||
//! documented.
|
||||
//!
|
||||
//! ```text
|
||||
//! POST /v0/inboxes
|
||||
//! GET /v0/inboxes list
|
||||
//! GET /v0/inboxes/{id}
|
||||
//! POST /v0/inboxes/{id}/messages/send
|
||||
//! GET /v0/inboxes/{id}/messages limit, page_token, labels
|
||||
//! GET /v0/inboxes/{id}/messages/{mid}
|
||||
//! POST /v0/inboxes/{id}/messages/{mid}/reply
|
||||
//! GET /v0/inboxes/{id}/threads
|
||||
//! GET /v0/inboxes/{id}/threads/{tid}
|
||||
//! ```
|
||||
//! Bearer auth, agent-shaped resources. Paths stay close to the shape existing
|
||||
//! agent-mail tooling expects, so a client can target a self-hosted `OpenMail`
|
||||
//! with a base-URL swap. Where compatibility and a clean native shape conflict,
|
||||
//! the native shape wins and the difference is documented.
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("unauthorized")]
|
||||
Unauthorized,
|
||||
#[error("not found")]
|
||||
NotFound,
|
||||
#[error("bad request: {0}")]
|
||||
BadRequest(String),
|
||||
#[error(transparent)]
|
||||
Store(#[from] openmail_store::Error),
|
||||
pub mod auth;
|
||||
pub mod error;
|
||||
pub mod routes;
|
||||
|
||||
pub use error::Error;
|
||||
|
||||
use sqlx::PgPool;
|
||||
|
||||
/// Everything a handler may reach. Deliberately small — a handler that needs
|
||||
/// something not in here is usually a handler doing too much.
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub db: PgPool,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user