SMTP Email Sender with Attachments and HTML Support
SMTP client helper that sends text or HTML emails with attachments, CC or BCC, and TLS configuration.
main.go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
package main
import (
"bytes"
"crypto/tls"
"encoding/base64"
"fmt"
"io/ioutil"
"mime/multipart"
"net/smtp"
"path/filepath"
"strings"
)
// EmailConfig contains SMTP server configuration and email details
type EmailConfig struct {
SMTPHost string
SMTPPort int
Username string
Password string
From string
To []string
CC []string
BCC []string
Subject string
Body string
IsHTML bool
Attachments []string
}
// SendEmail sends an email with optional attachments and HTML content
func SendEmail(config EmailConfig) error {
// Create email headers
headers := make(map[string]string)
headers["From"] = config.From
headers["To"] = strings.Join(config.To, ",")
if len(config.CC) > 0 {
headers["Cc"] = strings.Join(config.CC, ",")
}
headers["Subject"] = config.Subject
headers["MIME-Version"] = "1.0"
// Create multipart writer
var body bytes.Buffer
writer := multipart.NewWriter(&body)
boundary := writer.Boundary()
headers["Content-Type"] = fmt.Sprintf("multipart/mixed; boundary=\"%s\"", boundary)
// Write email body
contentType := "text/plain; charset=utf-8"
if config.IsHTML {
contentType = "text/html; charset=utf-8"
}
part, err := writer.CreatePart(map[string]string{
"Content-Type": contentType,
})
if err != nil {
return fmt.Errorf("failed to create body part: %w", err)
}
_, err = part.Write([]byte(config.Body))
if err != nil {
return fmt.Errorf("failed to write body: %w", err)
}
// Add attachments
for _, file := range config.Attachments {
if err := addAttachment(writer, file); err != nil {
return fmt.Errorf("failed to add attachment %s: %w", file, err)
}
}
// Close writer to finalize multipart message
if err := writer.Close(); err != nil {
return fmt.Errorf("failed to close writer: %w", err)
}
// Combine all recipients
recipients := append(config.To, config.CC...)
recipients = append(recipients, config.BCC...)
// Prepare auth
auth := smtp.PlainAuth("", config.Username, config.Password, config.SMTPHost)
// Connect to SMTP server with TLS
addr := fmt.Sprintf("%s:%d", config.SMTPHost, config.SMTPPort)
tlsConfig := &tls.Config{
ServerName: config.SMTPHost,
}
conn, err := tls.Dial("tcp", addr, tlsConfig)
if err != nil {
return fmt.Errorf("tls dial failed: %w", err)
}
client, err := smtp.NewClient(conn, config.SMTPHost)
if err != nil {
return fmt.Errorf("smtp client creation failed: %w", err)
}
defer client.Close()
// Authenticate
if err := client.Auth(auth); err != nil {
return fmt.Errorf("authentication failed: %w", err)
}
// Set sender and recipients
if err := client.Mail(config.From); err != nil {
return fmt.Errorf("failed to set sender: %w", err)
}
for _, rec := range recipients {
if err := client.Rcpt(rec); err != nil {
return fmt.Errorf("failed to set recipient %s: %w", rec, err)
}
}
// Send email body
writer, err = client.Data()
if err != nil {
return fmt.Errorf("failed to get data writer: %w", err)
}
// Write headers
for k, v := range headers {
_, err := writer.Write([]byte(fmt.Sprintf("%s: %s\r\n", k, v)))
if err != nil {
return fmt.Errorf("failed to write header %s: %w", k, err)
}
}
// Write empty line to separate headers from body
_, err = writer.Write([]byte("\r\n"))
if err != nil {
return fmt.Errorf("failed to write header-body separator: %w", err)
}
// Write body
_, err = writer.Write(body.Bytes())
if err != nil {
return fmt.Errorf("failed to write email body: %w", err)
}
// Close writer to send email
if err := writer.Close(); err != nil {
return fmt.Errorf("failed to close data writer: %w", err)
}
return client.Quit()
}
// addAttachment adds a file attachment to the email
func addAttachment(writer *multipart.Writer, filePath string) error {
fileData, err := ioutil.ReadFile(filePath)
if err != nil {
return fmt.Errorf("failed to read file: %w", err)
}
fileName := filepath.Base(filePath)
part, err := writer.CreatePart(map[string]string{
"Content-Type": "application/octet-stream",
"Content-Disposition": fmt.Sprintf("attachment; filename=\"%s\"", fileName),
"Content-Transfer-Encoding": "base64",
})
if err != nil {
return fmt.Errorf("failed to create attachment part: %w", err)
}
encoder := base64.NewEncoder(base64.StdEncoding, part)
defer encoder.Close()
_, err = encoder.Write(fileData)
if err != nil {
return fmt.Errorf("failed to encode attachment: %w", err)
}
return nil
}
func main() {
config := EmailConfig{
SMTPHost: "smtp.gmail.com",
SMTPPort: 465,
Username: "[email protected]",
Password: "your-app-password",
From: "[email protected]",
To: []string{"[email protected]"},
CC: []string{"[email protected]"},
Subject: "Test Email with Attachment",
Body: "<h1>Hello!</h1><p>This is an HTML email with attachment.</p>",
IsHTML: true,
Attachments: []string{"document.pdf"},
}
if err := SendEmail(config); err != nil {
fmt.Printf("Failed to send email: %v\n", err)
} else {
fmt.Println("Email sent successfully!")
}
}How It Works
SMTP client helper that sends text or HTML emails with attachments, CC or BCC, and TLS configuration.
Builds MIME messages with boundaries, attaches files by reading disk content, sets headers for recipients including CC and BCC, connects over TLS using net/smtp, and sends the composed message.
Key Concepts
- 1Supports multipart or alternative bodies for HTML plus plain text.
- 2Attachment encoding handles arbitrary file types safely.
- 3TLS dialer ensures credentials and content are encrypted in transit.
When to Use This Pattern
- Transactional email sending from backend services.
- Automated reports with PDF or CSV attachments.
- Testing SMTP integrations locally with mock servers.
Best Practices
- Use application-specific passwords or secrets from vaults.
- Set proper From and Reply-To headers to reduce spam scoring.
- Retry transient SMTP errors with backoff.
Go Version1.16
Difficultyintermediate
Production ReadyYes
Lines of Code199