package accounts import ( "crypto/tls" "encoding/json" "fmt" "net" "net/smtp" "strconv" "gomail/internal/crypto" "gomail/internal/db" ) // sendViaSMTP delivers an outgoing message through a linked account's own // SMTP settings — stdlib net/smtp, same choice as internal/queue's // MXDeliverer, so outbound for both "GoMail relays for me" and "I'm using my // own IMAP+SMTP provider" stay on the same dependency-free foundation. func sendViaSMTP(account *db.LinkedAccount, mk *crypto.MasterKey, msg *OutgoingMessage) error { plain, err := crypto.Decrypt(mk, account.ID, "linked-account-cred", account.CredentialEnc) if err != nil { return fmt.Errorf("decrypting stored credential: %w", err) } var cred IMAPCredential // same password shape reused for the paired SMTP auth if err := json.Unmarshal(plain, &cred); err != nil { return fmt.Errorf("parsing stored credential: %w", err) } addr := net.JoinHostPort(account.SMTPHost, strconv.Itoa(account.SMTPPort)) conn, err := net.DialTimeout("tcp", addr, dialTimeout) if err != nil { return fmt.Errorf("dial %s: %w", addr, err) } defer conn.Close() if account.SMTPTLS == "implicit" { // See provider_imap.go's connect() comment — same gap, same reason. conn = tls.Client(conn, &tls.Config{ServerName: account.SMTPHost, InsecureSkipVerify: true}) } client, err := smtp.NewClient(conn, account.SMTPHost) if err != nil { return fmt.Errorf("SMTP handshake: %w", err) } defer client.Close() if account.SMTPTLS == "starttls" { if ok, _ := client.Extension("STARTTLS"); ok { if err := client.StartTLS(&tls.Config{ServerName: account.SMTPHost}); err != nil { return fmt.Errorf("STARTTLS: %w", err) } } } auth := smtp.PlainAuth("", account.EmailAddress, cred.Password, account.SMTPHost) if err := client.Auth(auth); err != nil { return fmt.Errorf("SMTP auth: %w", err) } if err := client.Mail(account.EmailAddress); err != nil { return err } for _, to := range msg.To { if err := client.Rcpt(to); err != nil { return err } } w, err := client.Data() if err != nil { return err } raw := buildRFC5322(account.EmailAddress, msg) if _, err := w.Write(raw); err != nil { return err } if err := w.Close(); err != nil { return err } return client.Quit() }