Go SMTP Integration

Send emails using Go’s built-in net/smtp package with Shoutbox’s SMTP server.

Without Our Library

Using only Go’s standard library net/smtp package:

package main

import (
    "fmt"
    "net/smtp"
)

func main() {
    // SMTP server configuration
    host := "smtp.shoutbox.net"
    port := 587
    auth := smtp.PlainAuth(
        "",
        "shoutbox",
        "YOUR_API_KEY",
        host,
    )

    // Email content
    from := "[email protected]"
    to := []string{"[email protected]"}
    msg := []byte("From: [email protected]\r\n" +
        "To: [email protected]\r\n" +
        "Subject: Hello World\r\n" +
        "Content-Type: text/html; charset=UTF-8\r\n" +
        "\r\n" +
        "<h1>Welcome!</h1>\r\n")

    err := smtp.SendMail(
        fmt.Sprintf("%s:%d", host, port),
        auth,
        from,
        to,
        msg,
    )
    if err != nil {
        panic(err)
    }
}

With Our Library

For a more feature-rich experience, use our Go library which provides type safety, attachments, and easier email composition:

Installation

go get github.com/shoutboxnet/shoutbox-go/shoutbox

Quick Start

package main

import (
    "log"
    "os"
    "github.com/shoutboxnet/shoutbox-go/shoutbox"
)

func main() {
    client := shoutbox.NewSMTPClient(os.Getenv("SHOUTBOX_API_KEY"))

    msg := &shoutbox.EmailMessage{
        From:    "[email protected]",
        To:      []string{"[email protected]"},
        Subject: "Hello World",
        HTML:    "<h1>Welcome!</h1>",
    }

    err := client.SendEmail(msg)
    if err != nil {
        log.Fatal(err)
    }
}

Library Implementation Details

SMTP Client Structure

// This is what the library provides internally
package shoutbox

import (
    "bytes"
    "encoding/base64"
    "fmt"
    "mime/multipart"
    "net/smtp"
    "net/textproto"
    "os"
    "path/filepath"
    "strings"
)

type SMTPClient struct {
    Host     string
    Port     int
    Username string
    Password string
    Auth     smtp.Auth
}

func NewSMTPClient(apiKey string) *SMTPClient {
    host := "smtp.shoutbox.net"
    return &SMTPClient{
        Host:     host,
        Port:     587,
        Username: "shoutbox",
        Password: apiKey,
        Auth:     smtp.PlainAuth("", "shoutbox", apiKey, host),
    }
}

type EmailMessage struct {
    From        string
    To          []string
    Subject     string
    HTML        string
    Name        string
    ReplyTo     string
    Attachments []Attachment
    Headers     map[string]string
}

type Attachment struct {
    Filename    string
    Content     []byte
    ContentType string
}

func (c *SMTPClient) SendEmail(msg *EmailMessage) error {
    buffer := &bytes.Buffer{}
    writer := multipart.NewWriter(buffer)

    // Add headers
    headers := textproto.MIMEHeader{}
    headers.Set("From", formatAddress(msg.From, msg.Name))
    headers.Set("To", strings.Join(msg.To, ", "))
    headers.Set("Subject", msg.Subject)
    headers.Set("MIME-Version", "1.0")
    headers.Set("Content-Type", fmt.Sprintf("multipart/mixed; boundary=%s", writer.Boundary()))

    if msg.ReplyTo != "" {
        headers.Set("Reply-To", msg.ReplyTo)
    }

    // Add custom headers
    for key, value := range msg.Headers {
        headers.Set(key, value)
    }

    // Write headers
    for key, values := range headers {
        for _, value := range values {
            fmt.Fprintf(buffer, "%s: %s\r\n", key, value)
        }
    }
    buffer.WriteString("\r\n")

    // Add HTML part
    htmlPart, err := writer.CreatePart(textproto.MIMEHeader{
        "Content-Type":              {"text/html; charset=UTF-8"},
        "Content-Transfer-Encoding": {"quoted-printable"},
    })
    if err != nil {
        return fmt.Errorf("error creating HTML part: %w", err)
    }
    htmlPart.Write([]byte(msg.HTML))

    // Add attachments
    for _, attachment := range msg.Attachments {
        part, err := writer.CreatePart(textproto.MIMEHeader{
            "Content-Type":              {fmt.Sprintf("%s; name=%q", attachment.ContentType, attachment.Filename)},
            "Content-Disposition":       {fmt.Sprintf("attachment; filename=%q", attachment.Filename)},
            "Content-Transfer-Encoding": {"base64"},
        })
        if err != nil {
            return fmt.Errorf("error creating attachment part: %w", err)
        }

        encoder := base64.NewEncoder(base64.StdEncoding, part)
        encoder.Write(attachment.Content)
        encoder.Close()
    }

    writer.Close()

    // Send email
    err = smtp.SendMail(
        fmt.Sprintf("%s:%d", c.Host, c.Port),
        c.Auth,
        msg.From,
        msg.To,
        buffer.Bytes(),
    )
    if err != nil {
        return fmt.Errorf("error sending email: %w", err)
    }

    return nil
}

