后端应用程序需要向用户发送电子邮件,以便发送通知并在应用程序界面之外维持沟通。这些电子邮件通常包含针对每个用户的特定信息,例如用户的姓名或地址,因此它们是动态生成的。

本文将指导您使用React Email构建动态邮件模板,将其转换为HTML格式,并利用Go模板将数据插入其中。此外,文中还提供了一个可选章节,向您展示如何使用MailHog发送并测试邮件的送达情况。

要阅读本文,请确保您的计算机上已安装了Go和Node.js。同时,您应对React有一定的基本了解,并对Go模板有所熟悉,不过这些并非严格的要求,因为您可以在实践过程中逐步掌握这些知识。

目录

什么是React Email?

React Email是一个JavaScript库,它可以帮助您使用React构建动态邮件模板。如果您已经掌握了React的基础知识,那么React Email能为您构建动态邮件模板提供更好的开发体验。以下是其中的一些原因:

  • 熟悉React的语法:如果您已经了解React,那么React Email就能让您避免学习额外的模板语言、使用效率低下的拖放界面,或从头开始用HTML表格编写邮件模板。

  • 可重用的内置组件:React Email提供了许多可直接使用的UI组件,比如按钮页脚,因此您无需从头开始开发,从而让邮件制作过程更加顺畅且高效。

  • 在各种邮件客户端中的一致性:React Email生成的邮件模板已经过测试,在常见的邮件客户端中都能正常显示。这可以消除您对邮件在不同客户端上显示效果不一致的担忧。

  • 丰富的邮件开发工具:React Email提供了许多用于预览和评估使用它构建的邮件的功能,例如:

    • 本地开发服务器:您可以实时在浏览器中预览移动设备和桌面设备上的邮件显示效果。

    • 邮件发送测试功能:您可以将邮件发送到真实的电子邮件地址进行预览。

    • 兼容性检测工具:该工具能帮助您了解您的邮件在常见邮件客户端中的支持情况。

    • 垃圾邮件评分功能:该功能会分析您的邮件,判断它被标记为垃圾邮件的可能性。

  • 与Tailwind CSS的集成:Tailwind CSS是一个流行的CSS框架,它提供了许多用于美化HTML页面并使其具备响应式设计的样式。React Email可以轻松地与Tailwind CSS集成,帮助您制作出美观的邮件。

所有这些功能都是免费使用的。

在本文中,您将学习如何从React Email模板生成HTML文件,将其转换为Go模板,并向模板中插入数据以便进行预览。

Go模板

Go语言的html/template包允许您定义可重复使用的HTML模板,这些模板可以使用动态数据来填充内容。这些模板中包含占位符(称为“动作”),Go的模板引擎会在执行过程中对这些占位符进行评估,并将其替换为实际的值。

Golang HTML模板的解析与执行

首先,您需要为该包提供包含Go特定注释的HTML内容。系统会将这些HTML内容转换为Go HTML模板,同时将Go特定的注释转换成模板中的“动作”。随后,使用相应的数据执行这个模板,从而生成包含这些数据的HTML输出结果。

package main

import (
	"html/template"
	"os"
)

func main() {
	tmpl := template.New("hello")
	tmpl, _ = tmpl.Parse(`

Hello {{.}}

`) tmpl.Execute(os.Stdout, "Gopher") } // 输出结果:

Hello Gopher

// 测试链接: https://goplay.tools/snippet/KxbkWPIArz5

在上面的代码示例中:

  • template.New用于创建一个名为“hello”的空模板对象。

  • tmpl.Parse(`

    Hello {{.}}

    `)将HTML字符串`

    Hello {{.}}

    `解析为Go HTML模板,并将其保存在`tmp`变量中。其中,`{{.}}这部分内容就是作为数据占位符的“动作”;`{{`和`}}被称为分隔符,而`.`则是用于访问数据的标识符。

  • tmpl.Execute(os.Stdout, "Gopher")使用字符串“Gopher”来填充这个占位符,并将生成的HTML输出结果写入控制台。

Go模板的分隔符

在上面的代码示例中,我们使用了双大括号`{{`和`}}作为Go模板中的分隔符。分隔符是Go语言用来标识输入字符串中哪些部分代表“动作”的符号——也就是说,这些部分是需要被解析执行的代码。

您也可以通过调用模板的`Delims`方法来更改分隔符。下面的示例说明了这一点:

package main

import (
	"html/template"
	"os"
)

func main() {
	tmpl := template.New("hello")
	tmpl, _ = tmpl.Delims("((", ")").Parse(`

