feat(cli): configurable send-as From address (flags, TUI, validation)

- tui.ValidFromAddress: exported validator; blank passes, malformed rejects
- Fields.FromAddress: new field, round-trips through ToAccount/FieldsFromAccount
- Fields.Validate: calls ValidFromAddress before returning nil
- TUI form: from_address fieldDef between username and password
- send.go: From set via acc.SendFrom() instead of acc.Username
- admin.go account add: --from flag with pre-parse validation
- admin.go account edit: --from flag; validate before Visit, apply in Visit
- USER-MANUAL.md: --from flag added to account add flags table

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-23 20:25:14 +01:00
parent 6a99e5bb6e
commit b6e68ddeae
6 changed files with 100 additions and 2 deletions
+13
View File
@@ -45,6 +45,7 @@ func runAccount(args []string, role store.Role, out, errOut io.Writer) int {
smtpSec := fs.String("smtp-security", "tls", "tls|starttls")
user := fs.String("username", "", "login username")
pass := fs.String("password", "", "login password")
from := fs.String("from", "", "send-as address (blank = use username)")
subj := fs.String("subject-regex", "", "inbound subject filter")
wlIn := fs.Bool("whitelist-in", false, "enable inbound whitelist")
wlOut := fs.Bool("whitelist-out", false, "enable outbound whitelist")
@@ -56,9 +57,14 @@ func runAccount(args []string, role store.Role, out, errOut io.Writer) int {
fmt.Fprintln(errOut, "name, imap-host, and username are required")
return 2
}
if err := tui.ValidFromAddress(*from); err != nil {
fmt.Fprintln(errOut, err)
return 2
}
acc := store.Account{
Name: *name, Mode: *mode, IMAPHost: *host, IMAPPort: *port, IMAPSecurity: *sec,
AuthType: "password", Username: *user, Password: *pass,
FromAddress: *from,
SubjectRegex: *subj, WhitelistInEnabled: *wlIn, WhitelistOutEnabled: *wlOut,
ProcessBacklog: *backlog,
}
@@ -85,6 +91,7 @@ func runAccount(args []string, role store.Role, out, errOut io.Writer) int {
smtpSec := fs.String("smtp-security", "", "tls|starttls")
user := fs.String("username", "", "login username")
pass := fs.String("password", "", "login password (blank keeps existing)")
from := fs.String("from", "", "send-as address (blank keeps existing)")
subj := fs.String("subject-regex", "", "inbound subject filter")
if err := fs.Parse(rest); err != nil {
return 2
@@ -96,6 +103,10 @@ func runAccount(args []string, role store.Role, out, errOut io.Writer) int {
if fs.NFlag() == 1 { // only --name → interactive TUI form, prefilled
return editInteractive(st, *name, out, errOut)
}
if err := tui.ValidFromAddress(*from); err != nil {
fmt.Fprintln(errOut, err)
return 2
}
acc, err := st.GetAccount(*name)
if err != nil {
fmt.Fprintf(errOut, "edit: %v\n", err)
@@ -122,6 +133,8 @@ func runAccount(args []string, role store.Role, out, errOut io.Writer) int {
acc.Username = *user
case "password":
acc.Password = *pass
case "from":
acc.FromAddress = *from
case "subject-regex":
acc.SubjectRegex = *subj
}