func formatAddress(email, name string) string {
    if name == "" {
        return email
    }
    return fmt.Sprintf("%s <%s>", name, email)
}

Basic Request Structure

FieldTypeRequiredDescription
fromstringYesSender email address
to[]stringYesRecipient email address(es)
subjectstringYesEmail subject line
htmlstringYesHTML content of the email
namestringNoSender name
replyTostringNoReply-to email address

Recipients

Multiple Recipients

client := shoutbox.NewSMTPClient(os.Getenv("SHOUTBOX_API_KEY"))

msg := &shoutbox.EmailMessage{
    From:    "[email protected]",
    To:      []string{"[email protected]", "[email protected]"},
    Subject: "Team Update",
    HTML:    "<h1>Important Announcement</h1>",
}

err := client.SendEmail(msg)
if err != nil {
    log.Fatal(err)
}

Named Recipients

msg := &shoutbox.EmailMessage{
    From:    "[email protected]",
    Name:    "Notification System",
    To:      []string{"John Doe <[email protected]>", "Jane Smith <[email protected]>"},
    Subject: "Team Meeting",
    HTML:    "<h1>Meeting Invitation</h1>",
}

err := client.SendEmail(msg)
if err != nil {
    log.Fatal(err)
}

Reply-To Address

msg := &shoutbox.EmailMessage{
    From:    "[email protected]",
    To:      []string{"[email protected]"},
    ReplyTo: "Support Team <[email protected]>",
    Subject: "Support Ticket Update",
    HTML:    "<h1>Your ticket has been updated</h1>",
}

err := client.SendEmail(msg)
if err != nil {
    log.Fatal(err)
}

Attachments

Helper Functions for Attachments

// shoutbox/helpers.go
package shoutbox

import (
    "fmt"
    "io"
    "mime"
    "os"
    "path/filepath"
)

func NewAttachmentFromFile(filepath string) (Attachment, error) {
    content, err := os.ReadFile(filepath)
    if err != nil {
        return Attachment{}, fmt.Errorf("error reading file: %w", err)
    }

    // Detect content type
    contentType := mime.TypeByExtension(filepath.Ext(filepath))
    if contentType == "" {
        contentType = "application/octet-stream"
    }

    return Attachment{
        Filename:    filepath.Base(filepath),
        Content:     content,
        ContentType: contentType,
    }, nil
}

func NewAttachmentFromReader(reader io.Reader, filename string) (Attachment, error) {
    content, err := io.ReadAll(reader)
    if err != nil {
        return Attachment{}, fmt.Errorf("error reading content: %w", err)
    }

    contentType := mime.TypeByExtension(filepath.Ext(filename))
    if contentType == "" {
        contentType = "application/octet-stream"
    }

    return Attachment{
        Filename:    filename,
        Content:     content,
        ContentType: contentType,
    }, nil
}

