Day28 Gin with SMTP Server

What is an SMTP Server?

SMTP 全名为Simple Mail Transfer Protocol 简单邮件传输协议,它是邮件服务器用来在电子邮件发件人和收件人之间发送、接收和/或中继外发邮件的应用程序。

SMTP 电子邮件服务器将有一个地址(或多个地址),可由您使用的邮件客户端或应用程序设置,通常格式为 smtp.serveraddress.com。例如,Gmail 的 SMTP 服务器主机地址为 smtp.gmail.com,Twilio SendGrid 的为 smtp.sendgrid.com。您通常可以在邮件客户端的帐户或设置部分中找到您的 SMTP 电子邮件服务器地址。

当您发送电子邮件时,SMTP 服务器会处理您的电子邮件,决定将消息发送到哪个服务器,并将消息中继到该服务器。收件人的收件箱服务提供商(例如 Gmail 或 AOL)会下载邮件并将其放入收件人的收件箱中。

Is an SMTP server the same as normal server?

理论上是的,他与一般大多服务器相同,SMTP 服务器处理要发送到另一台服务器的数据,但它具有处理与电子邮件发送、接收和中继相关的数据的非常具体的目的。SMTP 服务器也不一定在机器上。它是一个不断运行以期待发送新邮件的应用程序。

Gin with SMTP

这边我们使用"net/smtp"这个官方的标准库来实现功能。

app/service/smtp.go

package service

import (
	"github.com/joho/godotenv"
	"github.com/sirupsen/logrus"
	"ironman-2021/app/middleware"
	"net/smtp"
	"os"
)

func Send(title string, body string, to string) {
	envErr := godotenv.Load()
	if envErr != nil {
		panic(envErr)
	}

	from := os.Getenv("MAIL_USERNAME")
	pass := os.Getenv("MAIL_PASSWORD")
	port := os.Getenv("MAIL_PORT")
	server := os.Getenv("MAIL_SERVER")

	msg := "From: " + from + "\n" +
		   "To: " + to + "\n" +
		   "Subject: " + title + "\n" +
		   body

	err := smtp.SendMail(server + ":" + port,
		   smtp.PlainAuth("", from, pass, server),
		   from, []string{to}, []byte(msg))
	if err != nil{
		middleware.Logger().WithFields(logrus.Fields{
			"name": "Smtp",
		}).Error("error: ", err)
		return
	}
	middleware.Logger().WithFields(logrus.Fields{
		"name": "Smtp",
	}).Info("Send from: ", from + ", To: ", to)
}
  • 首先将会使用到的环境变数像是MAIL_USERNAME, MAIL_PASSWORD, MAIL_PORTMAIL_SERVER 都给load进function来使用。
  • 接下来则是将subject与body给写入mail并send给Smtp 进行转发。
  • 最後则是将转发成功与否的讯息给写入log当中

app/controller/user.go

这边我们则是在创建新使用者成功时,发送一封提醒信。

// CreateUser @Summary
// @Tags user
// @version 1.0
// @produce application/json
// @param language header string true "language"
// @param register body Register true "register"
// @Success 200 string successful return value
// @Router /v1/users [post]
func (u UsersController) CreateUser(c *gin.Context) {
	t := gi18n.New()
	var form Register
	bindErr := c.BindJSON(&form)

	lan := c.Request.Header.Get("language")
	if lan == "" {
		lan = "en"
	}
	t.SetLanguage(lan)
	if bindErr == nil {
		err := service.RegisterOneUser(form.Account, form.Password, form.Email)
		if err == nil {
			service.Send("Register Notification", "Welcome to become our membership", form.Email)
			c.JSON(http.StatusOK, gin.H{
				"status": 1,
				"msg":    t.Translate(c, "Response_Success"),
				"data":   nil,
			})
		} else {
			c.JSON(http.StatusInternalServerError, gin.H{
				"status": -1,
				"msg":    "Register Failed" + err.Error(),
				"data":   nil,
			})
		}
	} else {
		c.JSON(http.StatusBadRequest, gin.H{
			"status": -1,
			"msg":    "Failed to parse register data" + bindErr.Error(),
			"data":   nil,
		})
	}
}

最後使用者则会收到一封类似下方的mail,同时我们在log也能看到发送成功的信息。

https://ithelp.ithome.com.tw/upload/images/20211013/20129737s0DU81eQL4.png

time="131310-10-10 1010:1010:1010" level=info msg="Send from: [email protected], To: [email protected]" name=Smtp
time="131310-10-10 1010:1010:1010" level=info msg="| 200 |    5.7536937s |   192.168.144.1 | POST | /v1/users/ |"

Summary

这章节我们实作了如何以smtp转发信件,但这边也遇到了一个小问题,那就是我们因为要等待smtp转发完毕才会将response回给client端,因此我们的response time变长了许多。也因此在下个章节我们要将现行架构结合async来创造一个更好的使用者体验。

这次的程序码我也会放在下方连结提供参考。

https://github.com/Neskem/Ironman-2021/tree/Day-28


<<:  Day28-介接 API(番外篇 III)Dialogflow ES 之 Fulfillment 与 Events

>>:  【Day 28】- 这个验证码有点难破解(应对反爬虫技术-验证码篇)

C# .WebAPI Tuple 回传空白的问题

近期将依些老程序转移至微服务上, 因为原本程序使用了大量 out 语法, 为了能够让微服务的 API...

伫列 - DAY 9

伫列定义 具有线性串列结构,资料遵循着先进先出,後进後出的存取顺序 实际使用 一、手枪的子弹 二、电...

DAY14支持向量机演算法(续三)

昨天介绍完SMO算法第三步,今天就要来写这个方法第四步, 昨天我们得到aj,接下来要使用aj来更新a...

[DAY-10] 人才密度最大化 留任测试

理想上 一个组织只要慎用选择人才 这些精挑细选而来的员工就会永远发光发热 BUT 有时候是你用人辜...

Powershell 入门之循环(上)

今天我们来看一下 powershell 中的循环。在 powershell 有两种循环,一种 for...