Add daily-digest Phase A: per-package SMTP send + admin test endpoint (v0.1.0:75)

Groundwork for the daily activity digest: give the CRM an outbound mail path.
Today nothing leaves the box (Gmail capture + drafts only), so this adds a
dedicated, per-package SMTP account independent of any StartOS system-wide SMTP.

- configureDigestSmtp Start9 action: writes host/port/from/username/password/
  security to /data/secrets/smtp/* (password piped over stdin, never argv/env;
  per-field files, owner-only) — mirrors the setAnthropicApiKey pattern.
- docker_entrypoint.sh reads those at boot and exports SMTP_* (operator env wins).
- backend/smtp_send.py: stdlib smtplib wrapper reading SMTP_* (one code path for
  dev .env and the box); starttls/tls/none modes.
- POST /api/admin/digest/test-email (admin-only): proves the pipe. Recipients are
  restricted to the active-admin set — an arbitrary `to` is rejected, so the
  endpoint is not an open relay; send failures are logged, not echoed (an SMTP
  auth error can carry the credential).
- Tests: test_smtp_send.py (sender), test_smtp_endpoint.py (gating + relay
  restriction + no-leak). 18/18 backend green; s9pk typechecks.

Analysis/summarization for the digest body (Phase B) will run on Spark, never
Claude — the digest is deliberately un-anonymized. Decisions + Phase B plan in
ROADMAP.md.
This commit is contained in:
Keysat
2026-06-15 18:33:06 -05:00
parent ecfc5d968a
commit 2758ac81d3
13 changed files with 765 additions and 14 deletions
+22
View File
@@ -69,6 +69,28 @@ elif [ -z "${ANTHROPIC_API_KEY:-}" ]; then
echo "[entrypoint] Architect: no API key yet (drop it at $ANTHROPIC_KEY_FILE to enable thesis generation)"
fi
# ── Daily-digest SMTP (per-package custom mailbox) ──────────────
# The CRM emails a daily activity digest. Credentials come from the "Configure
# Digest SMTP" StartOS action, which writes one file per field under
# $SECRETS_DIR/smtp. We read them back here (plain cat — never eval) and export
# SMTP_* for the server process. Each value is read only if not already set in
# the service environment, so an operator override still wins. Self-disabling
# until host is present (the digest mailer reports "not configured").
SMTP_DIR="$SECRETS_DIR/smtp"
if [ -z "${SMTP_HOST:-}" ] && [ -f "$SMTP_DIR/host" ]; then
export SMTP_HOST="$(cat "$SMTP_DIR/host")"
export SMTP_PORT="${SMTP_PORT:-$(cat "$SMTP_DIR/port" 2>/dev/null || echo 587)}"
export SMTP_FROM="${SMTP_FROM:-$(cat "$SMTP_DIR/from" 2>/dev/null || true)}"
export SMTP_USERNAME="${SMTP_USERNAME:-$(cat "$SMTP_DIR/username" 2>/dev/null || true)}"
export SMTP_PASSWORD="${SMTP_PASSWORD:-$(cat "$SMTP_DIR/password" 2>/dev/null || true)}"
export SMTP_SECURITY="${SMTP_SECURITY:-$(cat "$SMTP_DIR/security" 2>/dev/null || echo starttls)}"
echo "[entrypoint] Digest SMTP: configured (host $SMTP_HOST)"
elif [ -n "${SMTP_HOST:-}" ]; then
echo "[entrypoint] Digest SMTP: using SMTP_HOST from the service environment"
else
echo "[entrypoint] Digest SMTP: not configured (use the Configure Digest SMTP action)"
fi
# ── Phase-0 ingest / retrieval env ──────────────────────────────
# These are consumed by the ingest pipeline (backend/ingest/) and the MCP
# server (backend/mcp/) — NOT by the CRM web server, which ignores them.