// Package report sends a deployment summary email via SMTP. package report import ( "fmt" "net/smtp" "os" "path/filepath" "strings" "time" ) // SMTP2Go relay configuration for X9.cz deployment reports. const ( smtpHost = "mail-eu.smtp2go.com" smtpPort = "2525" smtpUser = "xetup" smtpPass = "M9ahxHOnJ8fM0CEF" mailFrom = "xetup@x9.cz" mailTo = "net@x9.cz" ) // localReportPath is where a local HTML copy of the report is always saved. const localReportPath = `C:\X9\report.html` // StepResult holds one row of the deployment report. type StepResult struct { Num string Name string Status string // OK, ERROR, SKIPPED, CANCELLED Elapsed time.Duration } // Send builds the deployment report, saves a local HTML copy to C:\X9\, // and emails it via SMTP with retries. Returns the last SMTP error if all // attempts fail (the local copy is always written regardless). func Send(results []StepResult) error { hostname, _ := os.Hostname() now := time.Now().Format("2006-01-02 15:04") subject := fmt.Sprintf("xetup report %s", hostname) body := buildHTML(results, hostname, now) // Always save local copy so technician has a record even if SMTP fails _ = os.MkdirAll(filepath.Dir(localReportPath), 0755) if err := os.WriteFile(localReportPath, []byte(body), 0644); err != nil { fmt.Fprintf(os.Stderr, "[WARN] Failed to save local report: %v\n", err) } // Retry SMTP up to 3 times with exponential backoff (1s, 5s, 15s) delays := []time.Duration{0, 1 * time.Second, 5 * time.Second} var lastErr error for attempt, delay := range delays { if delay > 0 { time.Sleep(delay) } if err := sendMail(subject, body); err != nil { lastErr = err fmt.Fprintf(os.Stderr, "[WARN] Email attempt %d/3 failed: %v\n", attempt+1, err) continue } return nil } fmt.Fprintf(os.Stderr, "[ERROR] All email attempts failed. Local copy saved: %s\n", localReportPath) return lastErr } func sendMail(subject, body string) error { msg := strings.Join([]string{ "From: " + mailFrom, "To: " + mailTo, "Subject: " + subject, "MIME-Version: 1.0", "Content-Type: text/html; charset=UTF-8", "", body, }, "\r\n") auth := smtp.PlainAuth("", smtpUser, smtpPass, smtpHost) return smtp.SendMail( smtpHost+":"+smtpPort, auth, mailFrom, []string{mailTo}, []byte(msg), ) } func buildHTML(results []StepResult, hostname, dateTime string) string { var ok, errs, skipped int var rows strings.Builder for _, r := range results { color := "#333" icon := "" switch r.Status { case "OK": ok++ color = "#2e7d32" icon = "✓" case "ERROR": errs++ color = "#c62828" icon = "✗" default: skipped++ color = "#9e9e9e" icon = "–" } elapsed := "" if r.Elapsed > 0 { elapsed = r.Elapsed.Round(time.Second).String() } fmt.Fprintf(&rows, `
Odeslano z xetup.exe — log: C:\Windows\Setup\Scripts\Deploy.log
`, hostname, dateTime, rows.String(), summaryColor, summaryText, ok, errs, skipped) }