Golang 使用 Template 引擎构建漂亮的邮件内容并且完成邮件发送

背景

邮件是常见的触达用户的途径,本文详细介绍基于 golang 的模版引擎构建漂亮的邮件内容,并且发送给模板用户。

思路

go 内置了 html/template 模块,类似 ejs 模块引擎。利用 template 能力可以将变量动态的注入到HTML字符串中,最终获得成功注入变量的字符串内容。

具体实现思路:

  1. 首先根据设计图输出静态的HTML文件;
  2. 然后将HTML中需要变化的内容提取变量占位符;
  3. 利用 template 工具将 HTML 中的变量按照规则注入;
  4. 最终通过运行 template 引擎,获得最终的动态HMTL内容;

邮件内容模版

1. 输出静态HTML文件

根据 figma 设计图编写对应的 HTML 代码;根据产品要求,内容需要动态变化的地方提取变量,方便后续的动态内容注入。

Untitled.png

<!DOCTYPE HTML
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml"
  xmlns:o="urn:schemas-microsoft-com:office:office">

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="x-apple-disable-message-reformatting">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>乐闻世界 - 列表</title>
</head>

<body
  style="margin: 0;padding: 0;line-height: inherit;margin: 0;padding: 0;-webkit-text-size-adjust: 100%;background-color: #f6f6f6;color: #333333">
  <table
    style="vertical-align: top;border-collapse: collapse;line-height: inherit;color: #000000;border-collapse: collapse;table-layout: fixed;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;vertical-align: top;min-width: 320px;Margin: 0 auto;background-color: #f6f6f6;width:100%"
    cellpadding="0" cellspacing="0">
    <tbody style="line-height: inherit;">
      <tr style="vertical-align: top;border-collapse: collapse;line-height: inherit;vertical-align: top">
        <td
          style="vertical-align: top;border-collapse: collapse;line-height: inherit;color: #000000;word-break: break-word;border-collapse: collapse !important;vertical-align: top">
          <!-- 邮件主体内容 -->
          <div style="line-height: inherit;padding-top: 24px;background-color: transparent">
            <div
              style="line-height: inherit;Margin: 0 auto;min-width: 320px;max-width: 620px;min-height: 512px; overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;background-color: transparent;">
              <div
                style="line-height: inherit;border-collapse: collapse;display: table;width: 100%;background-color: transparent;">
                <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding: 16px 0px;background-color: transparent;" align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:620px;"><tr style="background-color: transparent;"><![endif]-->

                <!--[if (mso)|(IE)]><td align="center" width="620" style="background-color: #ffffff;width: 620px;padding: 32px 0px 24px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;" valign="top"><![endif]-->
                <div
                  style="line-height: inherit;max-width: 620px;min-width: 320px;display: table-cell;vertical-align: top;">
                  <div
                    style="line-height: inherit;background-color: #ffffff;width: 100% !important;border-radius: 8px;">
                    <!--[if (!mso)&(!IE)]><!-->
                    <div
                      style="line-height: inherit;padding: 48px 36px 96px 36px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;">
                      <!--<![endif]-->

                      <!-- LOGO-->
                      {{if .Picture}}
                      <table style="line-height: inherit;color: #000000;font-family:arial,helvetica,sans-serif;"
                        role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
                        <tbody style="line-height: inherit;">
                          <tr style="line-height: inherit;">
                            <td
                              style="line-height: inherit;color: #000000;overflow-wrap:break-word;word-break:break-word;padding:0px;font-family:arial,helvetica,sans-serif;"
                              align="left">

                              <img align="center" border="0" src="{{.Picture}}" alt="乐闻世界"
                                title="乐闻世界"
                                style="line-height: inherit;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;clear: both;display: inline-block !important;border: none;height: auto;float: none;width: 60%;max-width: 300px;margin-bottom: 48px;"
                                width="173.6" />
                            </td>
                          </tr>
                        </tbody>
                      </table>
                      {{end}}

                      <!-- 邮件内容 -->
                      <table
                        style="vertical-align: top;border-collapse: collapse;line-height: inherit;color: #000000;font-family:arial,helvetica,sans-serif;"
                        role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
                        <tbody>
                          <tr style="vertical-align: top;border-collapse: collapse;">
                            <td
                              style="vertical-align: top;border-collapse: collapse;line-height: inherit;color: #000000;overflow-wrap:break-word;word-break:break-word;padding:10px;font-family:arial,helvetica,sans-serif;"
                              align="left">

                              <div
                                style="line-height: 26px; text-align: left; word-wrap: break-word;font-size: 16px;font-weight: 500;">
                                <p style="margin: 0;font-size: 16px; line-height: 26px;">{{.Title}}</p>
                                <p style="margin: 0;font-size: 16px; line-height: 26px;"> <br />{{.Desc}}</p>

                                {{if .Logs}}
                                <!-- 警示语 -->
                                <p
                                  style="font-size: 14px;font-weight: 600; line-height: 22px;color: #999999;margin: 24px 0;">
                                  ⚠️
                                  {{.Warning}}
                                </p>
                                {{end}}
                              </div>
                            </td>
                          </tr>
                        </tbody>
                      </table>

                      <!-- 日志内容 -->
                      {{range .Logs}}
                      <div style="line-height: inherit;border-top:1px dashed #BDBDBD;padding-top: 24px;">
                        <div
                          style="line-height: 26px; text-align: left; word-wrap: break-word;font-size: 16px;font-weight: 500;">
                          {{if .IsRespondent}}
                          <p style="margin: 0;font-size: 14px;font-weight: 700; line-height: 22px;color:  #338AFF;">
                            {{.Title}}</p>
                          {{else}}
                          <p style="margin: 0;font-size: 14px;font-weight: 700; line-height: 22px;color:  #6ABF40;">
                            {{.Title}}</p>
                          {{end}}

                          <p style="margin: 0;font-size: 14px;font-weight: 400; line-height: 22px;color: #999999">
                            {{.Time}}
                          </p>
                          <p
                            style="margin: 0;font-size: 14px;font-weight: 400; line-height: 22px;color: #333333;margin-top: 8px;">
                            {{.Content}}
                          </p>
                        </div>
                      </div>
                      {{if .AttachFiles}}
                      <table
                        style="vertical-align: top;border-collapse: collapse;line-height: inherit;color: #000000;margin-bottom:24px;"
                        role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
                        <tbody style="line-height: inherit;">
                          {{range .AttachFiles}}
                          <tr
                            style="vertical-align: top;border-collapse: collapse;line-height: inherit;overflow-wrap:break-word;word-break:break-word;padding:10px;font-family:arial,helvetica,sans-serif;"
                            align="left">
                            {{range .}}
                            <td
                              style="vertical-align: top;border-collapse: collapse;line-height: inherit;color: #000000;overflow-wrap:break-word;word-break:break-word;padding:0px;font-family:arial,helvetica,sans-serif;"
                              align="left">

                              <img align="center" border="0" src="{{.}}" alt="乐闻世界"
                                title="乐闻世界"
                                style="line-height: inherit;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;clear: both;display: inline-block !important;border: none;height: auto;float: none;width: 80%;max-width: 300px;margin-top: 8px;" />
                            </td>
                            {{end}}
                          </tr>
                          {{end}}
                        </tbody>
                      </table>
                      {{end}}
                      {{end}}
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </td>
      </tr>
    </tbody>
  </table>
