Phase 1: Foundation (Database & CLI)

Overview

Operators can register a mailbox’s outbound SMTP server through the CLI with encrypted credential storage, strict TLS enforcement, and an explicit TLS-mode choice (implicit for port 465, starttls_required for port 587). The outbound_messages table provides the durable queue that all subsequent phases build on. Nothing is sent yet — this phase is pure data foundation.

Goal

Extend the existing IMAP proxy database schema and CLI to handle SMTP settings and the outbound message queue, setting the foundation for sending capabilities.

TDD Acceptance Criteria

  1. pytest tests/models/test_mailbox.py::test_smtp_fields_exist MUST PASS
  2. pytest tests/models/test_mailbox.py::test_smtp_password_encryption MUST PASS
  3. pytest tests/models/test_outbound_messages.py::test_table_creation_and_status_enum MUST PASS
  4. pytest tests/test_cli.py::test_mailboxes_create_prompts_for_smtp MUST PASS

Technical Specifications

SQLAlchemy Models

Mailbox Model Updates:

  • smtp_host: String, nullable=False
  • smtp_port: Integer, default=587
  • smtp_username: String, nullable=False
  • smtp_password_encrypted: LargeBinary, nullable=False
  • smtp_tls_mode: Enum (MailboxTlsMode: implicit / starttls_required), nullable=False, default starttls_required. Replaces the use_tls = smtp_port == 465 port heuristic. implicit means TLS from byte zero (typical on port 465). starttls_required means plaintext connect followed by a mandatory STARTTLS upgrade; if the server does not advertise STARTTLS, or if the handshake fails, the worker fails the job closed and never sends AUTH over plaintext. Certificate verification is pinned on (validate_certs=True) for both modes. Closes FEEDBACK.md §1.7.

OutboundMessage Model:

  • id: UUID (Primary Key)
  • mailbox_id: UUID (Foreign Key)
  • recipient_email: String
  • subject: String
  • status: Enum (queued, processing, sent, failed, bounced)
  • attempts: Integer, default=0
  • next_retry_at: DateTime, nullable=True
  • processing_started_at: DateTime, nullable=True
  • sent_at: DateTime, nullable=True
  • opened_at: DateTime, nullable=True
  • clicked_at: DateTime, nullable=True
  • error_log: Text, nullable=True
  • created_at: DateTime
  • updated_at: DateTime

CLI Updates

  • Command mailboxes:create must ask for SMTP Host, SMTP Port, SMTP Username, SMTP Password, and SMTP TLS Mode.
  • The TLS-mode prompt is constrained to {implicit, starttls_required} and defaults to starttls_required; the command layer coerces via MailboxTlsMode(...) so invalid values raise ValueError before reaching the DB.
  • Update messages:create permission to messages:send in JWT issuance logic.

Security Considerations

  • smtp_password_encrypted must be encrypted using the exact same Fernet symmetric encryption setup as the existing IMAP password.

Sourced from docs/features/01_database_and_config in the repo. Edits go through the same review as code.