some more email work for smtp errors
This commit is contained in:
@@ -1,25 +1,19 @@
|
||||
package email
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/smtp"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rideaware/admin-panel/internal/config"
|
||||
"github.com/rideaware/admin-panel/internal/database"
|
||||
|
||||
"github.com/wneessen/go-mail"
|
||||
)
|
||||
|
||||
// SendUpdate sends a newsletter with the given subject and body to all subscriber emails stored in the database.
|
||||
// It returns a human-readable status message and, when subscriber retrieval fails, the underlying error.
|
||||
// - If retrieving subscribers fails: returns "Failed to retrieve subscribers" and the error.
|
||||
// - If no subscribers are found: returns "No subscribers found." and nil.
|
||||
// - If sending to a specific subscriber fails: returns "Failed to send to <email>" and nil.
|
||||
// - On success: returns "Email has been sent to all subscribers." and nil.
|
||||
// Note: logging the newsletter entry in the database is attempted after sending and any logging failure is non-fatal.
|
||||
func SendUpdate(subject, body string) (string, error) {
|
||||
subscribers, err := database.GetAllEmails()
|
||||
if err != nil {
|
||||
@@ -28,6 +22,7 @@ func SendUpdate(subject, body string) (string, error) {
|
||||
if len(subscribers) == 0 {
|
||||
return "No subscribers found.", nil
|
||||
}
|
||||
|
||||
var succeeded, failed int
|
||||
for _, email := range subscribers {
|
||||
if send(subject, body, email) {
|
||||
@@ -36,88 +31,118 @@ func SendUpdate(subject, body string) (string, error) {
|
||||
failed++
|
||||
}
|
||||
}
|
||||
|
||||
if err := database.LogNewsletter(subject, body); err != nil {
|
||||
log.Printf("Error logging newsletter: %v", err)
|
||||
}
|
||||
|
||||
if failed == 0 {
|
||||
return fmt.Sprintf("Email sent to all %d subscribers.", succeeded), nil
|
||||
}
|
||||
return fmt.Sprintf("Sent to %d/%d subscribers; %d failed.", succeeded, succeeded+failed, failed), nil
|
||||
}
|
||||
|
||||
// send constructs and sends an HTML newsletter update to the specified recipient using the current SMTP configuration.
|
||||
// It embeds an unsubscribe link for the recipient and returns true if the message was sent successfully, false if client creation, message setup, or sending fails.
|
||||
// send constructs and sends an HTML newsletter update to the specified recipient
|
||||
func send(subject, body, recipient string) bool {
|
||||
cfg := config.Current
|
||||
|
||||
var opts []mail.ClientOption
|
||||
opts = append(opts,
|
||||
mail.WithPort(cfg.SMTPPort),
|
||||
mail.WithSMTPAuth(mail.SMTPAuthPlain),
|
||||
mail.WithUsername(cfg.SMTPUser),
|
||||
mail.WithPassword(cfg.SMTPPassword),
|
||||
mail.WithTimeout(10*time.Second),
|
||||
)
|
||||
log.Printf("Attempting to send email to %s via %s:%d", recipient, cfg.SMTPServer, cfg.SMTPPort)
|
||||
|
||||
// Use SSL for port 465, STARTTLS for others
|
||||
if cfg.SMTPPort == 465 {
|
||||
opts = append(opts, mail.WithSSL())
|
||||
} else {
|
||||
opts = append(opts, mail.WithTLSPolicy(mail.TLSMandatory))
|
||||
}
|
||||
|
||||
client, err := mail.NewClient(cfg.SMTPServer, opts...)
|
||||
if err != nil {
|
||||
log.Printf("Failed to create mail client: %v", err)
|
||||
return false
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
m := mail.NewMsg()
|
||||
if err := m.From(cfg.SenderEmail); err != nil {
|
||||
log.Printf("Failed to set from: %v", err)
|
||||
return false
|
||||
}
|
||||
if err := m.To(recipient); err != nil {
|
||||
log.Printf("Failed to set to: %v", err)
|
||||
return false
|
||||
}
|
||||
m.Subject(subject)
|
||||
addr := fmt.Sprintf("%s:%d", cfg.SMTPServer, cfg.SMTPPort)
|
||||
|
||||
unsubLink := fmt.Sprintf("https://%s/unsubscribe?email=%s",
|
||||
cfg.BaseURL, url.QueryEscape(recipient))
|
||||
|
||||
// Build HTML body with unsubscribe link
|
||||
htmlBody := buildHTMLBody(body, unsubLink)
|
||||
m.SetBodyString(mail.TypeTextHTML, htmlBody)
|
||||
message := buildMessage(cfg.SenderEmail, recipient, subject, htmlBody)
|
||||
|
||||
if err := client.Send(m); err != nil {
|
||||
log.Printf("Failed to send email to %s: %v", recipient, err)
|
||||
// Create TLS connection
|
||||
tlsconfig := &tls.Config{
|
||||
ServerName: cfg.SMTPServer,
|
||||
}
|
||||
|
||||
conn, err := tls.Dial("tcp", addr, tlsconfig)
|
||||
if err != nil {
|
||||
log.Printf("Failed to connect to SMTP %s: %v", addr, err)
|
||||
return false
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Create SMTP client
|
||||
client, err := smtp.NewClient(conn, cfg.SMTPServer)
|
||||
if err != nil {
|
||||
log.Printf("Failed to create SMTP client: %v", err)
|
||||
return false
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// Authenticate
|
||||
auth := smtp.PlainAuth("", cfg.SMTPUser, cfg.SMTPPassword, cfg.SMTPServer)
|
||||
if err := client.Auth(auth); err != nil {
|
||||
log.Printf("SMTP auth failed for %s: %v", cfg.SMTPUser, err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Send the email
|
||||
if err := client.Mail(cfg.SenderEmail); err != nil {
|
||||
log.Printf("MAIL command failed: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if err := client.Rcpt(recipient); err != nil {
|
||||
log.Printf("RCPT command failed for %s: %v", recipient, err)
|
||||
return false
|
||||
}
|
||||
|
||||
w, err := client.Data()
|
||||
if err != nil {
|
||||
log.Printf("DATA command failed: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
_, err = w.Write([]byte(message))
|
||||
if err != nil {
|
||||
log.Printf("Failed to write message: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
log.Printf("Failed to close DATA: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
client.Quit()
|
||||
|
||||
log.Printf("Update email sent to: %s", recipient)
|
||||
return true
|
||||
}
|
||||
|
||||
func buildMessage(from, to, subject, body string) string {
|
||||
msg := fmt.Sprintf("From: %s\r\n", from)
|
||||
msg += fmt.Sprintf("To: %s\r\n", to)
|
||||
msg += fmt.Sprintf("Subject: %s\r\n", subject)
|
||||
msg += "MIME-Version: 1.0\r\n"
|
||||
msg += "Content-Type: text/html; charset=\"utf-8\"\r\n"
|
||||
msg += "\r\n"
|
||||
msg += body
|
||||
return msg
|
||||
}
|
||||
|
||||
// buildHTMLBody constructs the final HTML email body by appending an unsubscribe footer to the user-provided content.
|
||||
// It handles both complete HTML documents and HTML fragments.
|
||||
func buildHTMLBody(body, unsubLink string) string {
|
||||
footer := fmt.Sprintf(
|
||||
"<br><br><hr><p style='font-size: 12px; color: #666;'>If you ever wish to unsubscribe, "+
|
||||
"please click <a href='%s'>here</a>.</p>",
|
||||
unsubLink)
|
||||
|
||||
// If body contains closing html tag, insert before it
|
||||
if strings.Contains(strings.ToLower(body), "</html>") {
|
||||
return strings.Replace(body, "</html>", footer+"</html>", 1)
|
||||
}
|
||||
|
||||
// If body contains closing body tag, insert before it
|
||||
if strings.Contains(strings.ToLower(body), "</body>") {
|
||||
return strings.Replace(body, "</body>", footer+"</body>", 1)
|
||||
}
|
||||
|
||||
// Otherwise just append
|
||||
return body + footer
|
||||
}
|
||||
Reference in New Issue
Block a user