</body>

</html>

2. Template 注入变量

根据 HTML 模版中提取的变量占位符,定义业务动态变量内容。

package main

import (
	"fmt"
	"net/http"
	"text/template"
)

type TicketLog struct {
	Title        string
	Time         string
	Content      string
	IsRespondent bool
	AttachFiles  [][]string // 两个一组
}

type TicketInfo struct {
	Picture string
	Title   string
	Desc    string
	Warning string
	Logs    []TicketLog
}

func renderHtml(responseWriter http.ResponseWriter, request *http.Request) {
	// 解析指定文件生成模板对象
	tmpl, err := template.ParseFiles("./templates/levenx.html")
	if err != nil {
		fmt.Println("create template failed, err:", err)
		return
	}

	responseWriter.Header().Set("Content-Type", "text/html; charset=utf-8")

	ticketLogs := []TicketLog{
		{
			Title:        "[乐闻的回复]",
			Time:         "2022.05.05 05:05:05",
			Content:      "这是被投诉方的回复这是被投诉方的回复 这是被投诉方的回复 这是被投诉方的回复 这是被投诉方的回复 这是被投诉方的回复 这是被投诉方的回复",
			IsRespondent: true,
			AttachFiles: [][]string{
				{"http://localhost:3000/static/attach.png", "http://localhost:3000/static/attach.png"},
				{"http://localhost:3000/static/attach.png", "http://localhost:3000/static/attach.png"},
			},
		},
		{
			Title:        "[客服的回复]",
			Time:         "2022.05.05 05:05:05",
			Content:      "对不起,给您的使用带来了困扰,我们会尽快解决你的问题。",
			IsRespondent: false,
		},
	}

	ticketInfo := TicketInfo{
		Picture: "http://localhost:3000/static/logo.png",
		Title:   "乐闻的工单",
		Desc:    "对于您的工单12121,如果您对回复有任何疑问,可以直接回复此邮件。",
		Warning: "请不要修改邮件标题,否则我们的团队无法收到您的回复",
		Logs:    ticketLogs,
	}

	tmpl.Execute(responseWriter, ticketInfo)
}

