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:
@@ -35,8 +35,9 @@ import { v_0_1_0_71 } from './v0.1.0.71'
|
||||
import { v_0_1_0_72 } from './v0.1.0.72'
|
||||
import { v_0_1_0_73 } from './v0.1.0.73'
|
||||
import { v_0_1_0_74 } from './v0.1.0.74'
|
||||
import { v_0_1_0_75 } from './v0.1.0.75'
|
||||
|
||||
export const versionGraph = VersionGraph.of({
|
||||
current: v_0_1_0_74,
|
||||
other: [v_0_1_0_39, v_0_1_0_40, v_0_1_0_41, v_0_1_0_42, v_0_1_0_43, v_0_1_0_44, v_0_1_0_45, v_0_1_0_46, v_0_1_0_47, v_0_1_0_48, v_0_1_0_49, v_0_1_0_50, v_0_1_0_51, v_0_1_0_52, v_0_1_0_53, v_0_1_0_54, v_0_1_0_55, v_0_1_0_56, v_0_1_0_57, v_0_1_0_58, v_0_1_0_59, v_0_1_0_60, v_0_1_0_61, v_0_1_0_62, v_0_1_0_63, v_0_1_0_64, v_0_1_0_65, v_0_1_0_66, v_0_1_0_67, v_0_1_0_68, v_0_1_0_69, v_0_1_0_70, v_0_1_0_71, v_0_1_0_72, v_0_1_0_73],
|
||||
current: v_0_1_0_75,
|
||||
other: [v_0_1_0_39, v_0_1_0_40, v_0_1_0_41, v_0_1_0_42, v_0_1_0_43, v_0_1_0_44, v_0_1_0_45, v_0_1_0_46, v_0_1_0_47, v_0_1_0_48, v_0_1_0_49, v_0_1_0_50, v_0_1_0_51, v_0_1_0_52, v_0_1_0_53, v_0_1_0_54, v_0_1_0_55, v_0_1_0_56, v_0_1_0_57, v_0_1_0_58, v_0_1_0_59, v_0_1_0_60, v_0_1_0_61, v_0_1_0_62, v_0_1_0_63, v_0_1_0_64, v_0_1_0_65, v_0_1_0_66, v_0_1_0_67, v_0_1_0_68, v_0_1_0_69, v_0_1_0_70, v_0_1_0_71, v_0_1_0_72, v_0_1_0_73, v_0_1_0_74],
|
||||
})
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { VersionInfo } from '@start9labs/start-sdk'
|
||||
|
||||
// Phase-A of the daily activity digest: outbound SMTP send capability. Code-only,
|
||||
// no schema change (migrations are no-ops):
|
||||
// * New "Configure Digest SMTP" StartOS action (actions/configureDigestSmtp.ts):
|
||||
// writes a per-package, custom SMTP account to /data/secrets/smtp/{host,port,
|
||||
// from,username,password,security} — independent of any StartOS system-wide
|
||||
// SMTP account. Password is piped over stdin (never argv/env), like the
|
||||
// Anthropic-key action.
|
||||
// * docker_entrypoint.sh reads those files at boot and exports SMTP_* into the
|
||||
// server process (env still wins for an operator override).
|
||||
// * backend/smtp_send.py: stdlib smtplib wrapper reading SMTP_* (one code path
|
||||
// for dev .env and the box). New admin endpoint POST /api/admin/digest/test-email
|
||||
// sends a test message to the requesting `to` or to all active admins, to prove
|
||||
// the pipe before the digest itself (Phase B) is built.
|
||||
export const v_0_1_0_75 = VersionInfo.of({
|
||||
version: '0.1.0:75',
|
||||
releaseNotes: {
|
||||
en_US: [
|
||||
'Add outbound email: a "Configure Digest SMTP" action sets a dedicated mailbox for this',
|
||||
'service (no server-wide SMTP account required), and a new admin "send test email" action',
|
||||
'verifies it works — groundwork for the daily activity digest.',
|
||||
].join(' '),
|
||||
},
|
||||
migrations: { up: async () => {}, down: async () => {} },
|
||||
})
|
||||
Reference in New Issue
Block a user