Skip to content

Commit cd859c6

Browse files
author
childish-sambino
authored
feat: add support for Twilio Email (#392)
1 parent cb42c17 commit cd859c6

8 files changed

+295
-127
lines changed

base_interface.go

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package sendgrid
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
"strconv"
7+
"time"
8+
9+
"github.com/sendgrid/rest"
10+
"github.com/sendgrid/sendgrid-go/helpers/mail"
11+
)
12+
13+
// Version is this client library's current version
14+
const (
15+
Version = "3.5.4"
16+
rateLimitRetry = 5
17+
rateLimitSleep = 1100
18+
)
19+
20+
type options struct {
21+
Auth string
22+
Endpoint string
23+
Host string
24+
Subuser string
25+
}
26+
27+
// Client is the Twilio SendGrid Go client
28+
type Client struct {
29+
rest.Request
30+
}
31+
32+
func (o *options) baseURL() string {
33+
return o.Host + o.Endpoint
34+
}
35+
36+
// requestNew create Request
37+
// @return [Request] a default request object
38+
func requestNew(options options) rest.Request {
39+
requestHeaders := map[string]string{
40+
"Authorization": options.Auth,
41+
"User-Agent": "sendgrid/" + Version + ";go",
42+
"Accept": "application/json",
43+
}
44+
45+
if len(options.Subuser) != 0 {
46+
requestHeaders["On-Behalf-Of"] = options.Subuser
47+
}
48+
49+
return rest.Request{
50+
BaseURL: options.baseURL(),
51+
Headers: requestHeaders,
52+
}
53+
}
54+
55+
// Send sends an email through Twilio SendGrid
56+
func (cl *Client) Send(email *mail.SGMailV3) (*rest.Response, error) {
57+
cl.Body = mail.GetRequestBody(email)
58+
return MakeRequest(cl.Request)
59+
}
60+
61+
// DefaultClient is used if no custom HTTP client is defined
62+
var DefaultClient = rest.DefaultClient
63+
64+
// API sets up the request to the Twilio SendGrid API, this is main interface.
65+
// Please use the MakeRequest or MakeRequestAsync functions instead.
66+
// (deprecated)
67+
func API(request rest.Request) (*rest.Response, error) {
68+
return MakeRequest(request)
69+
}
70+
71+
// MakeRequest attempts a Twilio SendGrid request synchronously.
72+
func MakeRequest(request rest.Request) (*rest.Response, error) {
73+
return DefaultClient.Send(request)
74+
}
75+
76+
// MakeRequestRetry a synchronous request, but retry in the event of a rate
77+
// limited response.
78+
func MakeRequestRetry(request rest.Request) (*rest.Response, error) {
79+
retry := 0
80+
var response *rest.Response
81+
var err error
82+
83+
for {
84+
response, err = MakeRequest(request)
85+
if err != nil {
86+
return nil, err
87+
}
88+
89+
if response.StatusCode != http.StatusTooManyRequests {
90+
return response, nil
91+
}
92+
93+
if retry > rateLimitRetry {
94+
return nil, errors.New("rate limit retry exceeded")
95+
}
96+
retry++
97+
98+
resetTime := time.Now().Add(rateLimitSleep * time.Millisecond)
99+
100+
reset, ok := response.Headers["X-RateLimit-Reset"]
101+
if ok && len(reset) > 0 {
102+
t, err := strconv.Atoi(reset[0])
103+
if err == nil {
104+
resetTime = time.Unix(int64(t), 0)
105+
}
106+
}
107+
time.Sleep(resetTime.Sub(time.Now()))
108+
}
109+
}
110+
111+
// MakeRequestAsync attempts a request asynchronously in a new go
112+
// routine. This function returns two channels: responses
113+
// and errors. This function will retry in the case of a
114+
// rate limit.
115+
func MakeRequestAsync(request rest.Request) (chan *rest.Response, chan error) {
116+
r := make(chan *rest.Response)
117+
e := make(chan error)
118+
119+
go func() {
120+
response, err := MakeRequestRetry(request)
121+
if err != nil {
122+
e <- err
123+
}
124+
if response != nil {
125+
r <- response
126+
}
127+
}()
128+
129+
return r, e
130+
}

sendgrid.go

+15-122
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,44 @@
1-
// Package sendgrid provides a simple interface to interact with the Twilio SendGrid API
21
package sendgrid
32