Hello ((.))

`) tmpl.Execute(os.Stdout, "Gopher") } // 输出结果:

Hello Gopher

// 测试链接: https://goplay.tools/snippet/00RuDzvZYwN

在上面的代码片段中,(()) 被用作 hello 模板的分隔符。

这一点非常重要,因为你需要设置合适的分隔符,以避免 Go 的默认分隔符与 React 中的花括号在 React Email 模板中发生冲突。

使用 React Email 在 Go 语言中创建动态邮件

下图总结了本文中将要构建的示例应用程序的工作原理:

从 React Email 模板到包含动态数据的 HTML 邮件

你将创建一个包含 Go 模板注释的 React Email 模板,然后使用 Node.js 从这个模板生成 HTML 文件。Go 会解析这些 HTML 文件,重新生成 Go 模板并执行它,最后将这些生成的邮件发送出去。

可选地,你可以使用 go-mail 来发送邮件,并利用 MailHog 这个本地 SMTP 服务器在浏览器中预览邮件内容。

设置项目环境

首先,请确保你的电脑上已经安装了 Go 和 Node.js。克隆这个 freeCodeCamp-go-react-email 仓库,然后使用 git checkout 01-setup 命令切换到 01-setup 分支。

该项目在 cmd 目录下包含一个 main.go 文件以及一个 go.mod 文件,同时还包含一个 .gitignore 文件,用于告诉 Git 忽略所有的 node_modules 目录。

在项目的终端中运行 go run cmd/main.go。如果控制台显示 “It works!”,说明你的环境设置正确了,可以继续学习下一节内容。

配置 React Email

在项目根目录下创建一个名为 mailer 的目录,这个目录将用于存放与发送邮件相关的功能代码。

mailer 目录中,你会创建一个名为 emails 的 Node.js 项目,这个项目将负责实现 React Email 的相关功能。具体操作步骤如下:

  • mailer 目录下创建一个名为 _emails 的目录。该目录的名称以下划线开头,这样在运行 go build 命令时,这个目录会被忽略,因此不会被包含到编译后的可执行文件中。

  • _emails 目录的终端中运行 npm init -y,以初始化这个 Node.js 项目。这样会在该目录下生成一个 package.json 文件。

  • package.json 文件中的 “name” 字段值更改为 “emails”,使包名更加规范。这一步不是必须的。

接下来,请在 `_emails` 目录的根目录下运行以下命令,以安装所需的 React Email 库:

npm install @react-email/ui @types/react -D -E
npm install react-email react react-dom -E

安装完成后,请将package.json文件中的scripts字段替换为以下代码:

  "scripts": {
    "dev": "email dev --dir ./src",
    "export": "email export --pretty --dir ./src --outDir ../templates"
  },

dev脚本会启动服务器,以便在浏览器中预览React Email模板。您需要使用React编写这些模板,并将它们保存在src目录下的_emails文件夹中。export脚本会将src目录中的模板文件从JSX(或TSX)格式转换为HTML格式,然后将这些生成的HTML文件保存在名为templates的文件夹中。这个文件夹位于mailer目录下,而不是_emails目录下。

之所以将templates文件夹放在mailer目录下,是因为Go项目只需要templates文件夹中的HTML文件,而不需要_emails文件夹的内容。

如果您已经完成了所有这些步骤,那么就已经在emails这个Node.js项目中配置好了React Email功能。此时,如果您想查看项目的当前状态,请访问freeCodeCamp-go-react-email/02-setup-react-email

在下一节中,您将创建一个React Email模板,并在浏览器中预览它。

创建一个React Email模板

在这一节中,您将创建一个React Email模板,并在浏览器中预览它。同时,您还会将该模板编译为HTML文件并导出出来。

_emails目录下创建一个名为src的文件夹。在src文件夹内,创建一个名为welcome.tsx的文件。将以下代码内容复制并粘贴到welcome.tsx文件中。

import {
  Body,
  Button,
  Container,
  Head,
  Heading,
  Html,
  Img,
  Preview,
  Section,
  Tailwind,
  Text,
} from "react-email";

interface WelcomeEmailProps {
  username?: string;
  company?: string;
  gophers?: string[];
}

const WelcomeEmail = ({
  username = "Nicole",
  company = "GoWorld",
  gophers = ["Tinky Winky", "Dipsy", "Laa-Laa", "Po"],
}: WelcomeEmailProps) => {
  const previewText = `欢迎来到\({company}, \){username}!`;

  return (
    
      {previewText}</Preview>
      
        {company},{username}!
            
            你好,{username},
            
              我们非常高兴你加入{company}。希望你在我们的团队中度过一段愉快的时光。如果你有任何疑问或需要帮助,请随时联系以下成员:
            
            
{gopher}</li> ))}
    >
开始使用
祝好!
{company}团队
> >

上述代码片段就是本文中将要使用的React Email模板。要预览该模板,请进入 `_emails` 根目录的终端窗口,然后运行 `npm run dev` 命令。接着使用浏览器访问终端中显示的预览地址。点击左侧侧边栏中的“welcome”链接,你应该会看到如下截图所示的用户界面:

React Email预览界面

在上述用户界面中,React Email会使用传递给 `welcome` 模板的默认值来生成邮件内容。

要停止服务器运行,请在终端中按下 `CTRL + C` 来终止相关进程。接下来,在 `_emails` 根目录的终端中运行 `npm run export` 命令,即可将 `src` 目录中的HTML文件构建并导出。这些导出的HTML文件会被保存在 `mailer` 目录下的 `templates` 文件夹中。在 `templates` 文件夹中,你会看到一个名为 `welcome.html` 的文件——它其实就是从 `welcome.tsx` 文件生成的HTML内容。

如果要查看项目的当前状态,请访问以下链接:freeCodeCamp-go-react-email/03-create-react-email-template

从HTML文件生成Go模板

你已经创建了一个React Email模板,预览了它,构建了它,并将其导出了为HTML文件。在这一部分中,你会修改这个React Email模板,使其使用你自己设定的分隔符来代替React默认的大括号;同时,你还会从该HTML文件生成一个Go模板。

首先,请将 `welcome.tsx` 文件中的内容替换为以下代码片段,这样就可以使用 `((` 和 `))` 作为分隔符,并且也会移除TypeScript类型声明:

import {
  Body,
  Button,
  Container,
  Head,
  Heading,
  Html,
  Img,
  Preview,
  Section,
  Tailwind,
  Text,
} from "react-email";

const WelcomeEmail = () => {
  const previewText = `欢迎来到 ((.Company)), ((.Username))!`;

  return (
    
      {previewText}</Preview>
      
        ((.Company)), ((.Username))!
            
            你好,((.Username)),
            
              我们非常高兴你加入 ((.Company))!希望你在我们的团队中度过一段愉快的时光。如果你有任何疑问或需要帮助,请随时联系以下工作人员:
            
            
((.))</li> ((end))
开始使用
祝好,
<((.Company))>团队
); }; export default WelcomeEmail;

在 `_emails` 目录的根目录中运行 `npm run export` 命令,即可构建并将这个版本的 React Email 模板导出为 HTML 文件。生成的 HTML 文件会包含 Go 模板注释,当这些注释被 Go 解析时,它们会转化为相应的操作,从而形成可供 Go 应用程序使用的 HTML 模板。

在 `mailer` 目录中创建一个名为 `fs.go` 的文件。该文件中的代码用于将 `templates` 目录中的文件嵌入到 Go 应用程序中以便使用。请将以下代码片段复制并粘贴到 `fs.go` 文件中:

```go
package mailer

