feat: update SendgridEmailProvider to support dynamic host/path, add From name field (#3576)

* feat: add fields into UI FromName, Host, Endpoint

* feat: update SendgridEmailProvider support dynamic host/path client init, code convention
This commit is contained in:
Bui Le Anh Nguyen 2025-02-12 23:51:31 +07:00 committed by GitHub
parent e926a07c58
commit 37d93a5eea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 46 additions and 29 deletions

View File

@ -24,7 +24,7 @@ func GetEmailProvider(typ string, clientId string, clientSecret string, host str
} else if typ == "Custom HTTP Email" { } else if typ == "Custom HTTP Email" {
return NewHttpEmailProvider(endpoint, method) return NewHttpEmailProvider(endpoint, method)
} else if typ == "SendGrid" { } else if typ == "SendGrid" {
return NewSendgridEmailProvider(clientSecret) return NewSendgridEmailProvider(clientSecret, host, endpoint)
} else { } else {
return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl) return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl)
} }

View File

@ -17,14 +17,16 @@ package email
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings" "net/http"
"github.com/sendgrid/sendgrid-go" "github.com/sendgrid/sendgrid-go"
"github.com/sendgrid/sendgrid-go/helpers/mail" "github.com/sendgrid/sendgrid-go/helpers/mail"
) )
type SendgridEmailProvider struct { type SendgridEmailProvider struct {
ApiKey string ApiKey string
Host string
Endpoint string
} }
type SendgridResponseBody struct { type SendgridResponseBody struct {
@ -35,23 +37,25 @@ type SendgridResponseBody struct {
} `json:"errors"` } `json:"errors"`
} }
func NewSendgridEmailProvider(apiKey string) *SendgridEmailProvider { func NewSendgridEmailProvider(apiKey string, host string, endpoint string) *SendgridEmailProvider {
return &SendgridEmailProvider{ApiKey: apiKey} return &SendgridEmailProvider{ApiKey: apiKey, Host: host, Endpoint: endpoint}
} }
func (s *SendgridEmailProvider) Send(fromAddress string, fromName, toAddress string, subject string, content string) error { func (s *SendgridEmailProvider) Send(fromAddress string, fromName string, toAddress string, subject string, content string) error {
client := s.initSendgridClient()
from := mail.NewEmail(fromName, fromAddress) from := mail.NewEmail(fromName, fromAddress)
to := mail.NewEmail("", toAddress) to := mail.NewEmail("", toAddress)
message := mail.NewSingleEmail(from, subject, to, "", content) message := mail.NewSingleEmail(from, subject, to, "", content)
client := sendgrid.NewSendClient(s.ApiKey)
response, err := client.Send(message) resp, err := client.Send(message)
if err != nil { if err != nil {
return err return err
} }
if response.StatusCode >= 300 { if resp.StatusCode >= 300 {
var responseBody SendgridResponseBody var responseBody SendgridResponseBody
err = json.Unmarshal([]byte(response.Body), &responseBody) err = json.Unmarshal([]byte(resp.Body), &responseBody)
if err != nil { if err != nil {
return err return err
} }
@ -61,8 +65,23 @@ func (s *SendgridEmailProvider) Send(fromAddress string, fromName, toAddress str
messages = append(messages, sendgridError.Message) messages = append(messages, sendgridError.Message)
} }
return fmt.Errorf("SendGrid status code: %d, error message: %s", response.StatusCode, strings.Join(messages, " | ")) return fmt.Errorf("status code: %d, error message: %s", resp.StatusCode, messages)
}
if resp.StatusCode != http.StatusAccepted {
return fmt.Errorf("status code: %d", resp.StatusCode)
} }
return nil return nil
} }
func (s *SendgridEmailProvider) initSendgridClient() *sendgrid.Client {
if s.Host == "" || s.Endpoint == "" {
return sendgrid.NewSendClient(s.ApiKey)
}
request := sendgrid.GetRequest(s.ApiKey, s.Endpoint, s.Host)
request.Method = "POST"
return &sendgrid.Client{Request: request}
}

View File

@ -822,7 +822,7 @@ class ProviderEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
{ {
(this.state.provider.type === "WeChat Pay" || this.state.provider.type === "CUCloud") || (this.state.provider.category === "Email" && (this.state.provider.type === "Azure ACS" || this.state.provider.type === "SendGrid")) ? null : ( (this.state.provider.type === "WeChat Pay" || this.state.provider.type === "CUCloud") || (this.state.provider.category === "Email" && (this.state.provider.type === "Azure ACS")) ? null : (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{this.getClientSecret2Label(this.state.provider)} : {this.getClientSecret2Label(this.state.provider)} :
@ -908,7 +908,7 @@ class ProviderEditPage extends React.Component {
</Row> </Row>
) )
} }
{this.state.provider.category === "Storage" || ["Custom HTTP SMS", "Custom HTTP Email", "CUCloud"].includes(this.state.provider.type) ? ( {this.state.provider.category === "Storage" || ["Custom HTTP SMS", "Custom HTTP Email", "SendGrid", "CUCloud"].includes(this.state.provider.type) ? (
<div> <div>
{["Local File System", "CUCloud"].includes(this.state.provider.type) ? null : ( {["Local File System", "CUCloud"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
@ -922,7 +922,7 @@ class ProviderEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
)} )}
{["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology", "Casdoor", "CUCloud"].includes(this.state.provider.type) ? null : ( {["Custom HTTP SMS", "SendGrid", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology", "Casdoor", "CUCloud"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}> <Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} : {Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
@ -934,7 +934,7 @@ class ProviderEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
)} )}
{["Custom HTTP SMS", "Local File System", "CUCloud"].includes(this.state.provider.type) ? null : ( {["Custom HTTP SMS", "SendGrid", "Local File System", "CUCloud"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}> <Col style={{marginTop: "5px"}} span={2}>
{["Casdoor"].includes(this.state.provider.type) ? {["Casdoor"].includes(this.state.provider.type) ?
@ -948,7 +948,7 @@ class ProviderEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
)} )}
{["Custom HTTP SMS", "CUCloud"].includes(this.state.provider.type) ? null : ( {["Custom HTTP SMS", "SendGrid", "CUCloud"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}> <Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} :
@ -960,7 +960,7 @@ class ProviderEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
)} )}
{["Custom HTTP SMS", "Synology", "Casdoor", "CUCloud"].includes(this.state.provider.type) ? null : ( {["Custom HTTP SMS", "SendGrid", "Synology", "Casdoor", "CUCloud"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}> <Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
@ -1067,18 +1067,16 @@ class ProviderEditPage extends React.Component {
</React.Fragment> </React.Fragment>
) : this.state.provider.category === "Email" ? ( ) : this.state.provider.category === "Email" ? (
<React.Fragment> <React.Fragment>
{["SendGrid"].includes(this.state.provider.type) ? null : ( <Row style={{marginTop: "20px"}} >
<Row style={{marginTop: "20px"}} > <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> {Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} : </Col>
</Col> <Col span={22} >
<Col span={22} > <Input prefix={<LinkOutlined />} value={this.state.provider.host} onChange={e => {
<Input prefix={<LinkOutlined />} value={this.state.provider.host} onChange={e => { this.updateProviderField("host", e.target.value);
this.updateProviderField("host", e.target.value); }} />
}} /> </Col>
</Col> </Row>
</Row>
)}
{["Azure ACS", "SendGrid"].includes(this.state.provider.type) ? null : ( {["Azure ACS", "SendGrid"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>