You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

100 lines
2.7 KiB

package email
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
// resendEndpoint Resend 发信 API(走 HTTPS/443,规避国内出口 SMTP 被限速/信誉差导致的海外送达率问题)。
const resendEndpoint = "https://api.resend.com/emails"
// Resend 通过 Resend HTTP API 发信,实现 ISys。
// 复用 Options.Password 承载 API Key(re_ 开头);Options.FromEmail 须为在 Resend 已验证域名下的发件地址。
type Resend struct {
ApiKey string
From string
Name string
}
// resendAttachment Resend 附件:内容为 base64 编码。
type resendAttachment struct {
Filename string `json:"filename"`
Content string `json:"content"`
}
// resendPayload Resend /emails 请求体。
type resendPayload struct {
From string `json:"from"`
To []string `json:"to"`
Subject string `json:"subject"`
Html string `json:"html"`
Attachments []resendAttachment `json:"attachments,omitempty"`
}
var resendClient = &http.Client{Timeout: 15 * time.Second}
// fromAddr 拼成 "昵称 <邮箱>"(无昵称则仅邮箱)。
func (this *Resend) fromAddr() string {
if this.Name != "" {
return fmt.Sprintf("%s <%s>", this.Name, this.From)
}
return this.From
}
// send 提交一次发信请求,非 2xx 视为失败并带回响应体。
func (this *Resend) send(p *resendPayload) error {
body, err := json.Marshal(p)
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodPost, resendEndpoint, bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+this.ApiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := resendClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
rb, _ := io.ReadAll(resp.Body)
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("resend send failed: status=%d body=%s", resp.StatusCode, string(rb))
}
return nil
}
// SendMail 发送 HTML 邮件。
func (this *Resend) SendMail(title, content string, to ...string) error {
return this.send(&resendPayload{
From: this.fromAddr(),
To: to,
Subject: title,
Html: content,
})
}
// SendMailForFile 发送带单个附件的 HTML 邮件。
func (this *Resend) SendMailForFile(title string, content string, filepath string, filename string, to ...string) (err error) {
data, err := os.ReadFile(filepath)
if err != nil {
return err
}
return this.send(&resendPayload{
From: this.fromAddr(),
To: to,
Subject: title,
Html: content,
Attachments: []resendAttachment{{
Filename: filename,
Content: base64.StdEncoding.EncodeToString(data),
}},
})
}