mirror of
https://github.com/therootcompany/golib.git
synced 2026-01-27 15:08:05 +00:00
feat: add cmd/smtp-test for debugging smtp auth and delivery
This commit is contained in:
parent
939c733ace
commit
d3b59aebff
7
cmd/smtp-test/go.mod
Normal file
7
cmd/smtp-test/go.mod
Normal file
@ -0,0 +1,7 @@
|
||||
module github.com/therootcompany/golib/smtp-test
|
||||
|
||||
go 1.25.4
|
||||
|
||||
require golang.org/x/term v0.39.0
|
||||
|
||||
require golang.org/x/sys v0.40.0 // indirect
|
||||
4
cmd/smtp-test/go.sum
Normal file
4
cmd/smtp-test/go.sum
Normal file
@ -0,0 +1,4 @@
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
156
cmd/smtp-test/main.go
Normal file
156
cmd/smtp-test/main.go
Normal file
@ -0,0 +1,156 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
const (
|
||||
name = "smtp-test"
|
||||
licenseYear = "2026"
|
||||
licenseOwner = "AJ ONeal"
|
||||
licenseType = "CC0-1.0"
|
||||
)
|
||||
|
||||
// set by GoReleaser via ldflags
|
||||
var (
|
||||
version = "0.0.0-dev"
|
||||
commit = "0000000"
|
||||
date = "0001-01-01T00:00:00Z"
|
||||
)
|
||||
|
||||
func printVersion() {
|
||||
if len(commit) > 7 {
|
||||
commit = commit[:7]
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "%s v%s %s (%s)\n", name, version, commit, date)
|
||||
fmt.Fprintf(os.Stderr, "Copyright (C) %s %s\n", licenseYear, licenseOwner)
|
||||
fmt.Fprintf(os.Stderr, "Licensed under the %s license\n", licenseType)
|
||||
}
|
||||
|
||||
type CLIConfig struct {
|
||||
showVersion bool
|
||||
user string
|
||||
from string
|
||||
to string
|
||||
host string // e.g. smtp.mailgun.org:587 or smtp.gmail.com:587
|
||||
subject string
|
||||
body string
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := CLIConfig{
|
||||
subject: "smtp-test - connectivity check",
|
||||
body: "This is a test message from smtp-test.\nIf you received this, SMTP auth + send worked.",
|
||||
}
|
||||
|
||||
mainFlags := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
|
||||
mainFlags.BoolVar(&cfg.showVersion, "version", false, "Print version and exit")
|
||||
mainFlags.StringVar(&cfg.user, "user", os.Getenv("SMTP_USER"), "Auth email e.g. 'you@gmail.com' or set SMTP_USER")
|
||||
mainFlags.StringVar(&cfg.from, "from", os.Getenv("SMTP_FROM"), "Sender email, e.g. 'you@gmail.com' or set SMTP_FROM")
|
||||
mainFlags.StringVar(&cfg.to, "to", os.Getenv("SMTP_TO"), "Recipient email, e.g. 'test@yourdomain.com' or set SMTP_TO")
|
||||
mainFlags.StringVar(&cfg.host, "host", os.Getenv("SMTP_HOST"), "SMTP server + port, e.g. 'smtp.gmail.com:587' or set SMTP_HOST")
|
||||
mainFlags.StringVar(&cfg.subject, "subject", cfg.subject, "Subject line (default: connectivity check)")
|
||||
mainFlags.StringVar(&cfg.body, "body", cfg.body, "Plain text body (default: test message)")
|
||||
|
||||
mainFlags.Usage = func() {
|
||||
printVersion()
|
||||
out := mainFlags.Output()
|
||||
fmt.Fprintf(out, "\n")
|
||||
fmt.Fprintf(out, "USAGE\n")
|
||||
fmt.Fprintf(out, " smtp-test [options]\n")
|
||||
fmt.Fprintf(out, " (or provide most values via environment variables)\n\n")
|
||||
mainFlags.PrintDefaults()
|
||||
fmt.Fprintf(out, "\nExamples:\n")
|
||||
fmt.Fprintf(out, " SMTP_HOST=smtp.mailgun.org:587 SMTP_FROM=you@mg.domain SMTP_TO=you@gmail.com smtp-test\n")
|
||||
fmt.Fprintf(out, " smtp-test -host smtp.gmail.com:587 -from you@gmail.com -to debug@yourself.com\n")
|
||||
}
|
||||
|
||||
if len(os.Args) > 1 {
|
||||
switch os.Args[1] {
|
||||
case "-V", "version", "-version", "--version":
|
||||
printVersion()
|
||||
return
|
||||
case "help", "-help", "--help":
|
||||
mainFlags.SetOutput(os.Stdout)
|
||||
mainFlags.Usage()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := mainFlags.Parse(os.Args[1:]); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
mainFlags.SetOutput(os.Stderr)
|
||||
mainFlags.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if cfg.showVersion {
|
||||
printVersion()
|
||||
return
|
||||
}
|
||||
|
||||
// Required fields
|
||||
if cfg.from == "" || cfg.to == "" || cfg.host == "" {
|
||||
fmt.Fprintln(os.Stderr, "Missing required parameters: --from, --to, --host (or matching env vars)")
|
||||
mainFlags.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
user := cfg.user // usually same as from for plain auth
|
||||
pass, hasPass := os.LookupEnv("SMTP_PASS") // SMTP_PASS to be consistent style with your SMB_PASSWORD
|
||||
if !hasPass {
|
||||
fmt.Fprintf(os.Stderr, "SMTP_PASS is not set → ")
|
||||
fmt.Print("Password: ")
|
||||
password, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to read password: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
pass = strings.TrimRight(string(password), "\r\n \t")
|
||||
}
|
||||
|
||||
fmt.Printf("Trying to send from %s → %s via %s ...\n", cfg.from, cfg.to, cfg.host)
|
||||
|
||||
trySMTP(cfg.host, cfg.from, user, pass, cfg.to, cfg.subject, cfg.body)
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
func trySMTP(addr, from, username, password, to, subject, body string) {
|
||||
// Most modern SMTP servers expect 587 + STARTTLS (not native 465 SSL)
|
||||
// net/smtp.SendMail automatically attempts STARTTLS when available.
|
||||
auth := smtp.PlainAuth("", username, password, strings.Split(addr, ":")[0])
|
||||
|
||||
// Build minimal RFC-compliant message
|
||||
msg := (fmt.Appendf(
|
||||
[]byte{},
|
||||
"To: %s\r\n"+
|
||||
"From: %s\r\n"+
|
||||
"Subject: %s\r\n"+
|
||||
"\r\n"+
|
||||
"%s\r\n",
|
||||
to, from, subject, body,
|
||||
))
|
||||
|
||||
err := smtp.SendMail(addr, auth, from, []string{to}, msg)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "SMTP error: %v\n", err)
|
||||
// Common helpful hints
|
||||
if strings.Contains(err.Error(), "535") || strings.Contains(err.Error(), "authentication") {
|
||||
fmt.Fprintln(os.Stderr, "→ Check username/password (Gmail may need app password)")
|
||||
}
|
||||
if strings.Contains(err.Error(), "STARTTLS") || strings.Contains(err.Error(), "TLS") {
|
||||
fmt.Fprintln(os.Stderr, "→ Server may require TLS — try port 587 instead of 465, or vice versa")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Success! Email sent:\n%s\n", string(msg))
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user