From 5746b4bc70a6899596d1b84be5511442853c3303 Mon Sep 17 00:00:00 2001 From: Quest Henkart Date: Thu, 10 Feb 2022 00:22:56 +0530 Subject: [PATCH 01/24] feat: Refactor Inbound package to provide access to SendGrid's pre-processing (#443) * primary code transfer and expansion * add validation method * pass existing tests * add a test for validation * add parsed fields and update docs * parse envelope * update docs * docs * whitespace * simplify * comments * defensively checks parsed header to avoid breaking email clients * updates warning in documentation * updates docs, DRY, and adds an additional test --- helpers/inbound/README.md | 62 ++++++++++-- helpers/inbound/inbound.go | 169 +++++++++++++++++++++++++++++--- helpers/inbound/inbound_test.go | 59 ++++++++++- 3 files changed, 262 insertions(+), 28 deletions(-) diff --git a/helpers/inbound/README.md b/helpers/inbound/README.md index b7c06e86..9b26fc2f 100644 --- a/helpers/inbound/README.md +++ b/helpers/inbound/README.md @@ -2,10 +2,39 @@ ## Table of Contents +* [Fields](#fields) * [Example Usage](#example-usage) * [Testing the Source Code](#testing) * [Contributing](#contributing) +# Fields + +### ParsedEmail.Envelope + ParsedEmail.Envelope.To and ParsedEmail.Envelope.From represent the exact email addresses that the email was sent to and the exact email address of the sender. There are no special characters and these fields are safe to use without further parsing as email addresses + +### ParsedEmail.ParsedValues + Please see [SendGrid Docs](https://docs.sendgrid.com/for-developers/parsing-email/setting-up-the-inbound-parse-webhook) to see what fields are available and preparsed by SendGrid. Use these fields over the Headers as they are parsed by SendGrid and gauranteed to be consistent + +### ParsedEmail.TextBody + this field will satisfy most cases. SendGrid pre-parses the body into a plain text string separated with \n + +### ParsedEmail.ParsedAttachments + populated **only** when processing the email with ParseWithAttachments(). Provides the following ease of use values + - File: full attachment for uploading or processing (see example to upload to s3) + - Size: file size, useful for filtering or setting upper limits to attachments + - Filename: copies the original filename of the attachment, if there is not one, it defaults to 'Untitled' + - ContentType: the type of file + +### ParsedEmail.Body + populated *only* when the raw option is checked in the SendGrid Dashboard. Provides the raw HTML body of the email, unless you need to record the exact unparsed HTML payload from the email client, you should use the parsed fields instead. The field is named Body for backward compatability + +### ParsedEmail.Attachments + populated *only* when the raw option is checked in the SendGrid Dashboard. This field is deprecated. Use ParsedAttachments instead which does not require the Raw setting, and provides parsed values to use and process the attachments + +### ParsedEmail.Headers + this field is deprecated. Use the SendGrid processed fields in ParsedValues instead. While it maintains its presence to avoid breaking changes, it provides raw, unprocessed headers and not all email clients are compatible. For example. these fields will be empty if the email cient is Outlook.com + + # Example Usage ```go @@ -20,27 +49,40 @@ import ( ) func inboundHandler(response http.ResponseWriter, request *http.Request) { - parsedEmail, err := Parse(request) + parsedEmail, err := ParseWithAttachments(request) if err != nil { log.Fatal(err) } - - fmt.Print(parsedEmail.Headers["From"]) - - for filename, contents := range parsedEmail.Attachments { + + fmt.Print(parsedEmail.Envelope.From) + + for filename, contents := range parsedEmail.ParsedAttachments { // Do something with an attachment handleAttachment(filename, contents) } - - for section, body := range parsedEmail.Body { - // Do something with the email body - handleEmail(body) + + + for section, body := range strings.Split(parsedEmail.TextBody, "\n") { + // Do something with the email lines } - + + // Twilio SendGrid needs a 200 OK response to stop POSTing response.WriteHeader(http.StatusOK) } +// example of uploading an attachment to s3 using the Go sdk-2 +func handleAttachment(parsedEmail *ParsedEmail) { + for _, contents := range parsedEmail.ParsedAttachments { + if _, err := sgh.Client.Upload(ctx, &s3.PutObjectInput{ + Bucket: &bucket, + Key: &uploadPath, + Body: contents.File, + ContentType: aws.String(contents.ContentType), + } + } +} + func main() { http.HandleFunc("/inbound", inboundHandler) if err := http.ListenAndServe(":8000", nil); err != nil { diff --git a/helpers/inbound/inbound.go b/helpers/inbound/inbound.go index cd95e242..3c688fca 100644 --- a/helpers/inbound/inbound.go +++ b/helpers/inbound/inbound.go @@ -1,6 +1,8 @@ package inbound import ( + "encoding/json" + "fmt" "io" "io/ioutil" "mime" @@ -9,20 +11,72 @@ import ( "strings" ) +// ParsedEmail defines a multipart parsed email +// Body and Attachments are only populated if the Raw option is checked on the SendGrid inbound configuration and are named for backwards compatability type ParsedEmail struct { - Headers map[string]string - Body map[string]string + // Header values are raw and not pre-processed by SendGrid. They may change depending on the email client. Use carefully + Headers map[string]string + // Please see https://docs.sendgrid.com/for-developers/parsing-email/setting-up-the-inbound-parse-webhook to see the available fields in the email headers + // all fields listed there are available within the headers map except for text which lives in the TextBody field + ParsedValues map[string]string + // Primary email body parsed with \n. A common approach is to Split by the \n to bring every line of the email into a string array + TextBody string + + // Envelope expresses the exact email address that the email was addressed to and the exact email address it was from, without extra characters + Envelope struct { + From string `json:"from"` + To []string `json:"to"` + } + + // Attachments have been fully parsed to include the filename, size, content type and actual file for uploading or processing + ParsedAttachments map[string]*EmailAttachment + + // Raw only Attachments map[string][]byte - rawRequest *http.Request + // accessed with text/html and text/plain. text/plain is always parsed to the TextBody field + Body map[string]string + + rawRequest *http.Request + rawValues map[string][]string + withAttachments bool } -func Parse(request *http.Request) (*ParsedEmail, error) { - result := ParsedEmail{ - Headers: make(map[string]string), +// EmailAttachment defines information related to an email attachment +type EmailAttachment struct { + File multipart.File `json:"-"` + Filename string `json:"filename"` + Size int64 `json:"-"` + ContentType string `json:"type"` +} + +func newParsedEmail(request *http.Request) ParsedEmail { + return ParsedEmail{ + Headers: make(map[string]string), + ParsedValues: make(map[string]string), + ParsedAttachments: make(map[string]*EmailAttachment), + Body: make(map[string]string), Attachments: make(map[string][]byte), - rawRequest: request, + + rawRequest: request, + withAttachments: false, } +} + +// Parse parses an email using Go's multipart parser and populates the headers, and body +// This method skips processing the attachment file and is therefore more performant +func Parse(request *http.Request) (*ParsedEmail, error) { + result := newParsedEmail(request) + + err := result.parse() + return &result, err +} + +// ParseWithAttachments parses an email using Go's multipart parser and populates the headers, body and processes attachments +func ParseWithAttachments(request *http.Request) (*ParsedEmail, error) { + result := newParsedEmail(request) + result.withAttachments = true + err := result.parse() return &result, err } @@ -32,13 +86,75 @@ func (email *ParsedEmail) parse() error { if err != nil { return err } - emails := email.rawRequest.MultipartForm.Value["email"] - headers := email.rawRequest.MultipartForm.Value["headers"] - if len(headers) > 0 { - email.parseHeaders(headers[0]) + + email.rawValues = email.rawRequest.MultipartForm.Value + + // unmarshal the envelope + if len(email.rawValues["envelope"]) > 0 { + if err := json.Unmarshal([]byte(email.rawValues["envelope"][0]), &email.Envelope); err != nil { + return err + } + } + + // parse included headers + if len(email.rawValues["headers"]) > 0 { + email.parseHeaders(email.rawValues["headers"][0]) + } + + // apply the rest of the SendGrid fields to the headers map + for k, v := range email.rawValues { + if k == "text" || k == "email" || k == "headers" || k == "envelope" { + continue + } + + if len(v) > 0 { + email.ParsedValues[k] = v[0] + } } - if len(emails) > 0 { - email.parseRawEmail(emails[0]) + + // apply the plain text body + if len(email.rawValues["text"]) > 0 { + email.TextBody = email.rawValues["text"][0] + } + + // only included if the raw box is checked + if len(email.rawValues["email"]) > 0 { + email.parseRawEmail(email.rawValues["email"][0]) + } + + // if the client chose not to parse attachments, return as is + if !email.withAttachments { + return nil + } + + return email.parseAttachments(email.rawValues) +} + +func (email *ParsedEmail) parseAttachments(values map[string][]string) error { + if len(values["attachment-info"]) != 1 { + return nil + } + // unmarshal the sendgrid parsed aspects of the email attachment into the attachment struct + if err := json.Unmarshal([]byte(values["attachment-info"][0]), &email.ParsedAttachments); err != nil { + return err + } + + // range through the multipart files + for key, val := range email.rawRequest.MultipartForm.File { + // open the attachment file for processing + file, err := val[0].Open() + if err != nil { + return err + } + + // add the actual file and the size to the parsed files + email.ParsedAttachments[key].File = file + email.ParsedAttachments[key].Size = val[0].Size + + // if the file does not have a name. give it Untitled + if email.ParsedAttachments[key].Filename == "" { + email.ParsedAttachments[key].Filename = "Untitled" + } } return nil @@ -87,6 +203,7 @@ func (email *ParsedEmail) parseRawEmail(rawEmail string) error { if err != nil { return err } + email.Body[header] = string(b) } } @@ -108,6 +225,32 @@ func (email *ParsedEmail) parseHeaders(headers string) { splitHeaders := strings.Split(strings.TrimSpace(headers), "\n") for _, header := range splitHeaders { splitHeader := strings.SplitN(header, ": ", 2) + // keeps outlook emails from causing a panic + if len(splitHeader) != 2 { + continue + } + email.Headers[splitHeader[0]] = splitHeader[1] } } + +// Validate validates the DKIM and SPF scores to ensure that the email client and address was not spoofed +func (email *ParsedEmail) Validate() error { + if len(email.rawValues["dkim"]) == 0 || len(email.rawValues["SPF"]) == 0 { + return fmt.Errorf("missing DKIM and SPF score") + } + + for _, val := range email.rawValues["dkim"] { + if !strings.Contains(val, "pass") { + return fmt.Errorf("DKIM validation failed") + } + } + + for _, val := range email.rawValues["SPF"] { + if !strings.Contains(val, "pass") { + return fmt.Errorf("SPF validation failed") + } + } + + return nil +} diff --git a/helpers/inbound/inbound_test.go b/helpers/inbound/inbound_test.go index b8b0019e..d776f767 100644 --- a/helpers/inbound/inbound_test.go +++ b/helpers/inbound/inbound_test.go @@ -60,12 +60,13 @@ func TestParse(t *testing.T) { email, err := Parse(req) if test.expectedError != nil { assert.Error(subTest, err, "expected an error to occur") - } else { - assert.NoError(subTest, err, "did NOT expect an error to occur") - - from := "Example User " - assert.Equalf(subTest, email.Headers["From"], from, "Expected From: %s, Got: %s", from, email.Headers["From"]) + return } + + assert.NoError(subTest, err, "did NOT expect an error to occur") + + from := "Example User " + assert.Equalf(subTest, email.Headers["From"], from, "Expected From: %s, Got: %s", from, email.Headers["From"]) }) } } @@ -129,3 +130,51 @@ Content-Transfer-Encoding: quoted-printable // Content-Type multipart/mixed; boundary=TwiLIo // Hello Twilio SendGrid! } + +func TestValidate(t *testing.T) { + tests := []struct { + name string + values map[string][]string + expectedError error + }{ + { + name: "MissingHeaders", + values: map[string][]string{}, + expectedError: fmt.Errorf("missing DKIM and SPF score"), + }, + { + name: "FailedDkim", + values: map[string][]string{"dkim": {"pass", "fail", "pass"}, "SPF": {"pass"}}, + expectedError: fmt.Errorf("DKIM validation failed"), + }, + { + name: "FailedSpf", + values: map[string][]string{"dkim": {"pass", "pass", "pass"}, "SPF": {"pass", "fail", "pass"}}, + expectedError: fmt.Errorf("SPF validation failed"), + }, + { + name: "FailedSpfandDkim", + values: map[string][]string{"dkim": {"pass", "pass", "fail"}, "SPF": {"pass", "fail", "pass"}}, + expectedError: fmt.Errorf("DKIM validation failed"), + }, + { + name: "success", + values: map[string][]string{"dkim": {"pass", "pass", "pass"}, "SPF": {"pass", "pass", "pass"}}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(subTest *testing.T) { + //Load POST body + email := ParsedEmail{rawValues: test.values} + err := email.Validate() + + if test.expectedError != nil { + assert.EqualError(subTest, test.expectedError, err.Error()) + return + } + + assert.NoError(subTest, err, "did NOT expect an error to occur") + }) + } +} From 04025b0c7a357411a3d40c9d9695b6c975ec8378 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 9 Feb 2022 14:49:27 -0800 Subject: [PATCH 02/24] [Librarian] Version Bump --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36d7f321..4460efe9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Change Log All notable changes to this project will be documented in this file. +[2022-02-09] Version 3.11.0 +--------------------------- +**Library - Feature** +- [PR #443](https://github.com/sendgrid/sendgrid-go/pull/443): Refactor Inbound package to provide access to SendGrid's pre-processing. Thanks to [@qhenkart](https://github.com/qhenkart)! + +**Library - Docs** +- [PR #454](https://github.com/sendgrid/sendgrid-go/pull/454): add docs for bypass mail options. Thanks to [@shwetha-manvinkurke](https://github.com/shwetha-manvinkurke)! + +**Library - Chore** +- [PR #453](https://github.com/sendgrid/sendgrid-go/pull/453): upgrade supported language versions. Thanks to [@childish-sambino](https://github.com/childish-sambino)! +- [PR #452](https://github.com/sendgrid/sendgrid-go/pull/452): merge test and gh release workflows. Thanks to [@shwetha-manvinkurke](https://github.com/shwetha-manvinkurke)! + + [2022-01-12] Version 3.10.5 --------------------------- **Library - Chore** From 1101132fabbaac513f12beedfdd4bc32ec22ec97 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 9 Feb 2022 14:49:28 -0800 Subject: [PATCH 03/24] Release v3.11.0 --- base_interface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base_interface.go b/base_interface.go index 27a56537..e8ce4686 100644 --- a/base_interface.go +++ b/base_interface.go @@ -13,7 +13,7 @@ import ( // Version is this client library's current version const ( - Version = "3.10.5" + Version = "3.11.0" rateLimitRetry = 5 rateLimitSleep = 1100 ) From 542c69c0f83829e316acd42af1d5bd081d6a1f52 Mon Sep 17 00:00:00 2001 From: Elise Shanholtz Date: Mon, 28 Feb 2022 11:58:14 -0800 Subject: [PATCH 04/24] chore: push Datadog Release Metric upon deploy success (#456) --- .github/workflows/test-and-deploy.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml index 89ebb7f8..45dbc4c3 100644 --- a/.github/workflows/test-and-deploy.yml +++ b/.github/workflows/test-and-deploy.yml @@ -59,6 +59,11 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Submit metric to Datadog + uses: sendgrid/dx-automator/actions/datadog-release-metric@main + env: + DD_API_KEY: ${{ secrets.DATADOG_API_KEY }} + notify-on-failure: name: Slack notify on failure if: failure() && github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref_type == 'tag') From d14424fe164be200afe469434f80619386cdd7f1 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 9 Mar 2022 12:21:55 -0800 Subject: [PATCH 05/24] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4460efe9..f24d4542 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2022-03-09] Version 3.11.1 +--------------------------- +**Library - Chore** +- [PR #456](https://github.com/sendgrid/sendgrid-go/pull/456): push Datadog Release Metric upon deploy success. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + + [2022-02-09] Version 3.11.0 --------------------------- **Library - Feature** From 237c26eafeed68a6757901218b8657e897cc305b Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 9 Mar 2022 12:21:55 -0800 Subject: [PATCH 06/24] Release v3.11.1 --- base_interface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base_interface.go b/base_interface.go index e8ce4686..257fec14 100644 --- a/base_interface.go +++ b/base_interface.go @@ -13,7 +13,7 @@ import ( // Version is this client library's current version const ( - Version = "3.11.0" + Version = "3.11.1" rateLimitRetry = 5 rateLimitSleep = 1100 ) From 77fd696d4df4096aec140f0d34f085d786ff5c19 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Thu, 24 Mar 2022 10:48:14 -0500 Subject: [PATCH 07/24] chore: remove outdated announcements --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 44b09021..9bb50d92 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,6 @@ [![GitHub contributors](https://img.shields.io/github/contributors/sendgrid/sendgrid-go.svg)](https://github.com/sendgrid/sendgrid-go/graphs/contributors) [![Open Source Helpers](https://www.codetriage.com/sendgrid/sendgrid-go/badges/users.svg)](https://www.codetriage.com/sendgrid/sendgrid-go) -**NEW:** Subscribe to email [notifications](https://dx.sendgrid.com/newsletter/go) for releases and breaking changes. - -**The default branch name for this repository has been changed to `main` as of 07/27/2020.** - **This library allows you to quickly and easily use the Twilio SendGrid Web API v3 via Go.** Version 3.X.X of this library provides full support for all Twilio SendGrid [Web API v3](https://sendgrid.com/docs/API_Reference/Web_API_v3/index.html) endpoints, including the new [v3 /mail/send](https://sendgrid.com/blog/introducing-v3mailsend-sendgrids-new-mail-endpoint). @@ -227,9 +223,7 @@ Please see [our helper](helpers/inbound) for utilizing our Inbound Parse webhook # Announcements -Please see our announcement regarding [breaking changes](https://github.com/sendgrid/sendgrid-go/issues/81). Your support is appreciated! - -All updates to this library are documented in our [CHANGELOG](CHANGELOG.md) and [releases](https://github.com/sendgrid/sendgrid-go/releases). You may also subscribe to email [release notifications](https://dx.sendgrid.com/newsletter/go) for releases and breaking changes. +All updates to this library are documented in our [CHANGELOG](CHANGELOG.md) and [releases](https://github.com/sendgrid/sendgrid-go/releases). # How to Contribute From 2eb8d22d55f42d6c242e5f8e5d83ec8b9d32682b Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Fri, 25 Mar 2022 13:39:52 -0500 Subject: [PATCH 08/24] feat: add PR title validation --- .github/workflows/pr-lint.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/pr-lint.yml diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml new file mode 100644 index 00000000..8388f21e --- /dev/null +++ b/.github/workflows/pr-lint.yml @@ -0,0 +1,15 @@ +name: Lint PR +on: + pull_request_target: + types: [ opened, edited, reopened ] + +jobs: + validate: + name: Validate title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v4 + with: + types: chore docs fix feat test + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 62c49bc682ccc1d4a05046e76324de7badd78b7c Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Thu, 21 Apr 2022 14:44:36 -0500 Subject: [PATCH 09/24] test: lint PRs on synchronize events Since synchronize events clears the status checks, it needs to be re-run. --- .github/workflows/pr-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index 8388f21e..dc7af3d3 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -1,7 +1,7 @@ name: Lint PR on: pull_request_target: - types: [ opened, edited, reopened ] + types: [ opened, edited, synchronize, reopened ] jobs: validate: From 5e0c9996025c980bd77086aebb98e058c34b732a Mon Sep 17 00:00:00 2001 From: "Gareth Paul Jones (GPJ)" Date: Mon, 9 May 2022 10:03:47 -0700 Subject: [PATCH 10/24] docs: Modify README.md in alignment with SendGrid Support (#459) --- .github/ISSUE_TEMPLATE/config.yml | 10 ---------- CONTRIBUTING.md | 29 ----------------------------- ISSUE_TEMPLATE.md | 30 ------------------------------ PULL_REQUEST_TEMPLATE.md | 2 +- README.md | 9 ++++----- sendgrid_test.go | 1 - 6 files changed, 5 insertions(+), 76 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index c0bc82d0..00000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,10 +0,0 @@ -contact_links: - - name: Twilio SendGrid Support - url: https://support.sendgrid.com - about: Get Support - - name: Stack Overflow - url: https://stackoverflow.com/questions/tagged/sendgrid-go+or+sendgrid+go - about: Ask questions on Stack Overflow - - name: Documentation - url: https://sendgrid.com/docs/for-developers/ - about: View Reference Documentation diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cd224444..595d10ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,8 +2,6 @@ Hello! Thank you for choosing to help contribute to one of the Twilio SendGrid o All third-party contributors acknowledge that any contributions they provide will be made under the same open-source license that the open-source project is provided under. -- [Feature Request](#feature-request) -- [Submit a Bug Report](#submit-a-bug-report) - [Improvements to the Codebase](#improvements-to-the-codebase) - [Understanding the Code Base](#understanding-the-codebase) - [Testing](#testing) @@ -13,33 +11,6 @@ All third-party contributors acknowledge that any contributions they provide wil There are a few ways to contribute, which we'll enumerate below: - -## Feature Request - -If you'd like to make a feature request, please read this section. - -The GitHub issue tracker is the preferred channel for library feature requests, but please respect the following restrictions: - -- Please **search for existing issues** in order to ensure we don't have duplicate bugs/feature requests. -- Please be respectful and considerate of others when commenting on issues - - -## Submit a Bug Report - -Note: DO NOT include your credentials in ANY code examples, descriptions, or media you make public. - -A software bug is a demonstrable issue in the code base. In order for us to diagnose the issue and respond as quickly as possible, please add as much detail as possible into your bug report. - -Before you decide to create a new issue, please try the following: - -1. Check the Github issues tab if the identified issue has already been reported, if so, please add a +1 to the existing post. -2. Update to the latest version of this code and check if the issue has already been fixed -3. Copy and fill in the Bug Report Template we have provided below - -### Please use our Bug Report Template - -In order to make the process easier, we've included a [sample bug report template](ISSUE_TEMPLATE.md). - ## Improvements to the Codebase diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index 347eee74..00000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,30 +0,0 @@ - - -### Issue Summary -A summary of the issue and the environment in which it occurs. If suitable, include the steps required to reproduce the bug. Please feel free to include screenshots, screencasts, or code examples. - -### Steps to Reproduce -1. This is the first step -2. This is the second step -3. Further steps, etc. - -### Code Snippet -```go -# paste code here -``` - -### Exception/Log -``` -# paste exception/log here -``` - -### Technical details: -* sendgrid-go version: -* go version: - diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 567de305..0c20b485 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -28,4 +28,4 @@ A short description of what this PR does. - [ ] I have added the necessary documentation about the functionality in the appropriate .md file - [ ] I have added inline documentation to the code I modified -If you have questions, please file a [support ticket](https://support.sendgrid.com), or create a GitHub Issue in this repository. +If you have questions, please file a [support ticket](https://support.sendgrid.com). diff --git a/README.md b/README.md index 9bb50d92..a8042e11 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,7 @@ Version 3.X.X of this library provides full support for all Twilio SendGrid [Web This library represents the beginning of a new path for Twilio SendGrid. We want this library to be community driven and Twilio SendGrid led. We need your help to realize this goal. To help make sure we are building the right things in the right order, we ask that you create [issues](https://github.com/sendgrid/sendgrid-go/issues) and [pull requests](CONTRIBUTING.md) or simply upvote or comment on existing issues or pull requests. -Please browse the rest of this README for further detail. - -We appreciate your continued support, thank you! +**If you need help using SendGrid, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com).** # Table of Contents @@ -247,9 +245,10 @@ Please see our [troubleshooting guide](TROUBLESHOOTING.md) for common library is sendgrid-go is maintained and funded by Twilio SendGrid, Inc. The names and logos for sendgrid-go are trademarks of Twilio SendGrid, Inc. -If you need help installing or using the library, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com). + +# Support -If you've instead found a bug in the library or would like new features added, go ahead and open issues or pull requests against this repo! +If you need help using SendGrid, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com). # License [The MIT License (MIT)](LICENSE) diff --git a/sendgrid_test.go b/sendgrid_test.go index 5c0070f9..974a5ef8 100644 --- a/sendgrid_test.go +++ b/sendgrid_test.go @@ -34,7 +34,6 @@ func TestRepoFiles(t *testing.T) { "CHANGELOG.md", "CODE_OF_CONDUCT.md", "CONTRIBUTING.md", - "ISSUE_TEMPLATE.md", "LICENSE", "PULL_REQUEST_TEMPLATE.md", "README.md", From 7d2e30b2297daac2d6e8c221d3671e7423970733 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Thu, 12 May 2022 10:28:30 -0500 Subject: [PATCH 11/24] chore: drop the issue links from FIRST_TIMERS doc --- FIRST_TIMERS.md | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/FIRST_TIMERS.md b/FIRST_TIMERS.md index e53415c9..5f654cf4 100644 --- a/FIRST_TIMERS.md +++ b/FIRST_TIMERS.md @@ -51,29 +51,3 @@ git push origin ## Important notice Before creating a pull request, make sure that you respect the repository's constraints regarding contributions. You can find them in the [CONTRIBUTING.md](CONTRIBUTING.md) file. - -## Repositories with Open, Easy, Help Wanted, Issue Filters - -* [Python SDK](https://github.com/sendgrid/sendgrid-python/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [PHP SDK](https://github.com/sendgrid/sendgrid-php/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [C# SDK](https://github.com/sendgrid/sendgrid-csharp/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Ruby SDK](https://github.com/sendgrid/sendgrid-ruby/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Node.js SDK](https://github.com/sendgrid/sendgrid-nodejs/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Java SDK](https://github.com/sendgrid/sendgrid-java/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Go SDK](https://github.com/sendgrid/sendgrid-go/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Python SMTPAPI Client](https://github.com/sendgrid/smtpapi-python/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [PHP SMTPAPI Client](https://github.com/sendgrid/smtpapi-php/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [C# SMTPAPI Client](https://github.com/sendgrid/smtpapi-csharp/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Ruby SMTPAPI Client](https://github.com/sendgrid/smtpapi-ruby/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Node.js SMTPAPI Client](https://github.com/sendgrid/smtpapi-nodejs/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Java SMTPAPI Client](https://github.com/sendgrid/smtpapi-java/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Go SMTPAPI Client](https://github.com/sendgrid/smtpapi-go/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Python HTTP Client](https://github.com/sendgrid/python-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [PHP HTTP Client](https://github.com/sendgrid/php-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [C# HTTP Client](https://github.com/sendgrid/csharp-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Java HTTP Client](https://github.com/sendgrid/java-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Ruby HTTP Client](https://github.com/sendgrid/ruby-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Go HTTP Client](https://github.com/sendgrid/rest/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Open API Definition](https://github.com/sendgrid/sendgrid-oai/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [DX Automator](https://github.com/sendgrid/dx-automator/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Documentation](https://github.com/sendgrid/docs/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) From 6e0eb7248616322ee3946b792ea1ce2bfdd669d5 Mon Sep 17 00:00:00 2001 From: Raghav Katyal Date: Wed, 6 Jul 2022 16:04:58 -0700 Subject: [PATCH 12/24] Adding misc as PR type (#462) --- .github/workflows/pr-lint.yml | 2 +- PULL_REQUEST_TEMPLATE.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index dc7af3d3..2f5232b0 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -10,6 +10,6 @@ jobs: steps: - uses: amannn/action-semantic-pull-request@v4 with: - types: chore docs fix feat test + types: chore docs fix feat test misc env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 0c20b485..dbae6406 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -3,7 +3,7 @@ We appreciate the effort for this pull request but before that please make sure Please format the PR title appropriately based on the type of change: [!]: -Where is one of: docs, chore, feat, fix, test. +Where is one of: docs, chore, feat, fix, test, misc. Add a '!' after the type for breaking changes (e.g. feat!: new breaking feature). **All third-party contributors acknowledge that any contributions they provide will be made under the same open-source license that the open-source project is provided under.** From b63882c55712795ceca97103587781bc45bacf68 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Wed, 3 Aug 2022 10:36:57 -0500 Subject: [PATCH 13/24] docs: fix comment --- helpers/mail/mail_v3.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/mail/mail_v3.go b/helpers/mail/mail_v3.go index 59df3b46..6d7ac6b4 100644 --- a/helpers/mail/mail_v3.go +++ b/helpers/mail/mail_v3.go @@ -329,7 +329,7 @@ func (p *Personalization) AddTos(to ...*Email) { p.To = append(p.To, to...) } -//AddFrom ... +// AddFrom ... func (p *Personalization) AddFrom(from *Email) { p.From = from } From 90bbd692541581a2c4dea85ac481d5e2077cf757 Mon Sep 17 00:00:00 2001 From: Alaric Whitney Date: Wed, 14 Sep 2022 10:28:30 -0500 Subject: [PATCH 14/24] feat: go 1.19 compatibility (#464) --- .github/workflows/test-and-deploy.yml | 2 +- README.md | 2 ++ helpers/inbound/inbound.go | 7 +++---- helpers/inbound/inbound_test.go | 4 ++-- sendgrid_test.go | 3 +-- use-cases/attachments-with-mailer-helper.md | 6 +++--- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml index 45dbc4c3..f447a23c 100644 --- a/.github/workflows/test-and-deploy.yml +++ b/.github/workflows/test-and-deploy.yml @@ -17,7 +17,7 @@ jobs: timeout-minutes: 20 strategy: matrix: - go: [ '1.14', '1.15', '1.16', '1.17' ] + go: [ '1.14', '1.15', '1.16', '1.17', '1.18', '1.19' ] env: DOCKER_LOGIN: ${{ secrets.DOCKER_USERNAME && secrets.DOCKER_AUTH_TOKEN }} steps: diff --git a/README.md b/README.md index a8042e11..6a2130cd 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ This library supports the following Go implementations: * Go 1.15 * Go 1.16 * Go 1.17 +* Go 1.18 +* Go 1.19 ## Prerequisites diff --git a/helpers/inbound/inbound.go b/helpers/inbound/inbound.go index 3c688fca..ba7f0b89 100644 --- a/helpers/inbound/inbound.go +++ b/helpers/inbound/inbound.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "mime" "mime/multipart" "net/http" @@ -184,7 +183,7 @@ func (email *ParsedEmail) parseRawEmail(rawEmail string) error { break } header := emailBodyPart.Header.Get("Content-Type") - b, err := ioutil.ReadAll(emailPart) + b, err := io.ReadAll(emailPart) if err != nil { return err } @@ -192,14 +191,14 @@ func (email *ParsedEmail) parseRawEmail(rawEmail string) error { } } else if emailPart.FileName() != "" { - b, err := ioutil.ReadAll(emailPart) + b, err := io.ReadAll(emailPart) if err != nil { return err } email.Attachments[emailPart.FileName()] = b } else { header := emailPart.Header.Get("Content-Type") - b, err := ioutil.ReadAll(emailPart) + b, err := io.ReadAll(emailPart) if err != nil { return err } diff --git a/helpers/inbound/inbound_test.go b/helpers/inbound/inbound_test.go index d776f767..4dac61ab 100644 --- a/helpers/inbound/inbound_test.go +++ b/helpers/inbound/inbound_test.go @@ -3,16 +3,16 @@ package inbound import ( "bytes" "fmt" - "io/ioutil" "log" "net/http" + "os" "testing" "github.com/stretchr/testify/assert" ) func createRequest(filename string) *http.Request { - file, err := ioutil.ReadFile(filename) + file, err := os.ReadFile(filename) if err != nil { return nil } diff --git a/sendgrid_test.go b/sendgrid_test.go index 974a5ef8..f4557bd6 100644 --- a/sendgrid_test.go +++ b/sendgrid_test.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" "net/http" "net/http/httptest" "os" @@ -19,7 +18,7 @@ import ( ) func TestLicenseYear(t *testing.T) { - d, err := ioutil.ReadFile("LICENSE") + d, err := os.ReadFile("LICENSE") assert.Nil(t, err, "Cannot read the LICENSE file") l := fmt.Sprintf("Copyright (C) %v, Twilio SendGrid, Inc.", time.Now().Year()) assert.True(t, strings.Contains(string(d), l), fmt.Sprintf("License date range is not correct, it should be: %v", l)) diff --git a/use-cases/attachments-with-mailer-helper.md b/use-cases/attachments-with-mailer-helper.md index 88d0ffeb..a6da7e5c 100644 --- a/use-cases/attachments-with-mailer-helper.md +++ b/use-cases/attachments-with-mailer-helper.md @@ -34,7 +34,7 @@ func main() { // read/attach .txt file a_txt := mail.NewAttachment() - dat, err := ioutil.ReadFile("testing.txt") + dat, err := io.ReadFile("testing.txt") if err != nil { fmt.Println(err) } @@ -46,7 +46,7 @@ func main() { // read/attach .pdf file a_pdf := mail.NewAttachment() - dat, err = ioutil.ReadFile("testing.pdf") + dat, err = io.ReadFile("testing.pdf") if err != nil { fmt.Println(err) } @@ -58,7 +58,7 @@ func main() { // read/attach inline .jpg file a_jpg := mail.NewAttachment() - dat, err = ioutil.ReadFile("testing.jpg") + dat, err = io.ReadFile("testing.jpg") if err != nil { fmt.Println(err) } From 16c7cb35300eae2d8677756702b78b3307545bde Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 21 Sep 2022 11:58:00 -0700 Subject: [PATCH 15/24] [Librarian] Version Bump --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f24d4542..e28d128e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ # Change Log All notable changes to this project will be documented in this file. +[2022-09-21] Version 3.12.0 +--------------------------- +**Library - Feature** +- [PR #464](https://github.com/sendgrid/sendgrid-go/pull/464): go 1.19 compatibility. Thanks to [@AlaricWhitney](https://github.com/AlaricWhitney)! + +**Library - Test** +- [PR #462](https://github.com/sendgrid/sendgrid-go/pull/462): Adding misc as PR type. Thanks to [@rakatyal](https://github.com/rakatyal)! + +**Library - Docs** +- [PR #459](https://github.com/sendgrid/sendgrid-go/pull/459): Modify README.md in alignment with SendGrid Support. Thanks to [@garethpaul](https://github.com/garethpaul)! + + [2022-03-09] Version 3.11.1 --------------------------- **Library - Chore** From cc87a8dd8bb198511fcce9adb9bd2dd8b83afd59 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 21 Sep 2022 11:58:00 -0700 Subject: [PATCH 16/24] Release v3.12.0 --- base_interface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base_interface.go b/base_interface.go index 257fec14..60c04bda 100644 --- a/base_interface.go +++ b/base_interface.go @@ -13,7 +13,7 @@ import ( // Version is this client library's current version const ( - Version = "3.11.1" + Version = "3.12.0" rateLimitRetry = 5 rateLimitSleep = 1100 ) From bdbdc219ff7f51bd47087383c45002d43a877f07 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Tue, 3 Jan 2023 09:09:35 -0600 Subject: [PATCH 17/24] docs: updated the year in the license --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 5db04ff6..3154774a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (C) 2022, Twilio SendGrid, Inc. +Copyright (C) 2023, Twilio SendGrid, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From b4f86e391ef1e40c269286b6ca0d8645192a5ab6 Mon Sep 17 00:00:00 2001 From: Hang Qian Date: Wed, 9 Aug 2023 21:35:47 -0700 Subject: [PATCH 18/24] feat: gzip mail body when content-encoding is set to gzip (#468) [Mail body compression](https://docs.sendgrid.com/api-reference/mail-send/mail-send#mail-body-compression) is supported on V3 api. This patch added the support that when a client sets the content-encoding header to gzip, send compresses the mail body with gzip. --- base_interface.go | 20 +++++++ sendgrid_test.go | 150 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) diff --git a/base_interface.go b/base_interface.go index 60c04bda..b7472a24 100644 --- a/base_interface.go +++ b/base_interface.go @@ -1,6 +1,8 @@ package sendgrid import ( + "bytes" + "compress/gzip" "context" "errors" "net/http" @@ -61,6 +63,24 @@ func (cl *Client) Send(email *mail.SGMailV3) (*rest.Response, error) { // SendWithContext sends an email through Twilio SendGrid with context.Context. func (cl *Client) SendWithContext(ctx context.Context, email *mail.SGMailV3) (*rest.Response, error) { cl.Body = mail.GetRequestBody(email) + // when Content-Encoding header is set to "gzip" + // mail body is compressed using gzip according to + // https://docs.sendgrid.com/api-reference/mail-send/mail-send#mail-body-compression + if cl.Headers["Content-Encoding"] == "gzip" { + var gzipped bytes.Buffer + gz := gzip.NewWriter(&gzipped) + if _, err := gz.Write(cl.Body); err != nil { + return nil, err + } + if err := gz.Flush(); err != nil { + return nil, err + } + if err := gz.Close(); err != nil { + return nil, err + } + + cl.Body = gzipped.Bytes() + } return MakeRequestWithContext(ctx, cl.Request) } diff --git a/sendgrid_test.go b/sendgrid_test.go index f4557bd6..ada4dfe1 100644 --- a/sendgrid_test.go +++ b/sendgrid_test.go @@ -1602,6 +1602,156 @@ func Test_test_mail_batch__batch_id__get(t *testing.T) { assert.Equal(t, 200, response.StatusCode, "Wrong status code returned") } +func Test_test_send_client_with_mail_body_compression_enabled(t *testing.T) { + apiKey := "SENDGRID_API_KEY" + client := NewSendClient(apiKey) + client.Headers["Content-Encoding"] = "gzip" + + emailBytes := []byte(` { + "asm": { + "group_id": 1, + "groups_to_display": [ + 1, + 2, + 3 + ] + }, + "attachments": [ + { + "content": "[BASE64 encoded content block here]", + "content_id": "ii_139db99fdb5c3704", + "disposition": "inline", + "filename": "file1.jpg", + "name": "file1", + "type": "jpg" + } + ], + "batch_id": "[YOUR BATCH ID GOES HERE]", + "categories": [ + "category1", + "category2" + ], + "content": [ + { + "type": "text/html", + "value": "

