Caesar Mail

Learn how to abstract email services using the Caesar Mail package.

Overview

The Caesar Mail package provides an abstraction over email services such as Resend, AWS SES, and SMTP. This guide will walk you through setting up and using Caesar Mail in your application.

Setting Up Caesar Mail

Step 1: Install the Dependency

First, download the Caesar Mail package dependency:

go get -u github.com/caesar-rocks/mail

Step 2: Validate Environment Variables

Configure the necessary environment variables in the ./config/env.go file. Here's how to do it for Resend:

package config

import (
	"github.com/caesar-rocks/core"
)

// EnvironmentVariables is a struct that holds all the environment variables that need to be validated.
type EnvironmentVariables struct {
	RESEND_KEY string `validate:"required"`
	
	// Other environment variables...
}

func ProvideEnvironmentVariables() *EnvironmentVariables {
	return core.ValidateEnvironmentVariables[EnvironmentVariables]()
}

Step 3: Add Mailer Configuration

Next, create a ./config/mailer.go file to configure the Mailer service:

package config

import mailer "github.com/caesar-rocks/mail"

func ProvideMailer(env *EnvironmentVariables) *mailer.Mailer {
	return mailer.NewMailer(mailer.MailCfg{
		APIService: mailer.RESEND,
		APIKey:     env.RESEND_KEY,
	})
}

Step 4: Register Mailer as a Provider

Finally, register the Mailer service as a provider in your ./config/app.go file:

package config

import (
	"myapp/app/repositories"

	"github.com/caesar-rocks/core"
)

func ProvideApp() *core.App {
	app := core.NewApp(&core.AppConfig{
		Addr: ":8080",
	})

	env := ProvideEnvironmentVariables()
	mailer := ProvideMailer(env)

	// Register providers
	app.RegisterProviders(
		ProvideEnvironmentVariables,
		ProvideMailer,
		// Other providers...
	)

	// Other invokers...

	return app
}

Creating Mail Templates

You can create mail templates using Go Templ. Here’s an example of a forgot password email template:

package mails

templ ForgotPasswordMail(name string, url string) {
	<p>
		Hi  { name },
		<br/>
		<br/>
		You have requested to reset your password. Please <a href={ templ.SafeURL(url) }>click here</a> to reset your password.
		<br/>
		<br/>
		If you did not request to reset your password, please ignore this email.
		<br/>
		<br/>
		Thanks,
		<br/>
		The Software Citadel Team.
	</p>
}

Save this template in the ./views/mails/forgot_password.templ file.

Using Caesar Mail in Controllers

To demonstrate how to send an email using Caesar Mail, let’s consider a scenario where a user wants to reset their password.

Forgot Password Controller

Define a controller to handle the forgot password functionality:

package authControllers

import (
	"bytes"
	"citadel/app/repositories"
	"citadel/views/mails"
	authPages "citadel/views/pages/auth"
	"log/slog"

	caesar "github.com/caesar-rocks/core"
	mailer "github.com/caesar-rocks/mail"
)

type ForgotPwdController struct {
	mailer *mailer.Mailer
	repo   *repositories.UsersRepository
}

func NewForgotPwdController(mailer *mailer.Mailer, repo *repositories.UsersRepository) *ForgotPwdController {
	return &ForgotPwdController{mailer, repo}
}

func (c *ForgotPwdController) Show(ctx *caesar.CaesarCtx) error {
	return ctx.Render(authPages.ForgotPasswordPage())
}

type ForgotPwdValidator struct {
	Email string `form:"email" validate:"required,email"`
}

func (c *ForgotPwdController) Handle(ctx *caesar.CaesarCtx) error {
	data, _, ok := caesar.Validate[ForgotPwdValidator](ctx)
	if !ok {
		return ctx.Render(authPages.ForgotPasswordSuccessAlert())
	}

	user, _ := c.repo.FindOneBy(ctx.Context(), "email", data.Email)
	if user == nil {
		return ctx.Render(authPages.ForgotPasswordSuccessAlert())
	}

	var buf bytes.Buffer
	res := mails.ForgotPasswordMail(data.Email, "http://localhost:3000/reset-password")
	if err := res.Render(ctx.Context(), &buf); err != nil {
		slog.Error("Failed to render email", "err", err)
		return ctx.Render(authPages.ForgotPasswordSuccessAlert())
	}

	if err := c.mailer.Send(mailer.Mail{
		From:    "Software Citadel <contact@softwarecitadel.com>",
		To:      data.Email,
		Subject: "Reset your password",
		Html:    buf.String(),
	}); err != nil {
		slog.Error("Failed to send email", "err", err)
	}

	return ctx.Render(authPages.ForgotPasswordPage())
}

Registering Routes

Register the controller methods in your application's route configuration:

package config

import (
	"myapp/app/controllers"
	caesar "github.com/caesar-rocks/core"
	mailer "github.com/caesar-rocks/mail"
)

func RegisterRoutes(mailer *mailer.Mailer, usersRepo *repositories.UsersRepository) *caesar.Router {
	forgotPwdController := controllers.NewForgotPwdController(mailer, usersRepo)

	router := caesar.NewRouter()

	router.Get("/forgot-password", forgotPwdController.Show)
	router.Post("/forgot-password", forgotPwdController.Handle)

	return router
}

In the ProvideApp() function from ./config/app.go, ensure that RegisterRoutes is called with the correct parameters:

func ProvideApp() *core.App {
	app := core.NewApp(&core.AppConfig{
		Addr: ":8080",
	})

	env := ProvideEnvironmentVariables()
	mailer := ProvideMailer(env)

	// Register routes
	router := RegisterRoutes(mailer, usersRepo)
	app.SetRouter(router)

	// Register other providers and invokers...

	return app
}

Summary

The Caesar Mail package provides a simplified and unified interface to send emails using different services like Resend, AWS SES, and SMTP. By following this guide, you can set up and use Caesar Mail in your application to handle email functionalities efficiently.