Skip to content

Commit 9786bdd

Browse files
authored
Add sendgrid support for single and bulk mail (#5) (#8)
* Add sendgrid support for single and bulk mail (#5) * Clean up some stuff vet didn't like
1 parent e403054 commit 9786bdd

10 files changed

Lines changed: 260 additions & 25 deletions

File tree

.github/workflows/build-and-test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ jobs:
1111
runs-on: ubuntu-latest
1212
steps:
1313

14-
- name: Set up Go 1.14
14+
- name: Set up Go 1.15
1515
uses: actions/setup-go@v2
1616
with:
17-
go-version: 1.14
17+
go-version: 1.15
1818

1919
- uses: actions/checkout@v2
2020

.openapi-generator/FILES

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,15 @@ internal/server/api_notification_service.go
99
internal/server/helpers.go
1010
internal/server/impl.go
1111
internal/server/logger.go
12-
internal/server/model_email_message.go
1312
internal/server/model_error.go
13+
internal/server/model_mail_message.go
1414
internal/server/model_notification_message.go
1515
internal/server/model_recipient.go
16+
internal/server/model_send_bulk_mail_request.go
17+
internal/server/model_send_bulk_mail_response.go
18+
internal/server/model_send_bulk_mail_response_failed.go
19+
internal/server/model_send_bulk_mail_response_successful.go
20+
internal/server/model_send_mail_request.go
1621
internal/server/model_send_mail_response.go
1722
internal/server/model_send_notification_response.go
1823
internal/server/model_sender.go

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ Provide email, push notification, SMS capabilities for applications.
44

55
## Overview
66

7-
This project is schema-first, and is built using openapi-generator.
8-
To add or change a route, make a change to the schema in `api/` and then run `make generate`
7+
This project is schema-first, and is built using [openapi-generator](https://github.com/OpenAPITools/openapi-generator). It requires at least openapi-generator CLI version 5.0.0.
8+
9+
To add or change an API route definition, make a change to the schema in `api/` and then run `make generate`
910

1011
### Running the server
1112

api/notification-service.yaml

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ paths:
3232
content:
3333
application/json:
3434
schema:
35-
$ref: '#/components/schemas/EmailMessage'
35+
$ref: '#/components/schemas/SendMailRequest'
3636
responses:
3737
200:
3838
description: OK
@@ -47,6 +47,33 @@ paths:
4747
schema:
4848
$ref: '#/components/schemas/Error'
4949

50+
/email/sendBulk:
51+
post:
52+
summary: Send a batch of emails to many users with the same content. Note that it is possible for only a subset of these to fail.
53+
operationId: sendBulk
54+
tags:
55+
- email
56+
requestBody:
57+
description: Parameters of the messages to send
58+
required: true
59+
content:
60+
application/json:
61+
schema:
62+
$ref: '#/components/schemas/SendBulkMailRequest'
63+
responses:
64+
200:
65+
description: OK
66+
content:
67+
application/json:
68+
schema:
69+
$ref: '#/components/schemas/SendBulkMailResponse'
70+
default:
71+
description: unexpected error
72+
content:
73+
application/json:
74+
schema:
75+
$ref: '#/components/schemas/Error'
76+
5077

5178
/notification/send:
5279
post:
@@ -131,6 +158,7 @@ paths:
131158

132159
components:
133160
schemas:
161+
134162
Recipient:
135163
type: object
136164
required:
@@ -141,6 +169,7 @@ components:
141169
type: string
142170
address:
143171
type: string
172+
144173
Sender:
145174
type: object
146175
required:
@@ -151,25 +180,54 @@ components:
151180
type: string
152181
address:
153182
type: string
154-
EmailMessage:
183+
184+
SendMailRequest:
155185
type: object
156186
required:
157187
- to
158188
- from
159-
- subject
160189
properties:
161190
to:
162191
$ref: '#/components/schemas/Recipient'
163192
from:
164193
$ref: '#/components/schemas/Sender'
194+
message:
195+
$ref: '#/components/schemas/MailMessage'
196+
197+
SendBulkMailRequest:
198+
type: object
199+
required:
200+
- toAddresses
201+
- from
202+
properties:
203+
toAddresses:
204+
type: array
205+
items:
206+
$ref: '#/components/schemas/Recipient'
207+
from:
208+
$ref: '#/components/schemas/Sender'
209+
message:
210+
$ref: '#/components/schemas/MailMessage'
211+
212+
MailMessage:
213+
type: object
214+
properties:
165215
subject:
166216
type: string
167217
body:
168218
type: string
219+
description: A version of the body containing only text content
169220
richBody:
170221
type: string
222+
description: A version of the body containing rich content for clients which support it
171223
templateId:
172224
type: string
225+
description: A template to use instead of specifying the subject and body
226+
ScheduleSendAtTimestamp:
227+
type: integer
228+
description: Schedule these mesages to go out at the time specified by this UNIX timestamp
229+
format: int64
230+
173231
NotificationMessage:
174232
type: object
175233
required:
@@ -184,34 +242,62 @@ components:
184242
type: string
185243
templateId:
186244
type: string
245+
187246
SubscribeRequest:
188247
type: object
189248
properties:
190249
topicId:
191250
type: string
192251
required:
193252
- topicId
253+
194254
UnsubscribeRequest:
195255
type: object
196256
properties:
197257
topicId:
198258
type: string
199259
required:
200260
- topicId
261+
201262
SendMailResponse:
202263
type: object
203264
properties:
204265
trackingId:
205266
type: string
206-
required:
207-
- topicId
267+
268+
SendBulkMailResponse:
269+
type: object
270+
description: Contains the results of a bulk send operation where some messages may have failed and some may have succeeded
271+
properties:
272+
successful:
273+
type: array
274+
items:
275+
type: object
276+
properties:
277+
emailAddress:
278+
type: string
279+
trackingId:
280+
type: string
281+
failed:
282+
type: array
283+
items:
284+
type: object
285+
properties:
286+
emailAddress:
287+
type: string
288+
description: The address that was being sent to when this failure occurred
289+
error:
290+
type: string
291+
ProviderMessage:
292+
type: string
293+
description: Error or response message returned by the mail provider
294+
208295
SendNotificationResponse:
209296
type: object
210297
properties:
211298
trackingId:
212299
type: string
213-
required:
214-
- topicId
300+
215301
Error:
216302
type: object
217303
required:

go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
module github.com/commitdev/zero-notification-service
22

3-
go 1.14
3+
go 1.15
44

55
require (
66
github.com/gorilla/mux v1.7.3
7-
github.com/sendgrid/rest v2.6.2+incompatible // indirect
7+
github.com/sendgrid/rest v2.6.2+incompatible
88
github.com/sendgrid/sendgrid-go v3.7.2+incompatible
99
github.com/spf13/viper v1.7.1
10+
github.com/stretchr/testify v1.3.0
1011
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect
1112
golang.org/x/text v0.3.3 // indirect
1213
)

go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
5858
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
5959
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
6060
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
61+
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
6162
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
6263
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
6364
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -131,6 +132,7 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI
131132
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
132133
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
133134
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
135+
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
134136
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
135137
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
136138
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -148,10 +150,8 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
148150
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
149151
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
150152
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
151-
github.com/sendgrid/rest v1.0.2 h1:xdfALkR1m9eqf41/zEnUmV0fw4b31ZzGZ4Dj5f2/w04=
152153
github.com/sendgrid/rest v2.6.2+incompatible h1:zGMNhccsPkIc8SvU9x+qdDz2qhFoGUPGGC4mMvTondA=
153154
github.com/sendgrid/rest v2.6.2+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
154-
github.com/sendgrid/sendgrid-go v1.2.0 h1:2K3teZdhaPe12ftFyFL4AWDH4QmNPc+sCi6mWFx5+oo=
155155
github.com/sendgrid/sendgrid-go v3.7.2+incompatible h1:ePQr9ns8so+28whk+gLKRYiyI5IiCESkDIqy7cjiwLg=
156156
github.com/sendgrid/sendgrid-go v3.7.2+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
157157
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -172,6 +172,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
172172
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
173173
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
174174
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
175+
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
175176
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
176177
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
177178
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
@@ -269,6 +270,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
269270
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
270271
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
271272
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
273+
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
272274
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
273275
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
274276
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=

internal/config/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type Config struct {
1616
var config *Config
1717

1818
const (
19-
_ = string(iota) // We don't care about the values of these constants
19+
_ = string(rune(iota)) // We don't care about the values of these constants
2020
Port
2121
SendgridAPIKey
2222
GracefulShutdownTimeout

internal/mail/mail.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package mail
2+
3+
import (
4+
"sync"
5+
6+
"github.com/commitdev/zero-notification-service/internal/server"
7+
"github.com/sendgrid/rest"
8+
sendgridMail "github.com/sendgrid/sendgrid-go/helpers/mail"
9+
)
10+
11+
type BulkSendAttempt struct {
12+
EmailAddress string
13+
Response *rest.Response
14+
Error error
15+
}
16+
17+
type Client interface {
18+
Send(email *sendgridMail.SGMailV3) (*rest.Response, error)
19+
}
20+
21+
// SendBulkMail sends a batch of email messages to all the specified recipients
22+
// All the calls to send mail happen in parallel, with their responses returned on the provided channel
23+
func SendBulkMail(toList []server.Recipient, from server.Sender, message server.MailMessage, client Client, responseChannel chan BulkSendAttempt) {
24+
wg := sync.WaitGroup{}
25+
wg.Add(len(toList))
26+
27+
// Create goroutines for each send
28+
for _, to := range toList {
29+
go func(to server.Recipient) {
30+
response, err := SendIndividualMail(to, from, message, client)
31+
responseChannel <- BulkSendAttempt{to.Address, response, err}
32+
wg.Done()
33+
}(to)
34+
}
35+
// Wait on the all responses to close the channel to signal that the operation is complete
36+
go func() {
37+
wg.Wait()
38+
close(responseChannel)
39+
}()
40+
}
41+
42+
// SendIndividualMail sends an email message
43+
func SendIndividualMail(to server.Recipient, from server.Sender, message server.MailMessage, client Client) (*rest.Response, error) {
44+
sendFrom := sendgridMail.NewEmail(from.Name, from.Address)
45+
sendTo := sendgridMail.NewEmail(to.Name, to.Address)
46+
sendMessage := sendgridMail.NewSingleEmail(sendFrom, message.Subject, sendTo, message.Body, message.RichBody)
47+
sendMessage.SetTemplateID(message.TemplateId)
48+
sendMessage.SetSendAt(int(message.ScheduleSendAtTimestamp))
49+
return client.Send(sendMessage)
50+
}

0 commit comments

Comments
 (0)