3. 启动 Http 服务器,向网页输出动态HTML内容

启动 http server,支持静态资源,static 文件夹中的所有静态资源都可以通过http服务访问到。

func main() {
	fs := http.FileServer(http.Dir("assets/"))
	http.Handle("/static/", http.StripPrefix("/static/", fs))

	http.HandleFunc("/", renderHtml)
	err := http.ListenAndServe("127.0.0.1:3000", nil)
	if err != nil {
		fmt.Println("HTTP server failed,err:", err)
		return
	}
	fmt.Print("访问 http://localhost:3000")
}

尝试访问 http://localhost:3000

Untitled.png

发送邮件

邮件传输需要遵循特定的协议,其中使用SMTP协议即可完成邮件的发送和回复。golang 有现成的工具库支持了邮件发送,我们接下来将实现发送上面Template模板输出的内容到特定的邮箱。

  1. 安装依赖库
go get github.com/jordan-wright/email
  1. 获取邮件服务商的邮件授权码,比如使用QQ的企业账号

    根据下面截图开启SMTP服务,生成授权码

    Untitled.png

    Untitled.png

  2. 使用授权码,开始发送邮件

     import (
    	"fmt"
    	"net/http"
    	"text/template"
      "net/smtp"
      "github.com/jordan-wright/email"
      "log"
      "bytes"
    )  
    
    func sendMail() {
      body := new(bytes.Buffer)
    	tmpl.Execute(body, ticketInfo)
    
      e := email.NewEmail()
      //设置发送方的邮箱
      e.From = "乐闻 <1025534801@qq.com>"
      // 设置接收方的邮箱
      e.To = []string{"接受邮件的邮箱"}
      //设置主题
      e.Subject = "乐闻的工单"
      //设置文件发送的内容
      e.HTML = body.Bytes()
      auth := smtp.PlainAuth("", "发送邮件的邮箱", "授权码", "smtp.qq.com");
      //设置服务器相关的配置
      error := e.Send("smtp.qq.com:25",auth)
      if error != nil {
          log.Fatal(error)
      }
    }
    
    

    Untitled.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/237276.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

迅为RK3568开发板使用OpenCV处理图像(颜色转换)

1 颜色转换 本小节代码在配套资料“iTOP-3568 开发板\03_【iTOP-RK3568 开发板】指南教程 \04_OpenCV 开发配套资料\05”目录下&#xff0c;如下图所示&#xff1a; cv2.cvtColor()函数功能&#xff1a; 将一幅图像从一个色彩空间转换到另一个色彩空间。 函数原型&#xff…

