Easy Way To Switch Email Driver in Go

AriefArief
3 min read

In this article, we will explore a Go implementation for sending emails using different drivers (AWS SES and SMTP). We will discuss the patterns used, how to use the implementation.

Patterns Used

1. Builder Pattern

The builder pattern is used to create different configurations for sending emails. Depending on the environment configuration, the appropriate email driver (AWS SES or SMTP) is instantiated.

2. Interface Segregation

Interfaces are used to define contracts for sending emails. This allows for flexibility and easy switching between different email drivers.

3. Dependency Injection

Configuration values are injected into the structs, making the code more modular and testable.

Code Explanation

Configuration Structs

We have two main configuration structs: AwsConfig and SmtpConfig.

type AwsConfig struct {
    AccessKeyID     string
    SecretAccessKey string
    Region          string
    Session         *session.Session
}

type SmtpConfig struct {
    Host     string
    Port     int
    Username string
    Password string
}

Creating Configurations

The NewAwsConfig and NewSmtpConfig functions create instances of these configurations using values from the environment.

func NewAwsConfig(configuration ViperConfig) *AwsConfig {
    return &AwsConfig{
        AccessKeyID:     configuration.GetEnv("AWS_ACCESS_KEY"),
        SecretAccessKey: configuration.GetEnv("AWS_SECRET_KEY"),
        Region:          configuration.GetEnv("AWS_REGION"),
    }
}

func NewSmtpConfig(configuration ViperConfig) *SmtpConfig {
    portInt, _ := strconv.ParseInt(configuration.GetEnv("SMTP_PORT"), 10, 32)
    return &SmtpConfig{
        Host:     configuration.GetEnv("SMTP_HOST"),
        Port:     int(portInt),
        Username: configuration.GetEnv("SMTP_USERNAME"),
        Password: configuration.GetEnv("SMTP_PASSWORD"),
    }
}

Sending Emails

Both AwsConfig and SmtpConfig implement the SendEmail method to send emails using their respective drivers.

AWS SES

func (c *AwsConfig) SendEmail(to string, subject string, body string) error {
    if err := c.validateCredentials(); err != nil {
        return err
    }

    if c.Session == nil {
        return errors.New("AWS session not set")
    }

    svc := ses.New(c.Session)
    input := c.createSendEmailInput(to, subject, body)

    response, err := svc.SendEmail(input)
    if err != nil {
        c.logSendEmailError(to, subject, err)
        return err
    }

    log.Printf("Email: %s - Subject: %s - MessageID: %s", to, subject, response.String())
    return nil
}

SMTP

func (c *SmtpConfig) SendEmail(to string, subject string, body string) error {
    gm := gomail.NewMessage()
    gm.SetAddressHeader("From", "arief@test.com", "Arief")
    gm.SetHeader("To", to)
    gm.SetHeader("Subject", subject)
    gm.SetBody("text/html", body)

    dialer := gomail.NewDialer(c.Host, c.Port, c.Username, c.Password)
    if err := dialer.DialAndSend(gm); err != nil {
        return err
    }

    return nil
}

Builder Pattern for Email Configuration

The EmailConfig struct uses the builder pattern to create the appropriate email configuration based on the environment.

type EmailConfig struct {
    AwsConfig     *AwsConfig
    SmtpConfig    *SmtpConfig
    configuration ViperConfig
}

func NewEmailConfig() *EmailConfig {
    configuration := NewViperConfig()
    driver := configuration.GetEnv("EMAIL_DRIVER")
    switch driver {
    case "aws":
        return &EmailConfig{
            AwsConfig:     NewAwsConfig(configuration),
            configuration: configuration,
        }
    case "smtp":
        return &EmailConfig{
            SmtpConfig:    NewSmtpConfig(configuration),
            configuration: configuration,
        }
    default:
        panic("Email driver not set")
    }
}

Sending Emails Using the Configured Driver

The SendEmail method in EmailConfig sends an email using the configured driver.

func (c *EmailConfig) SendEmail(to string, subject string, body string) error {
    if c.AwsConfig != nil {
        if err := c.AwsConfig.SetNewSession(c.configuration); err != nil {
            log.Println("Error setting new session", err)
            return err
        }
        return c.AwsConfig.SendEmail(to, subject, body)
    }

    if c.SmtpConfig != nil {
        return c.SmtpConfig.SendEmail(to, subject, body)
    }

    return errors.New("Email driver not set")
}

How to Use

  1. Set Environment Variables: Ensure that the necessary environment variables are set for your chosen email driver (AWS SES or SMTP).

  2. Instantiate EmailConfig: Create an instance of EmailConfig using the NewEmailConfig function.

  3. Send Email: Use the SendEmail method to send an email.

func main() {
    emailConfig := NewEmailConfig()
    err := emailConfig.SendEmail("recipient@example.com", "Test Subject", "Test Body")
    if err != nil {
        log.Fatalf("Failed to send email: %v", err)
    }
}

Conclusion

In this article, we explored a Go implementation for sending emails using different drivers (AWS SES and SMTP). We discussed the patterns used, how to use the implementation. This modular and flexible approach allows for easy switching between different email drivers and makes the code more maintainable.

0
Subscribe to my newsletter

Read articles from Arief directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Arief
Arief

I am love to sharing everything i know about Web Developer especially for Backend Web Developer.