Complete Example with Attachments

// Create attachments
pdfAttachment, err := shoutbox.NewAttachmentFromFile("january_report.pdf")
if err != nil {
    log.Fatal(err)
}

xlsAttachment, err := shoutbox.NewAttachmentFromFile("data.xlsx")
if err != nil {
    log.Fatal(err)
}

msg := &shoutbox.EmailMessage{
    From:        "[email protected]",
    Name:        "Reports Team",
    To:          []string{"John Smith <[email protected]>"},
    Subject:     "Monthly Report - January 2024",
    HTML:        "<h1>Monthly Report</h1><p>Please find your report attached.</p>",
    Attachments: []shoutbox.Attachment{pdfAttachment, xlsAttachment},
}

err = client.SendEmail(msg)
if err != nil {
    log.Fatal(err)
}

Custom Headers

Complete Example with Headers

msg := &shoutbox.EmailMessage{
    From:    "[email protected]",
    Name:    "Newsletter Team",
    To:      []string{"Subscriber <[email protected]>"},
    ReplyTo: "Newsletter Support <[email protected]>",
    Subject: "Your Weekly Newsletter",
    HTML:    "<h1>This Week's Updates</h1><p>Latest news and updates...</p>",
    Headers: map[string]string{
        "List-Unsubscribe":          "<https://yourdomain.com/unsubscribe>",
        "List-Unsubscribe-Post":     "List-Unsubscribe=One-Click",
        "X-Campaign-ID":             "newsletter_2024_01",
        "X-Mailer":                  "ShoutboxAPI/1.0",
        "Precedence":                "bulk",
        "X-Auto-Response-Suppress":   "OOF, AutoReply",
    },
}

err := client.SendEmail(msg)
if err != nil {
    log.Fatal(err)
}

Security Best Practices

API Key Management

Use environment variables for API keys:

apiKey := os.Getenv("SHOUTBOX_API_KEY")
if apiKey == "" {
    log.Fatal("SHOUTBOX_API_KEY environment variable is not set")
}

Error Handling

Implement proper error handling:

type SMTPError struct {
    Op  string
    Err error
}

func (e *SMTPError) Error() string {
    return fmt.Sprintf("smtp error during %s: %v", e.Op, e.Err)
}

func (e *SMTPError) Unwrap() error {
    return e.Err
}

// Usage
if err := client.SendEmail(msg); err != nil {
    var smtpErr *SMTPError
    if errors.As(err, &smtpErr) {
        log.Printf("SMTP error: %v", err)
    } else {
        log.Printf("Unknown error: %v", err)
    }
    return
}

Rate Limiting

Use rate limiting:

import "golang.org/x/time/rate"

type RateLimitedSMTPClient struct {
    *SMTPClient
    limiter *rate.Limiter
}

func NewRateLimitedSMTPClient(apiKey string, rps float64) *RateLimitedSMTPClient {
    return &RateLimitedSMTPClient{
        SMTPClient: NewSMTPClient(apiKey),
        limiter:    rate.NewLimiter(rate.Limit(rps), 1),
    }
}

func (c *RateLimitedSMTPClient) SendEmail(msg *EmailMessage) error {
    if err := c.limiter.Wait(context.Background()); err != nil {
        return err
    }
    return c.SMTPClient.SendEmail(msg)
}

Email Validation

Validate email addresses:

import "net/mail"

func validateEmail(email string) error {
    _, err := mail.ParseAddress(email)
    return err
}

func validateEmailList(emails []string) error {
    for _, email := range emails {
        if err := validateEmail(email); err != nil {
            return fmt.Errorf("invalid email %q: %w", email, err)
        }
    }
    return nil
}

Rate Limits

The same rate limits apply to both SMTP and REST API usage:

  • 60 requests per minute per API key
  • Maximum attachment size: 10MB
  • Maximum recipients per email: 50

Please contact support if you need higher limits for your use case.

Support

For additional support or questions, please contact our support team.