Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sendgrid support for single and bulk mail (#5) #8

Merged
merged 2 commits into from
Dec 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ jobs:
runs-on: ubuntu-latest
steps:

- name: Set up Go 1.14
- name: Set up Go 1.15
uses: actions/setup-go@v2
with:
go-version: 1.14
go-version: 1.15

- uses: actions/checkout@v2

Expand Down
7 changes: 6 additions & 1 deletion .openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@ internal/server/api_notification_service.go
internal/server/helpers.go
internal/server/impl.go
internal/server/logger.go
internal/server/model_email_message.go
internal/server/model_error.go
internal/server/model_mail_message.go
internal/server/model_notification_message.go
internal/server/model_recipient.go
internal/server/model_send_bulk_mail_request.go
internal/server/model_send_bulk_mail_response.go
internal/server/model_send_bulk_mail_response_failed.go
internal/server/model_send_bulk_mail_response_successful.go
internal/server/model_send_mail_request.go
internal/server/model_send_mail_response.go
internal/server/model_send_notification_response.go
internal/server/model_sender.go
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ Provide email, push notification, SMS capabilities for applications.

## Overview

This project is schema-first, and is built using openapi-generator.
To add or change a route, make a change to the schema in `api/` and then run `make generate`
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.

To add or change an API route definition, make a change to the schema in `api/` and then run `make generate`

### Running the server

Expand Down
100 changes: 93 additions & 7 deletions api/notification-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/EmailMessage'
$ref: '#/components/schemas/SendMailRequest'
responses:
200:
description: OK
Expand All @@ -47,6 +47,33 @@ paths:
schema:
$ref: '#/components/schemas/Error'

/email/sendBulk:
post:
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.
operationId: sendBulk
tags:
- email
requestBody:
description: Parameters of the messages to send
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SendBulkMailRequest'
responses:
200:
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/SendBulkMailResponse'
default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'


/notification/send:
post:
Expand Down Expand Up @@ -131,6 +158,7 @@ paths:

components:
schemas:

Recipient:
type: object
required:
Expand All @@ -141,6 +169,7 @@ components:
type: string
address:
type: string

Sender:
type: object
required:
Expand All @@ -151,25 +180,54 @@ components:
type: string
address:
type: string
EmailMessage:

SendMailRequest:
type: object
required:
- to
- from
- subject
properties:
to:
$ref: '#/components/schemas/Recipient'
from:
$ref: '#/components/schemas/Sender'
message:
$ref: '#/components/schemas/MailMessage'

SendBulkMailRequest:
type: object
required:
- toAddresses
- from
properties:
toAddresses:
type: array
items:
$ref: '#/components/schemas/Recipient'
from:
$ref: '#/components/schemas/Sender'
message:
$ref: '#/components/schemas/MailMessage'

MailMessage:
type: object
properties:
subject:
type: string
body:
type: string
description: A version of the body containing only text content
richBody:
type: string
description: A version of the body containing rich content for clients which support it
templateId:
type: string
description: A template to use instead of specifying the subject and body
ScheduleSendAtTimestamp:
type: integer
description: Schedule these mesages to go out at the time specified by this UNIX timestamp
format: int64

NotificationMessage:
type: object
required:
Expand All @@ -184,34 +242,62 @@ components:
type: string
templateId:
type: string

SubscribeRequest:
type: object
properties:
topicId:
type: string
required:
- topicId

UnsubscribeRequest:
type: object
properties:
topicId:
type: string
required:
- topicId

SendMailResponse:
type: object
properties:
trackingId:
type: string
required:
- topicId

SendBulkMailResponse:
type: object
description: Contains the results of a bulk send operation where some messages may have failed and some may have succeeded
properties:
successful:
type: array
items:
type: object
properties:
emailAddress:
type: string
trackingId:
type: string
failed:
type: array
items:
type: object
properties:
emailAddress:
type: string
description: The address that was being sent to when this failure occurred
error:
type: string
ProviderMessage:
type: string
description: Error or response message returned by the mail provider

SendNotificationResponse:
type: object
properties:
trackingId:
type: string
required:
- topicId

Error:
type: object
required:
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
module github.com/commitdev/zero-notification-service

go 1.14
go 1.15

require (
github.com/gorilla/mux v1.7.3
github.com/sendgrid/rest v2.6.2+incompatible // indirect
github.com/sendgrid/rest v2.6.2+incompatible
github.com/sendgrid/sendgrid-go v3.7.2+incompatible
github.com/spf13/viper v1.7.1
github.com/stretchr/testify v1.3.0
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect
golang.org/x/text v0.3.3 // indirect
)
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
Expand Down Expand Up @@ -131,6 +132,7 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand All @@ -148,10 +150,8 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sendgrid/rest v1.0.2 h1:xdfALkR1m9eqf41/zEnUmV0fw4b31ZzGZ4Dj5f2/w04=
github.com/sendgrid/rest v2.6.2+incompatible h1:zGMNhccsPkIc8SvU9x+qdDz2qhFoGUPGGC4mMvTondA=
github.com/sendgrid/rest v2.6.2+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
github.com/sendgrid/sendgrid-go v1.2.0 h1:2K3teZdhaPe12ftFyFL4AWDH4QmNPc+sCi6mWFx5+oo=
github.com/sendgrid/sendgrid-go v3.7.2+incompatible h1:ePQr9ns8so+28whk+gLKRYiyI5IiCESkDIqy7cjiwLg=
github.com/sendgrid/sendgrid-go v3.7.2+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
Expand All @@ -172,6 +172,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
Expand Down Expand Up @@ -269,6 +270,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
Expand Down
2 changes: 1 addition & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Config struct {
var config *Config

const (
_ = string(iota) // We don't care about the values of these constants
_ = string(rune(iota)) // We don't care about the values of these constants
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious what does this do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a way to generate constants that have a specific, unique, but unimportant value. What we're using it for is to be able to refer to config options using a specific const like Port rather than just a string like "port" which allows us to be more precise about our type checking, avoid typos, etc.
I had to add rune in there because something changed in go 1.15 which caused a warning due to the conversion from int directly to string. It doesn't really matter in our situation because, again, the value itself is unimportant, as long as it's a unique string.

Port
SendgridAPIKey
GracefulShutdownTimeout
Expand Down
50 changes: 50 additions & 0 deletions internal/mail/mail.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package mail

import (
"sync"

"github.com/commitdev/zero-notification-service/internal/server"
"github.com/sendgrid/rest"
sendgridMail "github.com/sendgrid/sendgrid-go/helpers/mail"
)

type BulkSendAttempt struct {
EmailAddress string
Response *rest.Response
Error error
}

type Client interface {
Send(email *sendgridMail.SGMailV3) (*rest.Response, error)
}

// SendBulkMail sends a batch of email messages to all the specified recipients
// All the calls to send mail happen in parallel, with their responses returned on the provided channel
func SendBulkMail(toList []server.Recipient, from server.Sender, message server.MailMessage, client Client, responseChannel chan BulkSendAttempt) {
wg := sync.WaitGroup{}
wg.Add(len(toList))

// Create goroutines for each send
for _, to := range toList {
go func(to server.Recipient) {
response, err := SendIndividualMail(to, from, message, client)
responseChannel <- BulkSendAttempt{to.Address, response, err}
wg.Done()
}(to)
}
// Wait on the all responses to close the channel to signal that the operation is complete
go func() {
wg.Wait()
close(responseChannel)
}()
}

// SendIndividualMail sends an email message
func SendIndividualMail(to server.Recipient, from server.Sender, message server.MailMessage, client Client) (*rest.Response, error) {
sendFrom := sendgridMail.NewEmail(from.Name, from.Address)
sendTo := sendgridMail.NewEmail(to.Name, to.Address)
sendMessage := sendgridMail.NewSingleEmail(sendFrom, message.Subject, sendTo, message.Body, message.RichBody)
sendMessage.SetTemplateID(message.TemplateId)
sendMessage.SetSendAt(int(message.ScheduleSendAtTimestamp))
return client.Send(sendMessage)
}
Loading