5G CPE可代替宽带,解决断网问题

最近某运营商就玩起了套餐&#xff0c;断用户的网。 老百姓对宽带半知不解&#xff0c;网络断了没法上网&#xff0c;很着急。因为相信运营商&#xff0c;维修人员怎么说&#xff0c;老百姓就怎么办呗&#xff0c;直到最后才发现自己上当&#xff0c;但钱都给了。 截至2023年9月…

Django讲课笔记02:Django环境搭建

文章目录 一、学习目标二、相关概念&#xff08;一&#xff09;Python&#xff08;二&#xff09;Django 三、环境搭建&#xff08;一&#xff09;安装Python1. 从官方网站下载最新版本的Python2. 运行安装程序并按照安装向导进行操作3. 勾选添加到路径复选框4. 完成安装过程5.…

公共模块无法实例化Elasticsearch的interface类

public interface EsLogDao extends ElasticsearchRepository<EsLog, String> {}Data NoArgsConstructor Document(indexName "my_log") public class EsLog implements Serializable {Idprivate String id; } 出现的错误 解决方案&#xff0c;在公共模块增加…

centos7安全防护_CPU占用率超过百分之300_centos7.4中毒CPU百分之百_清理毒源---Linux工作笔记068

执行top命令的时候看到有个进程: sshd占用cpu百分之300多...而且就算是kill -9 杀掉进程以后,进程又会自动启动 ll /proc/7298 我们执行这个命令,可以看到有个/var/tmp/sshd的文件 我们进入cd /var/tmp 然后我们执行 rm -rf sshd删除这个文件,然后我们再去top可以看到 cpu就…

多线程(初阶九:线程池)

目录 一、线程池的由来 二、线程池的简单介绍 1、ThreadPoolExecutor类 &#xff08;1&#xff09;核心线程数和最大线程数&#xff1a; &#xff08;2&#xff09;保持存活时间和存活时间的单位 &#xff08;3&#xff09;放任务的队列 &#xff08;4&#xff09;线程工…

我的网站服务器被入侵了该怎么办?

最近有用户咨询到德迅云安全&#xff0c;说自己再用的网站服务器遇到了入侵情况&#xff0c;询问该怎么处理入侵问题&#xff0c;有什么安全方案可以解决服务器被入侵的问题。下面&#xff0c;我们就来简单讲下服务器遇到入侵了&#xff0c;该从哪方面入手处理&#xff0c;在预…

华清远见嵌入式学习——QT——作业3

作业要求: 代码效果图&#xff1a; 登录成功并跳转页面 登录失败 关闭 代码&#xff1a; 第一页面头文件&#xff1a; #ifndef LOGIN_H #define LOGIN_H#include <QWidget> #include <QMessageBox>QT_BEGIN_NAMESPACE namespace Ui { class Login; } QT_END_NAME…

Hbase2.5.5分布式部署安装记录

文章目录 1 环境准备1.1 节点部署情况1.2 安装说明 2 Hbase安装过程Step1&#xff1a;Step2:Step3:Step4&#xff1a; 3 Web UI检查状态并测试3.1 Web UI3.2 创建测试命名空间 1 环境准备 1.1 节点部署情况 Hadoop11&#xff1a;Hadoop3.1.4 、 zookeeper3.4.6、jdk8 Hadoop1…

OpenCV | sift函数使用——得到特征点

scale invariant feature transform (sift) 图像尺度空间 在一定的范围内&#xff0c;无论物体是大还是小&#xff0c;人眼都可以分辨出来&#xff0c;然而计算机要有相同的能力却很难&#xff0c;所以要让机器能够对物体在不同尺度下有一个统一的认知&#xff0c; 就需要考虑…

分页设计(平时在表下面的栏框,有首页 | 上一页 | 下一页 | 尾页),下面代码带你实现

