package utility import ( "context" "crypto/tls" "net" "net/smtp" "strings" "time" "github.com/gogf/gf/v2/errors/gerror" ) const DefaultExternalRequestTimeout = 10 * time.Second func ExternalRequestTimeout(configured string) time.Duration { timeout, err := time.ParseDuration(strings.TrimSpace(configured)) if err != nil || timeout <= 0 { return DefaultExternalRequestTimeout } return timeout } type SMTPOptions struct { Address string Host string Auth smtp.Auth From string To []string Data []byte SSL bool Timeout time.Duration } func SendSMTP(ctx context.Context, options SMTPOptions) (err error) { if ctx == nil { ctx = context.Background() } timeout := options.Timeout if timeout <= 0 { timeout = DefaultExternalRequestTimeout } ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() defer func() { if err != nil && ctx.Err() != nil { err = gerror.Wrap(ctx.Err(), "SMTP request canceled or timed out") } }() dialer := &net.Dialer{Timeout: timeout} conn, err := dialer.DialContext(ctx, "tcp", options.Address) if err != nil { return gerror.Wrap(err, "connect SMTP server failed") } defer conn.Close() stop := context.AfterFunc(ctx, func() { _ = conn.Close() }) defer stop() deadline, _ := ctx.Deadline() if err = conn.SetDeadline(deadline); err != nil { return gerror.Wrap(err, "set SMTP deadline failed") } tlsConfig := &tls.Config{ServerName: options.Host, MinVersion: tls.VersionTLS12} smtpConn := conn if options.SSL { tlsConn := tls.Client(conn, tlsConfig) if err = tlsConn.HandshakeContext(ctx); err != nil { return gerror.Wrap(err, "SMTP TLS handshake failed") } smtpConn = tlsConn } client, err := smtp.NewClient(smtpConn, options.Host) if err != nil { return gerror.Wrap(err, "read SMTP greeting failed") } defer client.Close() if !options.SSL { if available, _ := client.Extension("STARTTLS"); available { if err = client.StartTLS(tlsConfig); err != nil { return gerror.Wrap(err, "SMTP STARTTLS failed") } } } if options.Auth != nil { if err = client.Auth(options.Auth); err != nil { return gerror.Wrap(err, "SMTP authentication failed") } } if err = client.Mail(options.From); err != nil { return gerror.Wrap(err, "SMTP sender rejected") } for _, recipient := range options.To { if err = client.Rcpt(recipient); err != nil { return gerror.Wrap(err, "SMTP recipient rejected") } } writer, err := client.Data() if err != nil { return gerror.Wrap(err, "start SMTP message failed") } if _, err = writer.Write(options.Data); err != nil { _ = writer.Close() return gerror.Wrap(err, "write SMTP message failed") } if err = writer.Close(); err != nil { return gerror.Wrap(err, "SMTP message rejected") } return client.Quit() }