fix(smtp): proper SMTP dialogue, encoded mails, optional skip-verify

- Explicit SMTP flow (dial -> EHLO -> STARTTLS -> AUTH -> MAIL/RCPT/DATA)
  with errors naming the refusing step (also logged as smtp_step)
- EHLO with the sender's domain instead of "localhost"
- Envelope gets the bare address; "Name <addr>" stays in the From header
  (was rejected as invalid MAIL FROM)
- Date and Message-ID headers, RFC 2047 subject, quoted-printable body
- SMTP_SKIP_VERIFY / admin switch for self-signed certificates;
  SMTP checkboxes are switches now
- Tests against a fake SMTP server (STARTTLS, 451, display names)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-28 22:28:26 +01:00
1 parent bf0d68e7e1
commit ce321ce579
15 files changed
+477 -96

No files matched your search

+13 -3
View File
@@ -493,7 +493,8 @@ func (s *Store) migrate() error {
('smtp_user', ''),
('smtp_pass', ''),
('smtp_from', ''),
('smtp_tls', 'true')`)
('smtp_tls', 'true'),
('smtp_skip_verify', 'false')`)
// Clean up legacy roles in team_members and users
_, _ = s.db.Exec(`UPDATE team_members SET role = 'admin' WHERE role IN ('team_admin', 'owner')`)
@@ -5037,7 +5038,7 @@ func (s *Store) DeleteUserInvite(inviteID string) error {
// --- SYSTEM SETTINGS ---
func (s *Store) SeedSMTPDefaultsFromEnv(host, port, user, pass, from string, tls bool) {
func (s *Store) SeedSMTPDefaultsFromEnv(host, port, user, pass, from string, tls, skipVerify bool) {
cur, err := s.GetSystemSettings()
if err != nil || cur == nil {
return
@@ -5052,6 +5053,7 @@ func (s *Store) SeedSMTPDefaultsFromEnv(host, port, user, pass, from string, tls
cur.SMTPPass = pass
cur.SMTPFrom = strings.TrimSpace(from)
cur.SMTPTLS = tls
cur.SMTPSkipVerify = skipVerify
_ = s.UpdateSystemSettings(cur)
}
}
@@ -5095,6 +5097,8 @@ func (s *Store) GetSystemSettings() (*domain.SystemSettings, error) {
settings.SMTPFrom = v
case "smtp_tls":
settings.SMTPTLS = (v != "false" && v != "0")
case "smtp_skip_verify":
settings.SMTPSkipVerify = v == "true"
}
}
}
@@ -5110,6 +5114,10 @@ func (s *Store) UpdateSystemSettings(settings *domain.SystemSettings) error {
if settings.SMTPTLS {
tlsStr = "true"
}
skipVerifyStr := "false"
if settings.SMTPSkipVerify {
skipVerifyStr = "true"
}
if settings.SMTPPort == "" {
settings.SMTPPort = "587"
}
@@ -5126,7 +5134,8 @@ func (s *Store) UpdateSystemSettings(settings *domain.SystemSettings) error {
('smtp_user', ?),
('smtp_pass', ?),
('smtp_from', ?),
('smtp_tls', ?)
('smtp_tls', ?),
('smtp_skip_verify', ?)
ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
settings.SharingMode,
regStr,
@@ -5137,6 +5146,7 @@ func (s *Store) UpdateSystemSettings(settings *domain.SystemSettings) error {
settings.SMTPPass,
strings.TrimSpace(settings.SMTPFrom),
tlsStr,
skipVerifyStr,
)
return err
}