Hello, world!

" + } + ], + "custom_args": { + "New Argument 1": "New Value 1", + "activationAttempt": "1", + "customerAccountNumber": "[CUSTOMER ACCOUNT NUMBER GOES HERE]" + }, + "from": { + "email": "sam.smith@example.com", + "name": "Sam Smith" + }, + "headers": {}, + "ip_pool_name": "[YOUR POOL NAME GOES HERE]", + "mail_settings": { + "bcc": { + "email": "ben.doe@example.com", + "enable": true + }, + "bypass_list_management": { + "enable": true + }, + "footer": { + "enable": true, + "html": "

Thanks
The Twilio SendGrid Team

", + "text": "Thanks,/n The Twilio SendGrid Team" + }, + "sandbox_mode": { + "enable": false + }, + "spam_check": { + "enable": true, + "post_to_url": "http://example.com/compliance", + "threshold": 3 + } + }, + "personalizations": [ + { + "bcc": [ + { + "email": "sam.doe@example.com", + "name": "Sam Doe" + } + ], + "cc": [ + { + "email": "jane.doe@example.com", + "name": "Jane Doe" + } + ], + "custom_args": { + "New Argument 1": "New Value 1", + "activationAttempt": "1", + "customerAccountNumber": "[CUSTOMER ACCOUNT NUMBER GOES HERE]" + }, + "headers": { + "X-Accept-Language": "en", + "X-Mailer": "MyApp" + }, + "send_at": 1409348513, + "subject": "Hello, World!", + "substitutions": { + "id": "substitutions", + "type": "object" + }, + "to": [ + { + "email": "john.doe@example.com", + "name": "John Doe" + } + ] + } + ], + "reply_to": { + "email": "sam.smith@example.com", + "name": "Sam Smith" + }, + "send_at": 1409348513, + "subject": "Hello, World!", + "template_id": "[YOUR TEMPLATE ID GOES HERE]", + "tracking_settings": { + "click_tracking": { + "enable": true, + "enable_text": true + }, + "ganalytics": { + "enable": true, + "utm_campaign": "[NAME OF YOUR REFERRER SOURCE]", + "utm_content": "[USE THIS SPACE TO DIFFERENTIATE YOUR EMAIL FROM ADS]", + "utm_medium": "[NAME OF YOUR MARKETING MEDIUM e.g. email]", + "utm_name": "[NAME OF YOUR CAMPAIGN]", + "utm_term": "[IDENTIFY PAID KEYWORDS HERE]" + }, + "open_tracking": { + "enable": true, + "substitution_tag": "%opentrack" + }, + "subscription_tracking": { + "enable": true, + "html": "If you would like to unsubscribe and stop receiving these emails <% clickhere %>.", + "substitution_tag": "<%click here%>", + "text": "If you would like to unsubscribe and stop receiving these emails <% click here %>." + } + } + }`) + email := &mail.SGMailV3{} + err := json.Unmarshal(emailBytes, email) + assert.Nil(t, err, fmt.Sprintf("Unmarshal error: %v", err)) + client.Request.Headers["X-Mock"] = "202" + response, err := client.Send(email) + if err != nil { + t.Log(err) + } + t.Log(response) + assert.Equal(t, 202, response.StatusCode, "Wrong status code returned") + +} + func Test_test_send_client(t *testing.T) { apiKey := "SENDGRID_APIKEY" client := NewSendClient(apiKey) From b8d18a6312f78a0247e599c4960a2ad977fc87d3 Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 10 Aug 2023 09:52:02 +0000 Subject: [PATCH 19/24] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e28d128e..db8a2199 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2023-08-10] Version 3.13.0 +--------------------------- +**Library - Feature** +- [PR #468](https://github.com/sendgrid/sendgrid-go/pull/468): gzip mail body when content-encoding is set to gzip. Thanks to [@Bankq](https://github.com/Bankq)! + + [2022-09-21] Version 3.12.0 --------------------------- **Library - Feature** From 72d6eb8df83d5cccf49a5184d99eab2796e6f805 Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 10 Aug 2023 09:52:02 +0000 Subject: [PATCH 20/24] Release v3.13.0 --- base_interface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base_interface.go b/base_interface.go index b7472a24..ae826811 100644 --- a/base_interface.go +++ b/base_interface.go @@ -15,7 +15,7 @@ import ( // Version is this client library's current version const ( - Version = "3.12.0" + Version = "3.13.0" rateLimitRetry = 5 rateLimitSleep = 1100 ) From e7f240a420c872712f5a8a9c6f85d6eab4e02879 Mon Sep 17 00:00:00 2001 From: Shubham Date: Tue, 21 Nov 2023 11:26:09 +0530 Subject: [PATCH 21/24] feat: added data residency for eu and global regions (#469) * feat: added data residency for eu and global regions * feat: added data residency for eu and global regions * fix: added setters for host and data residency * chore: corrected the naming of test cases * chore: added inline documentation for SetDataResidency and SetHost * chore: added comment for SetDataResidency --- base_interface.go | 50 ++++++++++++++ examples/dataresidency/setRegion.go | 100 ++++++++++++++++++++++++++++ sendgrid_test.go | 47 +++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 examples/dataresidency/setRegion.go diff --git a/base_interface.go b/base_interface.go index ae826811..2f4e38c7 100644 --- a/base_interface.go +++ b/base_interface.go @@ -6,6 +6,7 @@ import ( "context" "errors" "net/http" + "net/url" "strconv" "time" @@ -20,6 +21,11 @@ const ( rateLimitSleep = 1100 ) +var allowedRegionsHostMap = map[string]string{ + "eu": "https://api.eu.sendgrid.com", + "global": "https://api.sendgrid.com", +} + type options struct { Auth string Endpoint string @@ -55,6 +61,50 @@ func requestNew(options options) rest.Request { } } +// extractEndpoint extracts the endpoint from a baseURL +func extractEndpoint(link string) (string, error) { + parsedURL, err := url.Parse(link) + if err != nil { + return "", err + } + + return parsedURL.Path, nil +} + +// SetHost changes the baseURL of the request with the host passed +/* + * This allows support for global and eu regions only. This set will likely expand in the future. + * Global should be the default + * Global region means the message should be sent through: + * HTTP: api.sendgrid.com + * EU region means the message should be sent through: + * HTTP: api.eu.sendgrid.com + */ +// @return [Request] the modified request object +func SetHost(request rest.Request, host string) (rest.Request, error) { + endpoint, err := extractEndpoint(request.BaseURL) + if err != nil { + return request, err + } + + request.BaseURL = host + endpoint + return request, nil +} + +// SetDataResidency modifies the host as per the region +// @return [Request] the modified request object +func SetDataResidency(request rest.Request, region string) (rest.Request, error) { + regionalHost, present := allowedRegionsHostMap[region] + if !present { + return request, errors.New("error: region can only be \"eu\" or \"global\"") + } + request, err := SetHost(request, regionalHost) + if err != nil { + return request, err + } + return request, nil +} + // Send sends an email through Twilio SendGrid func (cl *Client) Send(email *mail.SGMailV3) (*rest.Response, error) { return cl.SendWithContext(context.Background(), email) diff --git a/examples/dataresidency/setRegion.go b/examples/dataresidency/setRegion.go new file mode 100644 index 00000000..8376c67b --- /dev/null +++ b/examples/dataresidency/setRegion.go @@ -0,0 +1,100 @@ +package main + +import ( + "fmt" + "log" + "os" + + "github.com/sendgrid/rest" + "github.com/sendgrid/sendgrid-go/helpers/mail" + + "github.com/sendgrid/sendgrid-go" +) + +var SAMPLE_EMAIL = "test@example.com" + +// SetDataResidency : Set region for sendgrid. +func SetDataResidencyGlobal() { + message := buildHelloEmail() + request, err := buildSendgridObj("global") + if err != nil { + log.Println(err) + } else { + request.Body = mail.GetRequestBody(message) + response, err := sendgrid.API(request) + if err != nil { + log.Println(err) + } else { + fmt.Println(response.StatusCode) + fmt.Println(response.Body) + fmt.Println(response.Headers) + } + } +} + +func SetDataResidencyEu() { + message := buildHelloEmail() + request, err := buildSendgridObj("eu") + if err != nil { + log.Println(err) + } else { + request.Body = mail.GetRequestBody(message) + response, err := sendgrid.API(request) + if err != nil { + log.Println(err) + } else { + fmt.Println(response.StatusCode) + fmt.Println(response.Body) + fmt.Println(response.Headers) + } + } +} + +func SetDataResidencyDefault() { + message := buildHelloEmail() + request := sendgrid.GetRequest(os.Getenv("SENDGRID_API_KEY"), "/v3/mail/send", "") + request.Method = "POST" + request.Body = mail.GetRequestBody(message) + response, err := sendgrid.API(request) + if err != nil { + log.Println(err) + } else { + fmt.Println(response.StatusCode) + fmt.Println(response.Body) + fmt.Println(response.Headers) + } +} + +func buildHelloEmail() *mail.SGMailV3 { + // Note that when you use this constructor an initial personalization object + // is created for you. It can be accessed via + // mail.personalization.get(0) as it is a List object + + from := mail.NewEmail("test_user", SAMPLE_EMAIL) + subject := "Sending with Twilio SendGrid is Fun" + to := mail.NewEmail("test_user", SAMPLE_EMAIL) + plainTextContent := "and easy to do anywhere, even with Go" + htmlContent := "and easy to do anywhere, even with Go" + message := mail.NewSingleEmail(from, subject, to, plainTextContent, htmlContent) + email := mail.NewEmail("test_user", SAMPLE_EMAIL) + + p := mail.NewPersonalization() + p.AddTos(email) + message.AddPersonalizations(p) + + return message +} + +func buildSendgridObj(region string) (rest.Request, error) { + request := sendgrid.GetRequest(os.Getenv("SENDGRID_API_KEY"), "/v3/mail/send", "") + request.Method = "POST" + request, err := sendgrid.SetDataResidency(request, region) + if err != nil { + return request, err + } + return request, nil +} + +func main() { + // add your function calls here +} diff --git a/sendgrid_test.go b/sendgrid_test.go index ada4dfe1..365d46e5 100644 --- a/sendgrid_test.go +++ b/sendgrid_test.go @@ -81,6 +81,53 @@ func TestGetRequestSubuser(t *testing.T) { ShouldHaveHeaders(&request, t) } +func TestSetDataResidencyEU(t *testing.T) { + request := GetRequest("API_KEY", "", "") + request, err := SetDataResidency(request, "eu") + assert.Nil(t, err) + assert.Equal(t, "https://api.eu.sendgrid.com", request.BaseURL, "Host not correct as per the region") +} + +func TestSetDataResidencyGlobal(t *testing.T) { + request := GetRequest("API_KEY", "", "https://api.sendgrid.com") + request, err := SetDataResidency(request, "global") + assert.Nil(t, err) + assert.Equal(t, "https://api.sendgrid.com", request.BaseURL, "Host not correct as per the region") +} + +func TestSetDataResidencyOverrideHost(t *testing.T) { + request := GetRequest("API_KEY", "", "https://test.api.com") + request, err := SetDataResidency(request, "eu") + assert.Nil(t, err) + assert.Equal(t, "https://api.eu.sendgrid.com", request.BaseURL, "Host not correct as per the region") +} + +func TestSetDataResidencyOverrideDataResidency(t *testing.T) { + request := GetRequest("API_KEY", "", "") + request, err := SetDataResidency(request, "eu") + assert.Nil(t, err) + request, err = SetHost(request, "https://test.api.com") + assert.Nil(t, err) + assert.Equal(t, "https://test.api.com", request.BaseURL, "Host not correct as per the region") +} + +func TestSetDataResidencyIncorrectRegion(t *testing.T) { + request := GetRequest("API_KEY", "", "") + _, err := SetDataResidency(request, "foo") + assert.NotNil(t, err, "error: region can only be \"eu\" or \"global\"") +} + +func TestSetDataResidencyNullRegion(t *testing.T) { + request := GetRequest("API_KEY", "", "") + _, err := SetDataResidency(request, "") + assert.NotNil(t, err, "error: region can only be \"eu\" or \"global\"") +} + +func TestSetDataResidencyDefaultRegion(t *testing.T) { + request := GetRequest("API_KEY", "", "") + assert.Equal(t, "https://api.sendgrid.com", request.BaseURL, "Host not correct as per the region") +} + func getRequest(endpoint string) rest.Request { return GetRequest("SENDGRID_APIKEY", endpoint, "") } From ec79368cd5a8488bd14506812b1fa44dd65cf7e3 Mon Sep 17 00:00:00 2001 From: Shubham Date: Thu, 23 Nov 2023 14:26:36 +0530 Subject: [PATCH 22/24] chore: removed SetHost and shifted SetDataResidency to sendgrid.go (#470) --- base_interface.go | 50 ----------------------------------------------- sendgrid.go | 41 ++++++++++++++++++++++++++++++++++++++ sendgrid_test.go | 9 --------- 3 files changed, 41 insertions(+), 59 deletions(-) diff --git a/base_interface.go b/base_interface.go index 2f4e38c7..ae826811 100644 --- a/base_interface.go +++ b/base_interface.go @@ -6,7 +6,6 @@ import ( "context" "errors" "net/http" - "net/url" "strconv" "time" @@ -21,11 +20,6 @@ const ( rateLimitSleep = 1100 ) -var allowedRegionsHostMap = map[string]string{ - "eu": "https://api.eu.sendgrid.com", - "global": "https://api.sendgrid.com", -} - type options struct { Auth string Endpoint string @@ -61,50 +55,6 @@ func requestNew(options options) rest.Request { } } -// extractEndpoint extracts the endpoint from a baseURL -func extractEndpoint(link string) (string, error) { - parsedURL, err := url.Parse(link) - if err != nil { - return "", err - } - - return parsedURL.Path, nil -} - -// SetHost changes the baseURL of the request with the host passed -/* - * This allows support for global and eu regions only. This set will likely expand in the future. - * Global should be the default - * Global region means the message should be sent through: - * HTTP: api.sendgrid.com - * EU region means the message should be sent through: - * HTTP: api.eu.sendgrid.com - */ -// @return [Request] the modified request object -func SetHost(request rest.Request, host string) (rest.Request, error) { - endpoint, err := extractEndpoint(request.BaseURL) - if err != nil { - return request, err - } - - request.BaseURL = host + endpoint - return request, nil -} - -// SetDataResidency modifies the host as per the region -// @return [Request] the modified request object -func SetDataResidency(request rest.Request, region string) (rest.Request, error) { - regionalHost, present := allowedRegionsHostMap[region] - if !present { - return request, errors.New("error: region can only be \"eu\" or \"global\"") - } - request, err := SetHost(request, regionalHost) - if err != nil { - return request, err - } - return request, nil -} - // Send sends an email through Twilio SendGrid func (cl *Client) Send(email *mail.SGMailV3) (*rest.Response, error) { return cl.SendWithContext(context.Background(), email) diff --git a/sendgrid.go b/sendgrid.go index 7192bec2..1c50451c 100644 --- a/sendgrid.go +++ b/sendgrid.go @@ -1,7 +1,9 @@ package sendgrid import ( + "errors" "github.com/sendgrid/rest" + "net/url" ) // sendGridOptions for CreateRequest @@ -12,6 +14,12 @@ type sendGridOptions struct { Subuser string } +// sendgrid host map for different regions +var allowedRegionsHostMap = map[string]string{ + "eu": "https://api.eu.sendgrid.com", + "global": "https://api.sendgrid.com", +} + // GetRequest // @return [Request] a default request object func GetRequest(key, endpoint, host string) rest.Request { @@ -47,3 +55,36 @@ func NewSendClient(key string) *Client { request.Method = "POST" return &Client{request} } + +// extractEndpoint extracts the endpoint from a baseURL +func extractEndpoint(link string) (string, error) { + parsedURL, err := url.Parse(link) + if err != nil { + return "", err + } + + return parsedURL.Path, nil +} + +// SetDataResidency modifies the host as per the region +/* + * This allows support for global and eu regions only. This set will likely expand in the future. + * Global should be the default + * Global region means the message should be sent through: + * HTTP: api.sendgrid.com + * EU region means the message should be sent through: + * HTTP: api.eu.sendgrid.com + */ +// @return [Request] the modified request object +func SetDataResidency(request rest.Request, region string) (rest.Request, error) { + regionalHost, present := allowedRegionsHostMap[region] + if !present { + return request, errors.New("error: region can only be \"eu\" or \"global\"") + } + endpoint, err := extractEndpoint(request.BaseURL) + if err != nil { + return request, err + } + request.BaseURL = regionalHost + endpoint + return request, nil +} diff --git a/sendgrid_test.go b/sendgrid_test.go index 365d46e5..57b52709 100644 --- a/sendgrid_test.go +++ b/sendgrid_test.go @@ -102,15 +102,6 @@ func TestSetDataResidencyOverrideHost(t *testing.T) { assert.Equal(t, "https://api.eu.sendgrid.com", request.BaseURL, "Host not correct as per the region") } -func TestSetDataResidencyOverrideDataResidency(t *testing.T) { - request := GetRequest("API_KEY", "", "") - request, err := SetDataResidency(request, "eu") - assert.Nil(t, err) - request, err = SetHost(request, "https://test.api.com") - assert.Nil(t, err) - assert.Equal(t, "https://test.api.com", request.BaseURL, "Host not correct as per the region") -} - func TestSetDataResidencyIncorrectRegion(t *testing.T) { request := GetRequest("API_KEY", "", "") _, err := SetDataResidency(request, "foo") From 05cf7617f3cd20ce77bdc3cd60b8f5bb4736edc6 Mon Sep 17 00:00:00 2001 From: Twilio Date: Fri, 1 Dec 2023 05:16:19 +0000 Subject: [PATCH 23/24] [Librarian] Version Bump --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db8a2199..fe962665 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Change Log All notable changes to this project will be documented in this file. +[2023-12-01] Version 3.14.0 +--------------------------- +**Library - Chore** +- [PR #470](https://github.com/sendgrid/sendgrid-go/pull/470): removed SetHost and shifted SetDataResidency to sendgrid.go. Thanks to [@tiwarishubham635](https://github.com/tiwarishubham635)! + +**Library - Feature** +- [PR #469](https://github.com/sendgrid/sendgrid-go/pull/469): added data residency for eu and global regions. Thanks to [@tiwarishubham635](https://github.com/tiwarishubham635)! + + [2023-08-10] Version 3.13.0 --------------------------- **Library - Feature** From 574e54c73371f737f02d2dcafc0a5b3f56a2a1e9 Mon Sep 17 00:00:00 2001 From: Twilio Date: Fri, 1 Dec 2023 05:16:19 +0000 Subject: [PATCH 24/24] Release v3.14.0 --- base_interface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base_interface.go b/base_interface.go index ae826811..f9c0d609 100644 --- a/base_interface.go +++ b/base_interface.go @@ -15,7 +15,7 @@ import ( // Version is this client library's current version const ( - Version = "3.13.0" + Version = "3.14.0" rateLimitRetry = 5 rateLimitSleep = 1100 )