分页设计的本质就是&#xff0c;分页查询&#xff0c;就是SQL语句当中的(select * from ? limit ? , &#xff1f;&#xff09;,这里第一个&#xff1f;是所分页的那张表 &#xff0c;第二个&#xff1f;从哪条开始&#xff0c;第三个&#xff1f;是在页面上想让这张表出现几…

Linux系统vim,gcc,g++工具使用及环境配置,动静态库的概念及使用

Linux系统vim&#xff0c;gcc&#xff0c;g工具使用及环境配置&#xff0c;动静态库的概念及使用 1. Linux编辑器-vim的使用1.1 vim的基本概念1.2vim的基本操作1.3vim正常模式命令集1.4vim末端模式命令集1.5简单的vim配置 2.Linux编译器-gcc/g的使用2.1 准备阶段2.2gcc的使用2.…

Redis持久化机制 RDB 和 AOF 的选择

目录 一、Redis 的持久化 二、Redis 的持久化方式 Redis 提供了两种持久化的方式&#xff1a; RDB 介绍 RDB 的触发方式&#xff1a; AOF介绍 三、RDB 和 AOF 的选择 RDB 和 AOF 对比 1. 数据格式&#xff1a; 2. 恢复速度&#xff1a; 3. 数据丢失 4. 文件大小&…

这七款网工在线画拓扑工具,绝了!

你们好&#xff0c;我的网工朋友。 画拓扑图&#xff0c;绝对是网络工程师的基操。 上次给你来了篇手把手教你绘制拓扑图的好文&#xff0c;还没看过的先去看啊&#xff1a;《网络拓扑图怎么画最好&#xff1f;》。 关于画拓扑的工具&#xff0c;那就多了&#xff0c;直接用…

什么是 web 组态?web 组态与传统组态的区别是什么?

组态软件是一种用于控制和监控各种设备的软件&#xff0c;也是指在自动控制系统监控层一级的软件平台和开发环境。这类软件实际上也是一种通过灵活的组态方式&#xff0c;为用户提供快速构建工业自动控制系统监控功能的、通用层次的软件工具。通常用于工业控制&#xff0c;自动…

c++时间转换

获取当前时间字符串 std::string GetFormatTime() {time_t currentTime;time(&currentTime);tm* t_tm localtime(&currentTime);char formatTime[64] {0};snprintf(formatTime, 64, "%04d-%02d-%02d %02d:%02d:%02d", t_tm->tm_year 1900,t_tm->tm…

Python从入门到精通五:Python函数

函数介绍 学习目标&#xff1a; 快速体验函数的使用了解函数的作用 函数&#xff1a;是组织好的&#xff0c;可重复使用的&#xff0c;用来实现特定功能的代码段。 我们使用过的&#xff1a;input()、print()、str()、int()等都是Python的内置函数。 为什么要学习、使用函…

2023年团体程序设计天梯赛——总决赛题

F-L1-1 最好的文档 有一位软件工程师说过一句很有道理的话&#xff1a;“Good code is its own best documentation.”&#xff08;好代码本身就是最好的文档&#xff09;。本题就请你直接在屏幕上输出这句话。 输入格式&#xff1a; 本题没有输入。 输出格式&#xff1a; 在一…

使用 PyTorch FSDP 微调 Llama 2 70B

通过本文&#xff0c;你将了解如何使用 PyTorch FSDP 及相关最佳实践微调 Llama 2 70B。在此过程中&#xff0c;我们主要会用到 Hugging Face Transformers、Accelerate 和 TRL 库。我们还将展示如何在 SLURM 中使用 Accelerate。 完全分片数据并行 (Fully Sharded Data Paral…

java--集合基础

1.集合和数组的特点对比 集合类的特点 提供一种存储空间可变的存储模型&#xff0c;存储的数据容量可以发生改变 集合和数组的区别 共同点&#xff1a;都是存储数据的容器 不同点&#xff1a;数组的容量是固定的&#xff0c;集合的容量是可变的 2.ArrayList集合 ArrayLi…