c99eaedafd
Adds the `send` agent command and everything behind it: - store: Account carries SMTP host/port/security (NULL-safe scan/insert/select); admin `account add` gains --smtp-* flags (applied for RW accounts). - policy: OutboundRule.Check(recipients) → (ok, reason); RO ⇒ ro_mode, whitelist-out blocks the whole send if any recipient fails (no partial send). - mail: Header.References; OutgoingMessage + BuildMIME (plain text + attachments, In-Reply-To/References threading, Bcc envelope-only); SendSMTP (tls/starttls, SASL PLAIN, envelope send) via emersion/go-smtp. - cli: SendCmd gates outbound, resolves --reply-to under the inbound filter (filtered/absent source ⇒ not_found), reads attachments, audits, emits the JSON envelope; repeatable --to/--cc/--bcc/--attach flags wired into the router. Implemented test-first; full suite passes incl -race. Validated live against friday.mxlogin.com: real send to me@stevecliff.com, RO + whitelist-out blocks, and --reply-to threading off a live INBOX message. test-creds.md gitignored. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
27 lines
785 B
Go
27 lines
785 B
Go
package policy
|
|
|
|
// OutboundRule captures one account's send-side enforcement.
|
|
type OutboundRule struct {
|
|
Mode string // RO | RW
|
|
WhitelistOutEnabled bool
|
|
WhitelistOut []string
|
|
}
|
|
|
|
// Check reports whether a send to the given recipient set is permitted. On a
|
|
// block it returns a stable reason: "ro_mode" (account is read-only) or
|
|
// "whitelist_out" (a recipient is not whitelisted). The whole send is blocked
|
|
// if any single recipient fails — there is no partial send.
|
|
func (r OutboundRule) Check(recipients []string) (bool, string) {
|
|
if r.Mode == "RO" {
|
|
return false, "ro_mode"
|
|
}
|
|
if r.WhitelistOutEnabled {
|
|
for _, addr := range recipients {
|
|
if !MatchAddress(r.WhitelistOut, addr) {
|
|
return false, "whitelist_out"
|
|
}
|
|
}
|
|
}
|
|
return true, ""
|
|
}
|