import (
"embed"
"io/fs"
)

//go:embed templates/*
var embedded embed.FS
var templateFS, _ = fs.Sub.embedded, "templates")

//go:embed templates/* 这行代码告诉 Go 编译器将当前目录(`mailer` 目录)中的文件嵌入到 Go 应用程序的编译后的二进制文件中。这样,Go 应用程序才能访问这些 HTML 模板文件。`templateFS` 变量用于访问 `templates` 子目录中的文件。

在 `mailer` 目录中再创建一个文件,并将其命名为 `mailer.go`。`mailer.go` 文件中包含用于解析 HTML 文件以生成 Go HTML 模板以及发送电子邮件的代码。请将以下代码片段复制并粘贴到 `mailer.go` 文件中:

package mailer

import (
"html/template"
"io"
)

const (
welcomeMailKey = "welcome_mail"
)

func setUpTemplates() (map[string]*template.Template, error) {
templates := make(map[string]*template.Template)

tmpl := template.New("welcome.html").Delims("((", "))
welcomeEmailTmpl, err := tmpl.ParseFS(templateFS, "welcome.html")
if err != nil {
return nil, err
}

templates[welcomeMailKey] = welcomeEmailTmpl

return templates, nil
}

type Mailer struct {
templates map[string]*template.Template
}

// NewMailer 创建一个新的 Mailer 对象
func NewMailer() (*Mailer, error) {
tpls, err := setUpTemplates()
if err != nil {
return nil, err
}

return &Mailer{
templates: tpls,
}, nil
}

type WelcomEmailData struct {
Username string
Company string
Gophers []string
}

func (mailer *Mailer) WriteWelcomeMail(w io.Writer, data WelcomEmailData) error {
tmpl := mailertemplates[welcomeMailKey]
err := tmpl.Execute(w, data)

return err
}

在上述代码片段中:

- `setUpTemplates` 函数创建了一个模板对象 `tmpl`,并设置了其分隔符。然后它解析 `welcome.html` 文件,将其转换为 Go 模板,并将生成的模板存储在 `welcomeEmailTmpl` 变量中。最后,这个模板被添加到 `templates` 映射中,其中键为 `welcomeMailKey`,函数返回 `templates` 映射。

- `NewMailer` 函数创建并返回一个 `Mailer` 对象,该对象包含了用于操作这些模板的代码。

- `WriteWelcomeMail` 函数用于使用实际的数据来执行欢迎邮件模板。

要查看当前代码库的状态,请访问freeCodeCamp-go-react-email/04-create-golang-template

在浏览器中渲染动态邮件

在本节中,您将创建一个简单的Web服务器,用于查看其中包含了动态数据的邮件模板渲染结果。

请将main.go文件的内容替换为以下代码片段:

package main

import (
	"fmt"
	"net/http"
	"os"

	pkgMailer "github.com/orimdominic/freeCodeCamp-go-react-email/mailer"
)

func main() {
	mailer, err := pkgMailer.NewMailer()
	if err != nil {
		fmt.Fprint(os.Stderr, err)
		os.Exit(1)
	}

	http.HandleFunc("/mail", func(w http.ResponseWriter, r *http.Request) {
	(username := r.URL.Query().Get("username")
		company := r.URL.Query().Get("company")
		gophers := []string{"Tinky Winky", "Dipsy", "Laa-Laa", "Po"}

		err = mailer.WriteWelcomeMail(w, pkgMailer.WelcomEmailData{
			Username: username,
			Company:  company,
			Gophers:  gophers,
		})
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	})

	port := ":8888"
	err = http.ListenAndServe(port, nil)
	if err != nil {
		fmt.Fprint(os.Stderr, err)
		os.Exit(1)
	}
}

上述代码首先使用mailer.go文件中的NewMailer函数创建了一个邮件发送对象。在完成错误处理后,它会在端口8888上启动一个简单的Web服务器,并设置GET /mail路由。

GET /mail路由会接收两个查询参数:usernamecompany,这些参数将会被用作邮件内容中的动态数据。通过WriteWelcomeMail函数执行模板后,生成的结果会以HTML格式显示在浏览器中。您可以使用这个路由来测试mailer包的功能。

在启动服务器之前,您应该先构建并导出React Email模板文件,这样您的HTML文件就能始终使用最新的模板内容。为了方便进行构建、导出和运行服务器,您可以使用Makefile文件。

请进入项目根目录的终端窗口,创建一个名为Makefile的文件,然后将以下代码片段复制到其中:

run: email-build
	go run cmd/main.go

email-build: mailer/_emails
	npm --prefix mailer/_emails run export

上述Makefile中的run脚本会将React Email模板构建并导出为HTML文件,保存到mailer/templates目录中,然后启动Go应用程序。请注意,Makefile中使用的是制表符而不是空格来进行代码缩进。

在项目根目录的终端窗口中运行make run命令,然后在浏览器中访问http://localhost:8888/mail?username=Nicole&company=GoWorld,您就会看到邮件内容在浏览器界面中被渲染出来的效果。

在浏览器中执行的Go模板

请更换URL中的usernamecompany值,以便使用不同的参数来测试该邮件功能。

通过这种设置,你可以将模板执行后的结果直接发送到你的邮件客户端中,收件人看到的邮件内容将与在浏览器中显示的效果完全一致。

如果你想查看当前代码库的进展状态,请访问freeCodeCamp-go-react-email/05-render-dynamic-email

使用go-mail和MailHog发送并测试邮件

在上一节中,你提供了数据来执行模板,但邮件内容是在浏览器中显示的,而不是通过邮件客户端。在这一节中,你会使用go-mail来发送邮件,而MailHog则会拦截并显示这些邮件。

这一节是可选的。如果你没有在当地安装MailHog,那么你需要使用Docker Compose来为这个项目配置它。在继续之前,请确保你的电脑上已经安装了Docker Compose。

在终端中,导航到项目的根目录,然后运行go get github.com/wneessen/go-mail来安装go-mail。在项目的根目录下创建一个compose.yml文件,并将下面的代码片段粘贴到其中:

services:
  mailhog:
    image: mailhog/mailhog
    restart: no
    logging:
      driver: "none" # 禁止保存日志
    ports:
      - 1025:1025 # SMTP服务器
      - 8025:8025 # Web界面

在终端中,导航到项目的根目录,然后运行docker compose up来拉取并启动MailHog SMTP服务器。MailHog会在1025端口上监听邮件,并在http://localhost:8025提供Web界面,你可以通过这个界面查看被拦截的邮件。根据你的网络连接情况,初始的镜像拉取过程可能需要几分钟时间。

请将mailer.go文件的内容替换为下面的代码片段:

package mailer

import (
	"html/template"
	"io"

	"github.com/wneessen/go-mail"
)

const (
	welcomeMailKey = "welcome_mail"
	sender = "noreply@localhost.com"
)

func setUpTemplates() (map[string]*template.Template, error) {
	templates := make(map[string]*template.Template)

	tmpl := template.New("welcome.html").Delims("((", "))
	welcomeEmailTmpl, err := tmpl.ParseFS(templateFS, "welcome.html")
	if err != nil {
		return nil, err
	}

	templates[welcomeMailKey] = welcomeEmailTmpl

	return templates, nil
}

type Mailer struct {
	client    *mail.Client
	templates map[string]*template.Template
}

// NewMailer用于创建一个新的邮件发送器对象
func NewMailer() (*Mailer, error) {
	tpls, err := setUpTemplates()
	if err != nil {
		return nil, err
	}

	c, err := mail.NewClient(
		"localhost",
		mail.WithPort(1025),
		mail.WithTLSPolicy(mail.NoTLS),
	)

	if err != nil {
		return nil, err
	}

	return &Mailer{
		client:    c,
		templates: tpls,
	}, nil
}

type WelcomEmailData struct {
	Username string
	Company  string
	Gophers  []string
}

func (mailer *Mailer) WriteWelcomeMail(w io.Writer, data WelcomEmailData) error {
	tmpl := mailer.templates[welcomeMailKey]
	err := tmpl.Execute(w, data)

	return err
}

func (mailer *Mailer) SendWelcomeMail(to string, data WelcomEmailData) error {
	m := mail.NewMsg()
	m.From(sender)
	m.To(to)
	m.Subject("欢迎来到 " + data COMPANY)
	m.SetBodyHTMLTemplate(mailertemplates[welcomeMailKey], data)

	err := mailer.client.DialAndSend(m)
	return err
}

mailer.go文件中的新变更包括以下内容:

  • 引入了go-mail包

  • 创建了一个名为sender的常量,用于存储发件人的电子邮件地址

  • 使用go-mail包构建了一个邮件发送客户端

  • mailer结构体上添加了SendWelcomeMail方法,该方法会使用welcomeEmailTmpl模板生成电子邮件,然后将其发送给接收者

main.go文件中,需要将GET /mail路由对应的处理函数从WriteWelcomeMail改为SendWelcomeMail。你可以使用任何你想使用的电子邮件地址;下面的代码示例中使用的是fcc@go.dev

		err := mailer.SendWelcomeMail("fcc@go.dev", pkgMailer.WelcomEmailData{
			Username: username,
			Company:  company,
			Gophers:  gophers,
		})
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		fmt.Fprint(w, "电子邮件已发送")

请确保邮件服务器正在运行,你可以在浏览器中访问http://localhost:8025来检查服务器状态。在项目的根目录下,通过运行make run命令启动服务器。再次访问http://localhost:8888/mail?username=Nicole&company=GoWorld来测试该路由的功能是否正常。接下来,访问http://localhost:8025查看邮件服务器的界面,你应该会看到类似下图所示的用户界面:

用于预览邮件的MailHog界面

点击“Welcome to Helix”即可查看邮件内容。

如果你想了解当前代码库的运行状态,可以访问freeCodeCamp-go-react-email/06-send-email

结论

通过完成本教程的学习,你已经:

  • 学会了如何将React Email模板转换为Go语言编写的电子邮件模板

  • 了解了如何使用Makefile来运行自定义脚本

  • 能够在浏览器中预览生成的电子邮件,并使用MailHog进行测试

现在你再也不用费心去编写原始的HTML代码来制作邮件,也不需要学习新的模板语言了。借助React Email和Go模板,你可以更加轻松地构建并发送美观的电子邮件。

Comments are closed.