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
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),
|
|
}},
|
|
})
|
|
}
|
|
|