43
import (
5-
"errors"
6-
"net/http"
7-
"strconv"
8-
"time"
9-
10-
"github.com/sendgrid/rest" // depends on version 2.2.0
11-
"github.com/sendgrid/sendgrid-go/helpers/mail"
12-
)
13-
14-
// Version is this client library's current version
15-
const (
16-
Version = "3.5.4"
17-
rateLimitRetry = 5
18-
rateLimitSleep = 1100
4+
"github.com/sendgrid/rest"
195
)
206

21-
// Client is the Twilio SendGrid Go client
22-
type Client struct {
23-
// rest.Request
24-
rest.Request
25-
}
26-
27-
// options for requestNew
28-
type options struct {
7+
// sendGridOptions for CreateRequest
8+
type sendGridOptions struct {
299
Key string
3010
Endpoint string
3111
Host string
3212
Subuser string
3313
}
3414

35-
func (o *options) baseURL() string {
36-
return o.Host + o.Endpoint
37-
}
38-
3915
// GetRequest
4016
// @return [Request] a default request object
4117
func GetRequest(key, endpoint, host string) rest.Request {
42-
return requestNew(options{key, endpoint, host, ""})
18+
return createSendGridRequest(sendGridOptions{key, endpoint, host, ""})
4319
}
4420

4521
// GetRequestSubuser like GetRequest but with On-Behalf of Subuser
4622
// @return [Request] a default request object
4723
func GetRequestSubuser(key, endpoint, host, subuser string) rest.Request {
48-
return requestNew(options{key, endpoint, host, subuser})
24+
return createSendGridRequest(sendGridOptions{key, endpoint, host, subuser})
4925
}
5026

51-
// requestNew create Request
27+
// createSendGridRequest create Request
5228
// @return [Request] a default request object
53-
func requestNew(options options) rest.Request {
54-
if options.Host == "" {
55-
options.Host = "https://api.sendgrid.com"
29+
func createSendGridRequest(sgOptions sendGridOptions) rest.Request {
30+
options := options{
31+
"Bearer " + sgOptions.Key,
32+
sgOptions.Endpoint,
33+
sgOptions.Host,
34+
sgOptions.Subuser,
5635
}
5736

58-
requestHeaders := map[string]string{
59-
"Authorization": "Bearer " + options.Key,
60-
"User-Agent": "sendgrid/" + Version + ";go",
61-
"Accept": "application/json",
62-
}
63-
64-
if len(options.Subuser) != 0 {
65-
requestHeaders["On-Behalf-Of"] = options.Subuser
66-
}
67-
68-
return rest.Request{
69-
BaseURL: options.baseURL(),
70-
Headers: requestHeaders,
37+
if options.Host == "" {
38+
options.Host = "https://api.sendgrid.com"
7139
}
72-
}
7340

74-
// Send sends an email through Twilio SendGrid
75-
func (cl *Client) Send(email *mail.SGMailV3) (*rest.Response, error) {
76-
cl.Body = mail.GetRequestBody(email)
77-
return MakeRequest(cl.Request)
41+
return requestNew(options)
7842
}
7943

8044
// NewSendClient constructs a new Twilio SendGrid client given an API key
@@ -91,74 +55,3 @@ func NewSendClientSubuser(key, subuser string) *Client {
9155
request.Method = "POST"
9256
return &Client{request}
9357
}
94-
95-
// DefaultClient is used if no custom HTTP client is defined
96-
var DefaultClient = rest.DefaultClient
97-
98-
// API sets up the request to the Twilio SendGrid API, this is main interface.
99-
// Please use the MakeRequest or MakeRequestAsync functions instead.
100-
// (deprecated)
101-
func API(request rest.Request) (*rest.Response, error) {
102-
return MakeRequest(request)
103-
}
104-
105-
// MakeRequest attempts a Twilio SendGrid request synchronously.
106-
func MakeRequest(request rest.Request) (*rest.Response, error) {
107-
return DefaultClient.Send(request)
108-
}
109-
110-
// MakeRequestRetry a synchronous request, but retry in the event of a rate
111-
// limited response.
112-
func MakeRequestRetry(request rest.Request) (*rest.Response, error) {
113-
retry := 0
114-
var response *rest.Response
115-
var err error
116-
117-
for {
118-
response, err = MakeRequest(request)
119-
if err != nil {
120-
return nil, err
121-
}
122-
123-
if response.StatusCode != http.StatusTooManyRequests {
124-
return response, nil
125-
}
126-
127-
if retry > rateLimitRetry {
128-
return nil, errors.New("Rate limit retry exceeded")
129-
}
130-
retry++
131-
132-
resetTime := time.Now().Add(rateLimitSleep * time.Millisecond)
133-
134-
reset, ok := response.Headers["X-RateLimit-Reset"]
135-
if ok && len(reset) > 0 {
136-
t, err := strconv.Atoi(reset[0])
137-
if err == nil {
138-
resetTime = time.Unix(int64(t), 0)
139-
}
140-
}
141-
time.Sleep(resetTime.Sub(time.Now()))
142-
}
143-
}
144-
145-
// MakeRequestAsync attempts a request asynchronously in a new go
146-
// routine. This function returns two channels: responses
147-
// and errors. This function will retry in the case of a
148-
// rate limit.
149-
func MakeRequestAsync(request rest.Request) (chan *rest.Response, chan error) {
150-
r := make(chan *rest.Response)
151-
e := make(chan error)
152-
153-
go func() {
154-
response, err := MakeRequestRetry(request)
155-
if err != nil {
156-
e <- err
157-
}
158-
if response != nil {
159-
r <- response
160-
}
161-
}()
162-
163-
return r, e
164-
}

sendgrid_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ func TestRequestRetry_rateLimit(t *testing.T) {
128128
DefaultClient = &custom
129129
_, err := MakeRequestRetry(request)
130130
assert.NotNil(t, err, "An error did not trigger")
131-
assert.True(t, strings.Contains(err.Error(), "Rate limit retry exceeded"), "We did not receive the rate limit error")
131+
assert.True(t, strings.Contains(err.Error(), "rate limit retry exceeded"), "We did not receive the rate limit error")
132132
DefaultClient = rest.DefaultClient
133133
}
134134

@@ -146,7 +146,7 @@ func TestRequestRetry_rateLimit_noHeader(t *testing.T) {
146146
DefaultClient = &custom
147147
_, err := MakeRequestRetry(request)
148148
assert.NotNil(t, err, "An error did not trigger")
149-
assert.True(t, strings.Contains(err.Error(), "Rate limit retry exceeded"), "We did not receive the rate limit error")
149+
assert.True(t, strings.Contains(err.Error(), "rate limit retry exceeded"), "We did not receive the rate limit error")
150150
DefaultClient = rest.DefaultClient
151151
}
152152

@@ -194,7 +194,7 @@ func TestRequestAsync_rateLimit(t *testing.T) {
194194
t.Error("Received a valid response")
195195
return
196196
case err := <-e:
197-
assert.True(t, strings.Contains(err.Error(), "Rate limit retry exceeded"), "We did not receive the rate limit error")
197+
assert.True(t, strings.Contains(err.Error(), "rate limit retry exceeded"), "We did not receive the rate limit error")
198198
case <-time.After(10 * time.Second):
199199
t.Error("Timed out waiting for an error")
200200
}

twilio_email.go

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package sendgrid
2+
3+
import (
4+
"encoding/base64"
5+
6+
"github.com/sendgrid/rest"
7+
)
8+
9+
// TwilioEmailOptions for GetTwilioEmailRequest
10+
type TwilioEmailOptions struct {
11+
Username string
12+
Password string
13+
Endpoint string
14+
Host string
15+
}
16+
17+
// NewTwilioEmailSendClient constructs a new Twilio Email client given a username and password
18+
func NewTwilioEmailSendClient(username, password string) *Client {
19+
request := GetTwilioEmailRequest(TwilioEmailOptions{Username: username, Password: password, Endpoint: "/v3/mail/send"})
20+
request.Method = "POST"
21+
return &Client{request}
22+
}
23+
24+
// GetTwilioEmailRequest create Request
25+
// @return [Request] a default request object
26+
func GetTwilioEmailRequest(twilioEmailOptions TwilioEmailOptions) rest.Request {
27+
credentials := twilioEmailOptions.Username + ":" + twilioEmailOptions.Password
28+
encodedCreds := base64.StdEncoding.EncodeToString([]byte(credentials))
29+
30+
options := options{
31+
Auth: "Basic " + encodedCreds,
32+
Endpoint: twilioEmailOptions.Endpoint,
33+
Host: twilioEmailOptions.Host,
34+
}
35+
36+
if options.Host == "" {
37+
options.Host = "https://email.twilio.com"
38+
}
39+
40+
return requestNew(options)
41+
}

0 commit comments

Comments
 (0)