├── .env_sample
├── .github
└── workflows
│ ├── pr-lint.yml
│ └── test-and-deploy.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── FIRST_TIMERS.md
├── LICENSE
├── Makefile
├── PULL_REQUEST_TEMPLATE.md
├── README.md
├── TROUBLESHOOTING.md
├── USAGE.md
├── examples
└── example.go
├── go.test.sh
├── smtpapi.go
├── smtpapi_test.go
├── smtpapi_test_strings.json
├── static
└── img
│ ├── github-fork.png
│ └── github-sign-up.png
├── twilio_sendgrid_logo.png
└── use_cases
└── README.md
/.env_sample:
--------------------------------------------------------------------------------
1 | export SENDGRID_API_KEY=''
--------------------------------------------------------------------------------
/.github/workflows/pr-lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint PR
2 | on:
3 | pull_request_target:
4 | types: [ opened, edited, synchronize, reopened ]
5 |
6 | jobs:
7 | validate:
8 | name: Validate title
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: amannn/action-semantic-pull-request@v4
12 | with:
13 | types: chore docs fix feat test misc
14 | env:
15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
16 |
--------------------------------------------------------------------------------
/.github/workflows/test-and-deploy.yml:
--------------------------------------------------------------------------------
1 | name: Test and Deploy
2 | on:
3 | push:
4 | branches: [ '*' ]
5 | tags: [ '*' ]
6 | pull_request:
7 | branches: [ main ]
8 | schedule:
9 | # Run automatically at 8AM PST Monday-Friday
10 | - cron: '0 15 * * 1-5'
11 | workflow_dispatch:
12 |
13 | jobs:
14 | test:
15 | name: Test
16 | runs-on: ubuntu-latest
17 | timeout-minutes: 20
18 | strategy:
19 | matrix:
20 | go: [ '1.11', '1.12', '1.13', '1.14', '1.15', '1.16', '1.17' ]
21 | steps:
22 | - name: Checkout smtpapi-go
23 | uses: actions/checkout@v2
24 |
25 | - name: Setup Go environment
26 | uses: actions/setup-go@v2
27 | with:
28 | go-version: ${{ matrix.go }}
29 |
30 | - name: Set Go env vars
31 | run: |
32 | echo "GOPATH=$HOME" >> $GITHUB_ENV
33 | echo "GOBIN=$HOME/bin" >> $GITHUB_ENV
34 | echo "GO111MODULE=off" >> $GITHUB_ENV
35 |
36 | - name: Run Tests
37 | run: make test
38 |
39 | deploy:
40 | name: Deploy
41 | if: success() && github.ref_type == 'tag'
42 | needs: [ test ]
43 | runs-on: ubuntu-latest
44 | steps:
45 | - name: Checkout smtpapi-go
46 | uses: actions/checkout@v2
47 |
48 | - name: Create GitHub Release
49 | uses: sendgrid/dx-automator/actions/release@main
50 | env:
51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
52 |
53 | - name: Submit metric to Datadog
54 | uses: sendgrid/dx-automator/actions/datadog-release-metric@main
55 | env:
56 | DD_API_KEY: ${{ secrets.DATADOG_API_KEY }}
57 |
58 | notify-on-failure:
59 | name: Slack notify on failure
60 | if: failure() && github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref_type == 'tag')
61 | needs: [ test, deploy ]
62 | runs-on: ubuntu-latest
63 | steps:
64 | - uses: rtCamp/action-slack-notify@v2
65 | env:
66 | SLACK_COLOR: 'failure'
67 | SLACK_ICON_EMOJI: ':github:'
68 | SLACK_MESSAGE: ${{ format('Test *{0}*, Deploy *{1}*, {2}/{3}/actions/runs/{4}', needs.test.result, needs.deploy.result, github.server_url, github.repository, github.run_id) }}
69 | SLACK_TITLE: Action Failure - ${{ github.repository }}
70 | SLACK_USERNAME: GitHub Actions
71 | SLACK_MSG_AUTHOR: twilio-dx
72 | SLACK_FOOTER: Posted automatically using GitHub Actions
73 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
74 | MSG_MINIMAL: true
75 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.dll
4 | *.so
5 | *.dylib
6 |
7 | # Test binary, build with `go test -c`
8 | *.test
9 |
10 | # Output of the go coverage tool, specifically when used with LiteIDE
11 | *.out
12 |
13 | .DS_Store
14 | .env
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All the notable changes to this project will be documented in this file.
3 |
4 | This project adheres to [Semantic Versioning](http://semver.org/).
5 |
6 | [2022-03-09] Version 0.6.9
7 | --------------------------
8 | **Library - Chore**
9 | - [PR #91](https://github.com/sendgrid/smtpapi-go/pull/91): push Datadog Release Metric upon deploy success. Thanks to [@eshanholtz](https://github.com/eshanholtz)!
10 |
11 |
12 | [2022-02-09] Version 0.6.8
13 | --------------------------
14 | **Library - Chore**
15 | - [PR #90](https://github.com/sendgrid/smtpapi-go/pull/90): upgrade supported language versions. Thanks to [@childish-sambino](https://github.com/childish-sambino)!
16 | - [PR #89](https://github.com/sendgrid/smtpapi-go/pull/89): add deploy job to test and deploy gh workflow. Thanks to [@Hunga1](https://github.com/Hunga1)!
17 |
18 |
19 | [2021-12-15] Version 0.6.7
20 | --------------------------
21 | **Library - Chore**
22 | - [PR #88](https://github.com/sendgrid/smtpapi-go/pull/88): migrate to GitHub Actions. Thanks to [@beebzz](https://github.com/beebzz)!
23 |
24 |
25 | [2020-08-19] Version 0.6.6
26 | --------------------------
27 | **Library - Chore**
28 | - [PR #86](https://github.com/sendgrid/smtpapi-go/pull/86): update GitHub branch references. Thanks to [@thinkingserious](https://github.com/thinkingserious)!
29 |
30 | **Library - Docs**
31 | - [PR #68](https://github.com/sendgrid/smtpapi-go/pull/68): Run *.md documents through Grammer.ly. Thanks to [@ssiddhantsharma](https://github.com/ssiddhantsharma)!
32 |
33 |
34 | [2020-08-05] Version 0.6.5
35 | --------------------------
36 | **Library - Test**
37 | - [PR #56](https://github.com/sendgrid/smtpapi-go/pull/56): add CodeCov support to .travis.yml. Thanks to [@mptap](https://github.com/mptap)!
38 |
39 |
40 | [2020-07-08] Version 0.6.4
41 | --------------------------
42 | **Library - Docs**
43 | - [PR #82](https://github.com/sendgrid/smtpapi-go/pull/82): add use cases directory. Thanks to [@mtroiani](https://github.com/mtroiani)!
44 |
45 |
46 | [2020-02-19] Version 0.6.3
47 | --------------------------
48 | **Library - Docs**
49 | - [PR #61](https://github.com/sendgrid/smtpapi-go/pull/61): Added Code Review to Contributing.md. Thanks to [@mptap](https://github.com/mptap)!
50 |
51 |
52 | [2020-02-05] Version 0.6.2
53 | --------------------------
54 | **Library - Docs**
55 | - [PR #77](https://github.com/sendgrid/smtpapi-go/pull/77): Fixed link to bug report template. Thanks to [@alxshelepenok](https://github.com/alxshelepenok)!
56 |
57 |
58 | [2020-01-30] Version 0.6.1
59 | --------------------------
60 | **Library - Docs**
61 | - [PR #85](https://github.com/sendgrid/smtpapi-go/pull/85): baseline all the templated markdown docs. Thanks to [@childish-sambino](https://github.com/childish-sambino)!
62 | - [PR #80](https://github.com/sendgrid/smtpapi-go/pull/80): added announcement to README. Thanks to [@luciajimenez](https://github.com/luciajimenez)!
63 |
64 | **Library - Chore**
65 | - [PR #84](https://github.com/sendgrid/smtpapi-go/pull/84): prep repo for automation. Thanks to [@thinkingserious](https://github.com/thinkingserious)!
66 | - [PR #65](https://github.com/sendgrid/smtpapi-go/pull/65): fix failing build. Thanks to [@vaskoz](https://github.com/vaskoz)!
67 | - [PR #69](https://github.com/sendgrid/smtpapi-go/pull/69): add style standards check. Thanks to [@pangaunn](https://github.com/pangaunn)!
68 |
69 |
70 | [2018-06-04] Version 0.6.0
71 | --------------------------
72 | ### Added
73 | - PR [#24](https://github.com/sendgrid/smtpapi-go/pull/24): Added Code of Conduct. Big thanks to [Shivam Agarwal](https://github.com/gr8shivam) for the PR!
74 | - PR [#26](https://github.com/sendgrid/smtpapi-go/pull/26): Added TROUBLESHOOTING.md. Big thanks to [Callam Delaney](https://github.com/cal97g) for the PR!
75 | - PR [#30](https://github.com/sendgrid/smtpapi-go/pull/30): SEO updates. Big thanks to [Eric Liu](https://github.com/eric1iu) for the PR!
76 | - PR [#32](https://github.com/sendgrid/smtpapi-go/pull/32): Added table of contents to README.md. Big thanks to [thepriefy](https://github.com/thepriefy) for the PR!
77 | - PR [#29](https://github.com/sendgrid/smtpapi-go/pull/29): Added README.md updates. Big thanks to [Andrew Hamilton](https://github.com/ahamilton55) for the PR!
78 | - PR [#36](https://github.com/sendgrid/smtpapi-go/pull/36): Added .github/ISSUE_TEMPLATE. Big thanks to [thepriefy](https://github.com/thepriefy) for the PR!
79 | - PR [#31](https://github.com/sendgrid/smtpapi-go/pull/31): Parallelize all tests. Big thanks to [Vasko Zdravevski](https://github.com/vaskoz) for the PR!
80 | - PR [#38](https://github.com/sendgrid/smtpapi-go/pull/38): Created PULL_REQUEST_TEMPLATE. Big thanks to [Anatoly](https://github.com/anatolyyyyyy) for the PR!
81 | - PR [#51](https://github.com/sendgrid/smtpapi-go/pull/51): Don't ignore erros in test. Big thanks to [Revolta](https://github.com/keydrevolta) for the PR!
82 | - PR [#52](https://github.com/sendgrid/smtpapi-go/pull/52): Include Gometalinter in the Travis CI build. Big thanks to [Vasko Zdravevski](https://github.com/vaskoz) for the PR!
83 | - PR [#42](https://github.com/sendgrid/smtpapi-go/pull/42): Added a .env_sample file, updated gitignore, updated README.md. Big thanks to [thepriefy](https://github.com/thepriefy) for the PR!
84 | - PR [#44](https://github.com/sendgrid/smtpapi-go/pull/44): Added Usage.md. Big thanks to [Tariq Ibrahim](https://github.com/tariq1890) for the PR!
85 | - PR [#49](https://github.com/sendgrid/smtpapi-go/pull/49): Added tests(License): The end year in the license file should be current year. Big thanks to [Marco Antônio Singer](https://github.com/marcosinger) for the PR!
86 | - PR [#53](https://github.com/sendgrid/smtpapi-go/pull/53): Added tests to check for the certain repo files to exist in the repo. Big thanks to [Gabriel Lima](https://github.com/gabrielclima) for the PR!
87 | - PR [#63](https://github.com/sendgrid/smtpapi-go/pull/63): Adding dynamic data support. Big thanks to [Ron Canada](https://github.com/roncanada) for the PR!
88 |
89 | ### Fixed
90 | - PR [#21](https://github.com/sendgrid/smtpapi-go/pull/21): README.md typo. Big thanks to [Cícero Pablo](https://github.com/ciceropablo) for the PR!
91 | - PR [#22](https://github.com/sendgrid/smtpapi-go/pull/22): Fixed golint issues, added gitignore and boosted the test coverage to 100%. Big thanks to [Tariq Ibrahim](https://github.com/tariq1890) for the PR!
92 | - PR [#33](https://github.com/sendgrid/smtpapi-go/pull/33): Using the right contributors badge for smtpapi-go. Big thanks to [Tariq Ibrahim](https://github.com/tariq1890) for the PR!
93 | - PR [#35](https://github.com/sendgrid/smtpapi-go/pull/35): Fixed the CONTRIBUTING.md typo. Big thanks to [Alex](https://github.com/pushkyn) for the PR!
94 | - PR [#48](https://github.com/sendgrid/smtpapi-go/pull/48): Update the LICENSE.txt year. Big thanks to [Alex](https://github.com/pushkyn) for the PR!
95 |
96 | ## [0.5.0] - 2017-06-13
97 | ### Added
98 | - PR #20: Support asm_groups_to_display
99 | - A big thanks to [Chris Lee](https://github.com/clee) for the PR!
100 |
101 | ## [0.4.2] - 2017-05-14
102 | ### Added
103 | - PR #19: Improved the code coverage
104 | - A big thanks to [Vasko Zdravevski](https://github.com/vaskoz) for the PR!
105 |
106 | ## [0.4.1] - 2016-11-15
107 | ### Fixed
108 | - PR #16: Add support for the non-string filter settings
109 | - A big thanks to [Stephen Young](https://github.com/hownowstephen) for the PR!
110 |
111 | ## [0.4.0] - 2015-7-19
112 | ### Added
113 | - IP Pools
114 | - Version constant
115 |
116 | ## [0.3.0] - 2015-04-09
117 | ### Added
118 | - Scheduling Parameters
119 | - CHANGELOG
120 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | - Using welcoming and inclusive language
18 | - Being respectful of differing viewpoints and experiences
19 | - Gracefully accepting constructive criticism
20 | - Focusing on what is best for the community
21 | - Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | - The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | - Trolling, insulting/derogatory comments, and personal or political attacks
28 | - Public or private harassment
29 | - Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | - Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at open-source@twilio.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Hello! Thank you for considering to help contribute to one of the SendGrid open source libraries. There are many ways you can contribute to your projects and additional help is always welcome. We simply require that you follow the following contribution policies.
2 |
3 | - [Improvements to the Codebase](#improvements-to-the-codebase)
4 | - [Understanding the Code Base](#understanding-the-codebase)
5 | - [Testing](#testing)
6 | - [Style Guidelines & Naming Conventions](#style-guidelines-and-naming-conventions)
7 | - [Creating a Pull Request](#creating-a-pull-request)
8 | - [Code Reviews](#code-reviews)
9 |
10 |
11 | ## Improvements to the Codebase
12 |
13 | We welcome direct contributions to our smtpapi-go code base. Thank you!
14 |
15 | ### Development Environment ###
16 |
17 | #### Install and Run Locally ####
18 |
19 | ##### Supported Versions #####
20 |
21 | - Go 1.11-1.16
22 |
23 | ##### Initial setup: #####
24 |
25 | ```bash
26 | git clone https://github.com/sendgrid/smtpapi-go.git
27 | cd smtpapi-go
28 | ```
29 |
30 | ##### Execute: #####
31 |
32 | See the [examples folder](examples) to get started quickly.
33 |
34 | To run the example:
35 |
36 | ```bash
37 | go run examples/example.go
38 | ```
39 |
40 |
41 | ## Understanding the Code Base
42 |
43 | **/examples**
44 |
45 | Working examples that demonstrate usage.
46 |
47 | **/**
48 |
49 | *_test are the tests
50 | smtpapi.go is the source code
51 |
52 |
53 | ## Testing
54 |
55 | All PRs require passing tests before the PR will be reviewed.
56 |
57 | All test files are in the [`smtpapi_test.go`](smtpapi_test.go) file.
58 |
59 | For the purposes of contributing to this repo, please update the [`smtpapi_test.go`](smtpapi_test.go) and [`smtpapi_test_strings.json`](smtpapi_test_strings.json) files with unit tests as you modify the code.
60 |
61 | To run the tests:
62 |
63 | ```bash
64 | go test -v
65 | ```
66 |
67 |
68 | ## Style Guidelines & Naming Conventions
69 |
70 | Generally, we follow the style guidelines as suggested by the official language. However, we ask that you conform to the styles that already exist in the library. If you wish to deviate, please explain your reasoning.
71 |
72 | - [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)
73 |
74 | Please run your code through:
75 |
76 | - [fmt](https://blog.golang.org/go-fmt-your-code)
77 |
78 | ## Creating a Pull Request
79 |
80 | 1. [Fork](https://help.github.com/fork-a-repo/) the project, clone your fork,
81 | and configure the remotes:
82 |
83 | ```bash
84 | # Clone your fork of the repo into the current directory
85 | git clone https://github.com/sendgrid/smtpapi-go
86 | # Navigate to the newly cloned directory
87 | cd smtpapi-go
88 | # Assign the original repo to a remote called "upstream"
89 | git remote add upstream https://github.com/sendgrid/smtpapi-go
90 | ```
91 |
92 | 2. If you cloned this a while ago, get the latest changes from upstream:
93 |
94 | ```bash
95 | git checkout
96 | git pull upstream
97 | ```
98 |
99 | 3. Create a new topic branch (off the main project development branch) to
100 | contain your feature, change, or fix:
101 |
102 | ```bash
103 | git checkout -b
104 | ```
105 |
106 | 4. Commit your changes in logical chunks. Please adhere to these [git commit
107 | message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
108 | or your code is unlikely be merged into the main project. Use Git's
109 | [interactive rebase](https://help.github.com/articles/interactive-rebase)
110 | feature to tidy up your commits before making them public.
111 |
112 | 4a. Create tests.
113 |
114 | 4b. Create or update the example code that demonstrates the functionality of this change to the code.
115 |
116 | 5. Locally merge (or rebase) the upstream development branch into your topic branch:
117 |
118 | ```bash
119 | git pull [--rebase] upstream main
120 | ```
121 |
122 | 6. Push your topic branch up to your fork:
123 |
124 | ```bash
125 | git push origin
126 | ```
127 |
128 | 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
129 | with a clear title and description against the `main` branch. All tests must be passing before we will review the PR.
130 |
131 |
132 | ## Code Reviews
133 |
134 | If you can, please look at open PRs and review them. Give feedback and help us merge these PRs much faster! If you don't know how, Github has some [great information on how to review a Pull Request](https://help.github.com/articles/about-pull-request-reviews/).
135 |
--------------------------------------------------------------------------------
/FIRST_TIMERS.md:
--------------------------------------------------------------------------------
1 | # How To Contribute to Twilio SendGrid Repositories via GitHub
2 | Contributing to the Twilio SendGrid repositories is easy! All you need to do is find an open issue (see the bottom of this page for a list of repositories containing open issues), fix it and submit a pull request. Once you have submitted your pull request, the team can easily review it before it is merged into the repository.
3 |
4 | To make a pull request, follow these steps:
5 |
6 | 1. Log into GitHub. If you do not already have a GitHub account, you will have to create one in order to submit a change. Click the Sign up link in the upper right-hand corner to create an account. Enter your username, password, and email address. If you are an employee of Twilio SendGrid, please use your full name with your GitHub account and enter Twilio SendGrid as your company so we can easily identify you.
7 |
8 |
9 |
10 | 2. __[Fork](https://help.github.com/fork-a-repo/)__ the [smtpapi-go](https://github.com/sendgrid/smtpapi-go) repository:
11 |
12 |
13 |
14 | 3. __Clone__ your fork via the following commands:
15 |
16 | ```bash
17 | # Clone your fork of the repo into the current directory
18 | git clone https://github.com/your_username/smtpapi-go
19 | # Navigate to the newly cloned directory
20 | cd smtpapi-go
21 | # Assign the original repo to a remote called "upstream"
22 | git remote add upstream https://github.com/sendgrid/smtpapi-go
23 | ```
24 |
25 | > Don't forget to replace *your_username* in the URL by your real GitHub username.
26 |
27 | 4. __Create a new topic branch__ (off the main project development branch) to contain your feature, change, or fix:
28 |
29 | ```bash
30 | git checkout -b
31 | ```
32 |
33 | 5. __Commit your changes__ in logical chunks.
34 |
35 | Please adhere to these [git commit message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) or your code is unlikely be merged into the main project. Use Git's [interactive rebase](https://help.github.com/articles/interactive-rebase) feature to tidy up your commits before making them public. Probably you will also have to create tests (if needed) or create or update the example code that demonstrates the functionality of this change to the code.
36 |
37 | 6. __Locally merge (or rebase)__ the upstream development branch into your topic branch:
38 |
39 | ```bash
40 | git pull [--rebase] upstream main
41 | ```
42 |
43 | 7. __Push__ your topic branch up to your fork:
44 |
45 | ```bash
46 | git push origin
47 | ```
48 |
49 | 8. __[Open a Pull Request](https://help.github.com/articles/creating-a-pull-request/#changing-the-branch-range-and-destination-repository/)__ with a clear title and description against the `main` branch. All tests must be passing before we will review the PR.
50 |
51 | ## Important notice
52 |
53 | 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.
54 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (C) 2023, Twilio SendGrid, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9 | of the Software, and to permit persons to whom the Software is furnished to do
10 | so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: test install
2 |
3 | install:
4 | go get -t -v ./...
5 |
6 | test: install
7 | go test -race -cover -v ./...
8 |
--------------------------------------------------------------------------------
/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
16 |
17 | # Fixes #
18 |
19 | A short description of what this PR does.
20 |
21 | ### Checklist
22 | - [x] I acknowledge that all my contributions will be made under the project's license
23 | - [ ] I have made a material change to the repo (functionality, testing, spelling, grammar)
24 | - [ ] I have read the [Contribution Guidelines](https://github.com/sendgrid/smtpapi-go/blob/main/CONTRIBUTING.md) and my PR follows them
25 | - [ ] I have titled the PR appropriately
26 | - [ ] I have updated my branch with the main branch
27 | - [ ] I have added tests that prove my fix is effective or that my feature works
28 | - [ ] I have added the necessary documentation about the functionality in the appropriate .md file
29 | - [ ] I have added inline documentation to the code I modified
30 |
31 | If you have questions, please file a [support ticket](https://support.sendgrid.com), or create a GitHub Issue in this repository.
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://github.com/sendgrid/smtpapi-go/actions/workflows/test-and-deploy.yml)
4 | [](LICENSE)
5 | [](https://twitter.com/sendgrid)
6 | [](https://github.com/sendgrid/smtpapi-go/graphs/contributors)
7 | [](https://goreportcard.com/report/github.com/sendgrid/smtpapi-go)
8 | [](https://godoc.org/github.com/sendgrid/smtpapi-go)
9 |
10 | **This is a simple library to simplify the process of using [SendGrid's](https://sendgrid.com) [X-SMTPAPI](http://sendgrid.com/docs/API_Reference/SMTP_API/index.html) with the Go programming language**
11 |
12 | # Table of Contents
13 |
14 | * [Announcements](#announcements)
15 | * [Installation](#installation)
16 | * [Quick Start](#quick-start)
17 | * [Usage](#usage)
18 | * [How to Contribute](#how-to-contribute)
19 | * [About](#about)
20 | * [Support](#support)
21 | * [License](#license)
22 |
23 | # Announcements
24 | All the updates to this library are documented in our [CHANGELOG](CHANGELOG.md).
25 |
26 |
27 | # Installation
28 |
29 | ## Supported Versions
30 |
31 | * Go version 1.11-1.17
32 |
33 | ## Prerequisites
34 |
35 | * The SendGrid service, starting at the [free level](https://sendgrid.com/free?source=smtpapi-go)
36 |
37 | ## Install the Package
38 |
39 | ```bash
40 | go get github.com/sendgrid/smtpapi-go
41 | ```
42 |
43 | ## Setup the Environment Variables
44 |
45 | ### Environment Variable
46 |
47 | Update the development environment with your [SENDGRID_API_KEY](https://app.sendgrid.com/settings/api_keys), for example:
48 |
49 | ```bash
50 | echo "export SENDGRID_API_KEY='YOUR_API_KEY'" > sendgrid.env
51 | echo "sendgrid.env" >> .gitignore
52 | source ./sendgrid.env
53 | ```
54 |
55 |
56 | # Quick Start
57 |
58 | ```go
59 | package main
60 |
61 | import (
62 | "github.com/sendgrid/smtpapi-go"
63 | "fmt"
64 | )
65 |
66 | func main() {
67 | header := smtpapi.NewSMTPAPIHeader()
68 | header.AddTo("test@example.com")
69 | fmt.Println(header.JSONString())
70 | }
71 | ```
72 |
73 | # Usage
74 |
75 | * [SendGrid Docs](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html)
76 | * [Example Code](examples)
77 |
78 | # How to Contribute
79 |
80 | We encourage contribution to our libraries, please see our [CONTRIBUTING](CONTRIBUTING.md) guide for more details on contributions.
81 |
82 | Quick links:
83 |
84 | * [Feature Request](CONTRIBUTING.md#feature-request)
85 | * [Bug Reports](CONTRIBUTING.md#submit-a-bug-report)
86 | * [Improvements to the Codebase](CONTRIBUTING.md#improvements-to-the-codebase)
87 | * [Review Pull Requests](CONTRIBUTING.md#code-reviews)
88 |
89 |
90 | # About
91 |
92 | smtpapi-go is maintained and funded by Twilio SendGrid, Inc. The names and logos for smtpapi-go are trademarks of Twilio SendGrid, Inc.
93 |
94 |
95 | # Support
96 |
97 | If you need help with SendGrid, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com).
98 |
99 |
100 | # License
101 |
102 | [The MIT License (MIT)](LICENSE)
103 |
--------------------------------------------------------------------------------
/TROUBLESHOOTING.md:
--------------------------------------------------------------------------------
1 | If you have a non-library SendGrid issue, please contact our [support team](https://support.sendgrid.com).
2 |
3 | If you can't find an issue below, please open an [issue](https://github.com/sendgrid/smtpapi-go/issues).
4 |
5 |
6 | ## Table of Contents
7 | * [Viewing the Request Body](#request-body)
8 |
9 |
10 | ## Viewing the request body
11 |
12 | If you are having issues with the SMTPAPI, or it is not working in the way you expect,
13 | viewing the instructions that are being sent by you, is a good place to start your troubleshooting.
14 |
15 | You can view the headers that we are passing to the SendGrid API in the following way.
16 |
17 | ```golang
18 | package main
19 |
20 | import (
21 | "fmt"
22 | "github.com/sendgrid/smtpapi-go"
23 | )
24 |
25 | func main(){
26 | //instantiate NewSMTPAPIHeader
27 | header := smtapi.NewSMTPAPIHeader()
28 |
29 | //Set some value
30 | header.AddCategory("NewUser")
31 |
32 | fmt.Println(header.JSONString())
33 | }
34 | ```
35 |
36 | ```json
37 | {"category":["NewUser"]}
38 | ```
39 |
40 | Now you can ensure that your headers will add up to your desired behaviour.
41 |
--------------------------------------------------------------------------------
/USAGE.md:
--------------------------------------------------------------------------------
1 | # Usage
2 |
3 | ## Initialisation
4 |
5 | ```go
6 | package main
7 |
8 | import (
9 | "fmt"
10 | "github.com/sendgrid/smtpapi-go"
11 | )
12 |
13 | header := smtpapi.NewSMTPAPIHeader()
14 | ```
15 |
16 | ## Table of Contents
17 |
18 | - [Recipients](#recipients)
19 | - [Categories](#categories)
20 | - [Substitution](#substitution)
21 | - [Sections](#sections)
22 | - [Filters](#filters)
23 | - [Advanced Suppression Manager (ASM)](#asm)
24 | - [Send At](#send-at)
25 | - [Batches](#batches)
26 | - [IP Pools](#ip-pools)
27 | - [Retrieving Data](#retrieving-data)
28 |
29 |
30 | ## Recipients
31 |
32 | #### Adding a single recipient
33 |
34 | The `header.AddTo` method allows you to add a new recipient to the `To` array.
35 |
36 | To do this, you can use the function with an email address, and optionally include the recipient's name as a second parameter.
37 |
38 | ```go
39 | header.SetTo("example@example.com") // An email address with no name provided
40 | header.SetTo("example@example.com", "An Example User") // An email address with a name provided as the second parameter
41 | ```
42 |
43 | #### Setting multiple recipients
44 |
45 | The `header.SetTos` method allows you to set multiple recipients by providing an array of email addresses.
46 |
47 | This will set the `To` array to the array you provide. This will need to either be an array of emails, or if names are provided, they need to be formatted as `{{ name }} <{{ email }}>`.
48 |
49 | ```go
50 | header.SetTos([]string{
51 | "example@example.com", // An email address with no name
52 | "An Example User ", // An email with a name field
53 | })
54 | ```
55 |
56 |
57 | ## Categories
58 |
59 | Categories are a useful way to organise your email analytics by tagging your emails with a specific type or topic.
60 |
61 | There are multiple methods available for setting your categories when sending emails.
62 |
63 | #### Adding a single category
64 |
65 | The `header.AddCategory` method can be used to add a category to your list.
66 |
67 | ```go
68 | header.AddCategory('marketing') // Add a new category of 'marketing' to the array of categories
69 | ```
70 |
71 | #### Setting multiple categories
72 |
73 | The `header.SetCategories` method can be used to set your categories list to an array of strings.
74 |
75 | This is useful when there are a set amount of categories required for the email you are sending.
76 |
77 | This method will remove any categories that have been previously set.
78 |
79 | ```go
80 | header.SetCategories([]string{
81 | "marketing",
82 | "sales",
83 | }) // Sets the current categories to be 'marketing' and 'sales'
84 | ```
85 |
86 | #### Setting a single category
87 |
88 | The `header.SetCategory` method can be used to set a single specific category.
89 |
90 | It is useful for removing the previously set categories and it will create a new array with the string you provide.
91 |
92 | This method will remove any categories that have been previously set.
93 |
94 | ```go
95 | header.SetCategory("marketing") // Reset the categories to be 'marketing' only
96 | ```
97 |
98 |
99 | ## Substitution
100 |
101 | Substitutions are a great way of writing some short dynamic email content easily,
102 |
103 | #### Adding a single substitution string
104 |
105 | The `header.AddSubstitution` method can be used to replace the content for recipients.
106 |
107 | ```go
108 | header.AddSubstitution("-name-", "John") // Replace the -name- variable with John.
109 | ```
110 |
111 | #### Setting substitution strings
112 |
113 | The `header.SetSubstitutions` method can be used to replace the content for any number of strings.
114 |
115 | This method will reset any key pairs that have previously been set.
116 |
117 | ```go
118 | header.SetSubstitutions(map[string][]string{
119 | "-name-": {"John", "Jane"}, // Replace the -name- variable to John or Jane
120 | "-number-": {"555.555.5555", "777.777.7777"}, // Replace the -number- variable with the provided numbers
121 | })
122 | ```
123 |
124 |
125 | ## Sections
126 |
127 | Sections that are similar to substitutions, but are specific to the actual message rather than the recipient.
128 |
129 | This is useful when you are sending multiple emails with the same style, but with different content.
130 |
131 | Note that substitution variables can also be included within a section, but the section variables cannot.
132 |
133 | #### Adding a section
134 |
135 | The `header.AddSection` method can be used to add a new section to the sections array. This will be useful for building up a list of sections dynamically, perhaps based on a user's actions.
136 |
137 | ```go
138 | header.AddSection("-event_details-", "The event will be held tomorrow.") // Replaces -event_details- with the event's string
139 | ```
140 |
141 | #### Setting multiple sections
142 |
143 | The `header.SetSections` allows you to set multiple sections in a single array.
144 |
145 | This is good when sending out multiple emails where there are no dynamic variations required.
146 |
147 | This will reset any section key-pairs that have previously been set.
148 |
149 | ```go
150 | header.SetSections(map[string]string{
151 | "-event_details-": "The event will be held tomorrow.",
152 | "-event_open_time-": "'It will be open from 1am to 9pm.",
153 | })
154 | ```
155 |
156 |
157 | ## Filters
158 |
159 | Filters allow you to dynamically toggle features such as click tracking, blind copying and DKIM domain validation.
160 |
161 | #### Adding a single filter
162 |
163 | Adding a filter is easy by using the `header.AddFilter` method.
164 |
165 | This method requires 3 values:
166 | - The filter's name
167 | - The parameter's name
168 | - The value
169 |
170 | ```go
171 | header.AddFilter("dkim", "use_from", true)
172 | header.AddFilter("dkim", "domain", "example.com")
173 | ```
174 |
175 | #### Adding pre-existing filters
176 |
177 | Filters with pre-determined settings can also be added using the `header.SetFilter` method.
178 |
179 | ```go
180 | filter := &Filter{
181 | Settings: make(map[string]interface{}),
182 | }
183 | filter.Settings["enable"] = 1
184 | filter.Settings["text/plain"] = "You can haz footers!"
185 | header.SetFilter("footer", filter)
186 | ```
187 |
188 |
189 | ## Advanced Suppression Management (ASM)
190 |
191 | Advanced Suppression Management (or Unsubscribe Groups) are a good way of allowing recipients to unsubscribe from a specific set of emails.
192 |
193 | You can
194 |
195 | #### Setting the ASM group ID
196 |
197 | The `header.SetASMGroupID` method is a quick way to set the type of email that you are sending.
198 |
199 | All it requires is the ID of the ASM group, which can be found using the API.
200 |
201 | ```go
202 | header.SetASMGroupID(42) // Sets the ASM ID to 42
203 | ```
204 |
205 |
206 | ## Send At
207 |
208 | Scheduling the time of your email campaign can be done using a collection of quick and easy methods.
209 |
210 | #### Adding a single 'send at' date
211 |
212 | The `header.AddSendEachAt` method is a good way to add the time to send the email.
213 |
214 | This method requires a unix timestamp as the input.
215 |
216 | ```go
217 | header.AddSendEachAt(1508694645)
218 | ```
219 |
220 | #### Setting multiple 'send at' date
221 |
222 | The `header.SetSendEachAt` method is a useful method for setting an array of times to which recipients have their emails sent.
223 |
224 | This method requires an array of unix timestamps as the input.
225 |
226 | ```go
227 | header.SetSendEachAt([]int64{
228 | 1508694645,
229 | 1508694835,
230 | })
231 | ```
232 |
233 | #### Setting a single date to send all emails
234 |
235 | The `header.SetSendAt` method is useful for setting a single time that all emails in the collection will be sent.
236 |
237 | This method requires a unix timestamp as the input.
238 |
239 | ```go
240 | header.SetSendAt(1508694645)
241 | ```
242 |
243 |
244 | ## Batches
245 |
246 | Batches are a great way to group a collection of scheduled items for sending. It allows you to cancel the scheduled emails, and provides more control over the emails.
247 |
248 | The batch ID can be set using the `header.AddBatchId` method. You must have generated the batch ID first with the help of the API.
249 |
250 | ```go
251 | header.AddBatchId("HkJ5yLYULb7Rj8GKSx7u025ouWVlMgAi") // Adds a previously generated batch ID to the emails
252 | ```
253 |
254 |
255 | ## IP Pools
256 |
257 | IP Pools allow you to group SendGrid IP addresses together. For example, if you have a set of marketing IPs, you can assign them a pool ID of `marketing`.
258 |
259 | The IP Pool name can be set using the `header.SetIpPool` method. You must have generated the IP Pool first with the help of the API.
260 |
261 | ```go
262 | header.SetIpPool("marketing") // Sets the IP Pool to be the marketing collection of IPs
263 | ```
264 |
265 |
266 |
267 | #### Retrieving data as a JSON string
268 |
269 | The `header.JSONString` method allows the data from the header instance to be exported as a JSON string.
270 |
271 | ```go
272 | headerString, _ := header.JSONString()
273 | fmt.Println(headerString)
274 |
275 | // {"to":["test@example.com"],"sub":{"key":["value"]},"section":{"section":"value"},"category":["category"],"unique_args":{"key":"value"},"filters":{"filter":{"settings":{"setting":"value"}}},"asm_group_id":1,"send_at":1428611024,"ip_pool":"testPool"}
276 | ```
--------------------------------------------------------------------------------
/examples/example.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/sendgrid/smtpapi-go"
6 | )
7 |
8 | func main() {
9 | header := smtpapi.NewSMTPAPIHeader()
10 |
11 | // Recipients
12 | header.AddTo("test@example.com")
13 | // or
14 | //tos := []string{"test1@example.com", "test2@example.com"}
15 | //header.AddTos(tos)
16 | // or
17 | //header.SetTos(tos)
18 |
19 | //[Substitutions](http://sendgrid.com/docs/API_Reference/SMTP_API/substitution_tags.html)
20 | header.AddSubstitution("key", "value")
21 | // or
22 | //values := []string{"value1", "value2"}
23 | //header.AddSubstitutions("key", values)
24 | // or
25 | //sub := make(map[string][]string)
26 | //sub["key"] = values
27 | //header.SetSubstitutions(sub)
28 |
29 | //[Section](http://sendgrid.com/docs/API_Reference/SMTP_API/section_tags.html)
30 | header.AddSection("section", "value")
31 | // or
32 | //sections := make(map[string]string)
33 | //sections["section"] = "value"
34 | //header.SetSections(sections)
35 |
36 | //[Category](http://sendgrid.com/docs/Delivery_Metrics/categories.html)
37 | header.AddCategory("category")
38 | // or
39 | //categories := []string{"setCategories"}
40 | //header.AddCategories(categories)
41 | // or
42 | //header.SetCategories(categories)
43 |
44 | //[Unique Arguments](http://sendgrid.com/docs/API_Reference/SMTP_API/unique_arguments.html)
45 | header.AddUniqueArg("key", "value")
46 | // or
47 | //args := make(map[string]string)
48 | //args["key"] = "value"
49 | //header.SetUniqueArgs(args)
50 |
51 | //[Filters](http://sendgrid.com/docs/API_Reference/SMTP_API/apps.html)
52 | header.AddFilter("filter", "setting", "value")
53 | // or
54 | /*
55 | filter := &Filter{
56 | Settings: make(map[string]string),
57 | }
58 | filter.Settings["enable"] = "1"
59 | filter.Settings["text/plain"] = "You can haz footers!"
60 | header.SetFilter("footer", filter)
61 | */
62 |
63 | //[Send At](https://sendgrid.com/docs/API_Reference/SMTP_API/scheduling_parameters.html)
64 | header.SetSendAt(1428611024)
65 | // or
66 | //sendEachAt := []int64{1428611024, 1428611025}
67 | //header.SetSendEachAt(sendEachAt)
68 | // or
69 | //header.AddSendEachAt(1428611024)
70 | //header.AddSendEachAt(1428611025)
71 |
72 | //[ASM Group ID](https://sendgrid.com/docs/User_Guide/advanced_suppression_manager.html)
73 | asmGroupID := 1
74 | header.SetASMGroupID(asmGroupID)
75 |
76 | // [IP Pools](https://sendgrid.com/docs/API_Reference/Web_API_v3/IP_Management/ip_pools.html)
77 | header.SetIpPool("testPool")
78 |
79 | fmt.Println(header.JSONString())
80 | }
81 |
--------------------------------------------------------------------------------
/go.test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 | echo "" > coverage.txt
5 |
6 | for d in $(go list ./... | grep -v vendor); do
7 | go test -race -coverprofile=profile.out -covermode=atomic $d
8 | if [ -f profile.out ]; then
9 | cat profile.out >> coverage.txt
10 | rm profile.out
11 | fi
12 | done
13 |
--------------------------------------------------------------------------------
/smtpapi.go:
--------------------------------------------------------------------------------
1 | package smtpapi
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "unicode/utf16"
8 | )
9 |
10 | // Version represents the current version of the smtpapi-go library
11 | const Version = "0.6.9"
12 |
13 | // SMTPAPIHeader will be used to set up X-SMTPAPI params
14 | type SMTPAPIHeader struct {
15 | To []string `json:"to,omitempty"`
16 | Sub map[string][]string `json:"sub,omitempty"`
17 | Section map[string]string `json:"section,omitempty"`
18 | Category []string `json:"category,omitempty"`
19 | UniqueArgs map[string]string `json:"unique_args,omitempty"`
20 | Filters map[string]Filter `json:"filters,omitempty"`
21 | ASMGroupID int `json:"asm_group_id,omitempty"`
22 | ASMGroups []int `json:"asm_groups_to_display,omitempty"`
23 | SendAt int64 `json:"send_at,omitempty"`
24 | SendEachAt []int64 `json:"send_each_at,omitempty"`
25 | IpPool string `json:"ip_pool,omitempty"`
26 | BatchID string `json:"batch_id,omitempty"`
27 | DynamicData map[string]interface{} `json:"dynamic_template_data,omitempty"`
28 | }
29 |
30 | // Filter represents an App/Filter and its settings
31 | type Filter struct {
32 | Settings map[string]interface{} `json:"settings,omitempty"`
33 | }
34 |
35 | // NewSMTPAPIHeader creates a new header struct
36 | func NewSMTPAPIHeader() *SMTPAPIHeader {
37 | return &SMTPAPIHeader{}
38 | }
39 |
40 | // AddTo appends a single email to the To header
41 | func (h *SMTPAPIHeader) AddTo(email string) {
42 | h.To = append(h.To, email)
43 | }
44 |
45 | // AddTos appends multiple emails to the To header
46 | func (h *SMTPAPIHeader) AddTos(emails []string) {
47 | for i := 0; i < len(emails); i++ {
48 | h.AddTo(emails[i])
49 | }
50 | }
51 |
52 | // SetTos sets the value of the To header
53 | func (h *SMTPAPIHeader) SetTos(emails []string) {
54 | h.To = emails
55 | }
56 |
57 | // AddSubstitution adds a new substitution to a specific key
58 | func (h *SMTPAPIHeader) AddSubstitution(key, sub string) {
59 | if h.Sub == nil {
60 | h.Sub = make(map[string][]string)
61 | }
62 | h.Sub[key] = append(h.Sub[key], sub)
63 | }
64 |
65 | // AddSubstitutions adds a multiple substitutions to a specific key
66 | func (h *SMTPAPIHeader) AddSubstitutions(key string, subs []string) {
67 | for i := 0; i < len(subs); i++ {
68 | h.AddSubstitution(key, subs[i])
69 | }
70 | }
71 |
72 | // SetSubstitutions sets the value of the substitutions on the Sub header
73 | func (h *SMTPAPIHeader) SetSubstitutions(sub map[string][]string) {
74 | h.Sub = sub
75 | }
76 |
77 | // AddSection sets the value for a specific section
78 | func (h *SMTPAPIHeader) AddSection(section, value string) {
79 | if h.Section == nil {
80 | h.Section = make(map[string]string)
81 | }
82 | h.Section[section] = value
83 | }
84 |
85 | // SetSections sets the value for the Section header
86 | func (h *SMTPAPIHeader) SetSections(sections map[string]string) {
87 | h.Section = sections
88 | }
89 |
90 | // AddCategory adds a new category to the Category header
91 | func (h *SMTPAPIHeader) AddCategory(category string) {
92 | h.Category = append(h.Category, category)
93 | }
94 |
95 | // AddCategories adds multiple categories to the Category header
96 | func (h *SMTPAPIHeader) AddCategories(categories []string) {
97 | for i := 0; i < len(categories); i++ {
98 | h.AddCategory(categories[i])
99 | }
100 | }
101 |
102 | // SetCategories will set the value of the Categories field
103 | func (h *SMTPAPIHeader) SetCategories(categories []string) {
104 | h.Category = categories
105 | }
106 |
107 | // SetASMGroupID will set the value of the ASMGroupID field
108 | func (h *SMTPAPIHeader) SetASMGroupID(groupID int) {
109 | h.ASMGroupID = groupID
110 | }
111 |
112 | // AddASMGroupToDisplay adds a new ASM group ID to be displayed
113 | func (h *SMTPAPIHeader) AddASMGroupToDisplay(groupID int) {
114 | h.ASMGroups = append(h.ASMGroups, groupID)
115 | }
116 |
117 | // AddASMGroupsToDisplay adds multiple ASM group IDs to be displayed
118 | func (h *SMTPAPIHeader) AddASMGroupsToDisplay(groupIDs []int) {
119 | for i := 0; i < len(groupIDs); i++ {
120 | h.AddASMGroupToDisplay(groupIDs[i])
121 | }
122 | }
123 |
124 | // SetASMGroupsToDisplay will set the value of the ASMGroups field
125 | func (h *SMTPAPIHeader) SetASMGroupsToDisplay(groups []int) {
126 | h.ASMGroups = groups
127 | }
128 |
129 | // AddUniqueArg will set the value of a specific argument
130 | func (h *SMTPAPIHeader) AddUniqueArg(arg, value string) {
131 | if h.UniqueArgs == nil {
132 | h.UniqueArgs = make(map[string]string)
133 | }
134 | h.UniqueArgs[arg] = value
135 | }
136 |
137 | // SetUniqueArgs will set the value of the Unique_args header
138 | func (h *SMTPAPIHeader) SetUniqueArgs(args map[string]string) {
139 | h.UniqueArgs = args
140 | }
141 |
142 | // AddFilter will set the specific setting for a filter
143 | func (h *SMTPAPIHeader) AddFilter(filter, setting string, value interface{}) {
144 | if h.Filters == nil {
145 | h.Filters = make(map[string]Filter)
146 | }
147 | if _, ok := h.Filters[filter]; !ok {
148 | h.Filters[filter] = Filter{
149 | Settings: make(map[string]interface{}),
150 | }
151 | }
152 | h.Filters[filter].Settings[setting] = value
153 | }
154 |
155 | // SetFilter takes in a Filter struct with predetermined settings and sets it for such Filter key
156 | func (h *SMTPAPIHeader) SetFilter(filter string, value *Filter) {
157 | if h.Filters == nil {
158 | h.Filters = make(map[string]Filter)
159 | }
160 | h.Filters[filter] = *value
161 | }
162 |
163 | // SetSendAt takes in a timestamp which determines when the email will be sent
164 | func (h *SMTPAPIHeader) SetSendAt(sendAt int64) {
165 | h.SendAt = sendAt
166 | }
167 |
168 | // AddSendEachAt takes in a timestamp and pushes it into a list Must match length of To emails
169 | func (h *SMTPAPIHeader) AddSendEachAt(sendEachAt int64) {
170 | h.SendEachAt = append(h.SendEachAt, sendEachAt)
171 | }
172 |
173 | // SetSendEachAt takes an array of timestamps. Must match length of To emails
174 | func (h *SMTPAPIHeader) SetSendEachAt(sendEachAt []int64) {
175 | h.SendEachAt = sendEachAt
176 | }
177 |
178 | // SetIpPool takes a strings and sets the IpPool field
179 | func (h *SMTPAPIHeader) SetIpPool(ipPool string) {
180 | h.IpPool = ipPool
181 | }
182 |
183 | // Unicode escape
184 | func escapeUnicode(input string) string {
185 | //var buffer bytes.Buffer
186 | buffer := bytes.NewBufferString("")
187 | for _, r := range input {
188 | if r > 65535 {
189 | // surrogate pair
190 | var r1, r2 = utf16.EncodeRune(r)
191 | var s = fmt.Sprintf("\\u%x\\u%x", r1, r2)
192 | // error always nil https://golang.org/pkg/bytes/#Buffer.WriteString
193 | buffer.WriteString(s) // nolint: gas, gosec
194 | } else if r > 127 {
195 | var s = fmt.Sprintf("\\u%04x", r)
196 | // error always nil https://golang.org/pkg/bytes/#Buffer.WriteString
197 | buffer.WriteString(s) // nolint: gas, gosec
198 | } else {
199 | var s = fmt.Sprintf("%c", r)
200 | // error always nil https://golang.org/pkg/bytes/#Buffer.WriteString
201 | buffer.WriteString(s) // nolint: gas, gosec
202 | }
203 | }
204 | return buffer.String()
205 | }
206 |
207 | // JSONString returns the representation of the Header
208 | func (h *SMTPAPIHeader) JSONString() (string, error) {
209 | headers, e := json.Marshal(h)
210 | return escapeUnicode(string(headers)), e
211 | }
212 |
213 | // Load allows you to load a pre-formed x-smtpapi header
214 | func (h *SMTPAPIHeader) Load(b []byte) error {
215 | return json.Unmarshal(b, h)
216 | }
217 |
--------------------------------------------------------------------------------
/smtpapi_test.go:
--------------------------------------------------------------------------------
1 | package smtpapi
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io/ioutil"
7 | "os"
8 | "reflect"
9 | "regexp"
10 | "testing"
11 | "time"
12 | )
13 |
14 | func exampleJson() map[string]interface{} {
15 | data, _ := ioutil.ReadFile("smtpapi_test_strings.json")
16 | var f interface{}
17 | json.Unmarshal(data, &f)
18 | json := f.(map[string]interface{})
19 | return json
20 | }
21 |
22 | func TestNewSMTPIAPIHeader(t *testing.T) {
23 | t.Parallel()
24 | header := NewSMTPAPIHeader()
25 | if header == nil {
26 | t.Error("NewSMTPAPIHeader() should never return nil")
27 | }
28 | }
29 |
30 | func TestAddTo(t *testing.T) {
31 | t.Parallel()
32 | header := NewSMTPAPIHeader()
33 | header.AddTo("addTo@mailinator.com")
34 | result, _ := header.JSONString()
35 | if result != exampleJson()["add_to"] {
36 | t.Errorf("Result did not match")
37 | }
38 | }
39 |
40 | func TestAddTos(t *testing.T) {
41 | t.Parallel()
42 | header := NewSMTPAPIHeader()
43 | tos := []string{"addTo@mailinator.com"}
44 | header.AddTos(tos)
45 | result, _ := header.JSONString()
46 | if result != exampleJson()["add_to"] {
47 | t.Errorf("Result did not match")
48 | }
49 | }
50 |
51 | func TestSetTos(t *testing.T) {
52 | t.Parallel()
53 | header := NewSMTPAPIHeader()
54 | header.SetTos([]string{"setTos@mailinator.com"})
55 | result, _ := header.JSONString()
56 | if result != exampleJson()["set_tos"] {
57 | t.Errorf("Result did not match")
58 | }
59 | }
60 |
61 | func TestAddSubstitution(t *testing.T) {
62 | t.Parallel()
63 | header := NewSMTPAPIHeader()
64 | header.AddSubstitution("sub", "val")
65 | result, _ := header.JSONString()
66 | if result != exampleJson()["add_substitution"] {
67 | t.Errorf("Result did not match")
68 | }
69 | }
70 |
71 | func TestAddSubstitutions(t *testing.T) {
72 | t.Parallel()
73 | header := NewSMTPAPIHeader()
74 | header.AddSubstitutions("sub", []string{"val"})
75 | result, _ := header.JSONString()
76 | if result != exampleJson()["add_substitution"] {
77 | t.Errorf("Result did not match")
78 | }
79 | }
80 |
81 | func TestSetSubstitutions(t *testing.T) {
82 | t.Parallel()
83 | header := NewSMTPAPIHeader()
84 | sub := make(map[string][]string)
85 | sub["sub"] = []string{"val"}
86 | header.SetSubstitutions(sub)
87 | result, _ := header.JSONString()
88 | if result != exampleJson()["set_substitutions"] {
89 | t.Errorf("Result did not match")
90 | }
91 | }
92 |
93 | func TestAddSection(t *testing.T) {
94 | t.Parallel()
95 | header := NewSMTPAPIHeader()
96 | header.AddSection("set_section_key", "set_section_value")
97 | header.AddSection("set_section_key_2", "set_section_value_2")
98 | result, _ := header.JSONString()
99 | if result != exampleJson()["add_section"] {
100 | t.Errorf("Result did not match")
101 | }
102 | }
103 |
104 | func TestSetSections(t *testing.T) {
105 | t.Parallel()
106 | header := NewSMTPAPIHeader()
107 | sections := make(map[string]string)
108 | sections["set_section_key"] = "set_section_value"
109 | header.SetSections(sections)
110 | result, _ := header.JSONString()
111 | if result != exampleJson()["set_sections"] {
112 | t.Errorf("Result did not match")
113 | }
114 | }
115 |
116 | func TestAddCategory(t *testing.T) {
117 | t.Parallel()
118 | header := NewSMTPAPIHeader()
119 | header.AddCategory("addCategory")
120 | header.AddCategory("addCategory2")
121 | result, _ := header.JSONString()
122 | if result != exampleJson()["add_category"] {
123 | t.Errorf("Result did not match")
124 | }
125 | }
126 |
127 | func TestAddCategoryUnicode(t *testing.T) {
128 | t.Parallel()
129 | header := NewSMTPAPIHeader()
130 | header.AddCategory("カテゴリUnicode")
131 | header.AddCategory("カテゴリ2Unicode")
132 | header.AddCategory("鼖")
133 | result, _ := header.JSONString()
134 | if result != exampleJson()["add_category_unicode"] {
135 | t.Errorf("Result did not match")
136 | }
137 | }
138 |
139 | func TestAddCategories(t *testing.T) {
140 | t.Parallel()
141 | header := NewSMTPAPIHeader()
142 | categories := []string{"addCategory", "addCategory2"}
143 | header.AddCategories(categories)
144 | result, _ := header.JSONString()
145 | if result != exampleJson()["add_category"] {
146 | t.Errorf("Result did not match")
147 | }
148 | }
149 |
150 | func TestSetCategories(t *testing.T) {
151 | t.Parallel()
152 | header := NewSMTPAPIHeader()
153 | header.SetCategories([]string{"setCategories"})
154 | result, _ := header.JSONString()
155 | if result != exampleJson()["set_categories"] {
156 | t.Errorf("Result did not match")
157 | }
158 | }
159 |
160 | func TestAddUniqueArg(t *testing.T) {
161 | t.Parallel()
162 | header := NewSMTPAPIHeader()
163 | header.AddUniqueArg("add_unique_argument_key", "add_unique_argument_value")
164 | header.AddUniqueArg("add_unique_argument_key_2", "add_unique_argument_value_2")
165 | result, _ := header.JSONString()
166 | if result != exampleJson()["add_unique_arg"] {
167 | t.Errorf("Result did not match")
168 | }
169 | }
170 |
171 | func TestSetUniqueArgs(t *testing.T) {
172 | t.Parallel()
173 | header := NewSMTPAPIHeader()
174 | args := make(map[string]string)
175 | args["set_unique_argument_key"] = "set_unique_argument_value"
176 | header.SetUniqueArgs(args)
177 | result, _ := header.JSONString()
178 | if result != exampleJson()["set_unique_args"] {
179 | t.Errorf("Result did not match")
180 | }
181 | }
182 |
183 | func TestAddFilter(t *testing.T) {
184 | t.Parallel()
185 | header := NewSMTPAPIHeader()
186 | header.AddFilter("footer", "text/html", "boo")
187 | if len(header.Filters) != 1 {
188 | t.Error("AddFilter failed")
189 | }
190 | }
191 |
192 | func TestSetFilter(t *testing.T) {
193 | t.Parallel()
194 | header := NewSMTPAPIHeader()
195 | filter := &Filter{
196 | Settings: make(map[string]interface{}),
197 | }
198 | filter.Settings["enable"] = 1
199 | filter.Settings["text/plain"] = "You can haz footers!"
200 | header.SetFilter("footer", filter)
201 | result, _ := header.JSONString()
202 | if result != exampleJson()["set_filters"] {
203 | t.Errorf("Result did not match")
204 | }
205 | }
206 |
207 | func TestSetSendAt(t *testing.T) {
208 | t.Parallel()
209 | header := NewSMTPAPIHeader()
210 | header.SetSendAt(1428611024)
211 | result, _ := header.JSONString()
212 | if result != exampleJson()["set_send_at"] {
213 | t.Errorf("Result did not match")
214 | }
215 | }
216 |
217 | func TestAddSendEachAt(t *testing.T) {
218 | t.Parallel()
219 | header := NewSMTPAPIHeader()
220 | header.AddSendEachAt(1428611024)
221 | header.AddSendEachAt(1428611025)
222 | result, _ := header.JSONString()
223 | if result != exampleJson()["add_send_each_at"] {
224 | t.Errorf("Result did not match")
225 | }
226 | }
227 |
228 | func TestSetSendEachAt(t *testing.T) {
229 | t.Parallel()
230 | header := NewSMTPAPIHeader()
231 | sendEachAt := []int64{1428611024, 1428611025}
232 | header.SetSendEachAt(sendEachAt)
233 | result, _ := header.JSONString()
234 | if result != exampleJson()["set_send_each_at"] {
235 | t.Errorf("Result did not match")
236 | }
237 | }
238 |
239 | func TestSetASMGroupID(t *testing.T) {
240 | t.Parallel()
241 | header := NewSMTPAPIHeader()
242 | header.SetASMGroupID(1)
243 | result, _ := header.JSONString()
244 | if result != exampleJson()["set_asm_group_id"] {
245 | t.Errorf("Result did not match")
246 | }
247 | }
248 |
249 | func TestSetIpPool(t *testing.T) {
250 | t.Parallel()
251 | header := NewSMTPAPIHeader()
252 | header.SetIpPool("testPool")
253 | result, _ := header.JSONString()
254 | if result != exampleJson()["set_ip_pool"] {
255 | t.Errorf("Result did not match")
256 | }
257 | }
258 |
259 | func TestSAddASMGroupToDisplay(t *testing.T) {
260 | t.Parallel()
261 | header := NewSMTPAPIHeader()
262 | header.AddASMGroupToDisplay(671332)
263 | result, _ := header.JSONString()
264 | if result != exampleJson()["add_asm_group"] {
265 | t.Errorf("Result did not match")
266 | }
267 | }
268 |
269 | func TestSAddASMGroupsToDisplay(t *testing.T) {
270 | t.Parallel()
271 | header := NewSMTPAPIHeader()
272 | header.AddASMGroupsToDisplay([]int{45, 23})
273 | result, _ := header.JSONString()
274 | if result != exampleJson()["add_asm_groups"] {
275 | t.Errorf("Result did not match")
276 | }
277 | }
278 |
279 | func TestJSONString(t *testing.T) {
280 | t.Parallel()
281 | header := NewSMTPAPIHeader()
282 | result, _ := header.JSONString()
283 | if result != exampleJson()["json_string"] {
284 | t.Errorf("Result did not match")
285 | }
286 | }
287 |
288 | func TestJSONStringWithAdds(t *testing.T) {
289 | t.Parallel()
290 | validHeader, _ := json.Marshal([]byte(`{"to":["test@email.com"],"sub":{"subKey":["subValue"]},"section":{"testSection":"sectionValue"},"category":["testCategory"],"unique_args":{"testUnique":"uniqueValue"},"filters":{"testFilter":{"settings":{"filter":"filterValue"}}}}`))
291 | header := NewSMTPAPIHeader()
292 | header.AddTo("test@email.com")
293 | header.AddSubstitution("subKey", "subValue")
294 | header.AddSection("testSection", "sectionValue")
295 | header.AddCategory("testCategory")
296 | header.AddUniqueArg("testUnique", "uniqueValue")
297 | header.AddFilter("testFilter", "filter", "filterValue")
298 | if h, e := header.JSONString(); e != nil {
299 | t.Errorf("Error! %s", e)
300 | } else {
301 | testHeader, _ := json.Marshal([]byte(h))
302 | if reflect.DeepEqual(testHeader, validHeader) {
303 | t.Logf("Success")
304 | } else {
305 | t.Errorf("Invalid header")
306 | }
307 | }
308 | }
309 |
310 | func TestJSONStringWithSets(t *testing.T) {
311 | t.Parallel()
312 | validHeader, _ := json.Marshal([]byte(`{"to":["test@email.com"],"sub":{"subKey":["subValue"]},"section":{"testSection":"sectionValue"},"category":["testCategory"],"unique_args":{"testUnique":"uniqueValue"},"filters":{"testFilter":{"settings":{"filter":"filterValue"}}},"asm_group_id":1,"ip_pool":"testPool"}`))
313 | header := NewSMTPAPIHeader()
314 | header.SetTos([]string{"test@email.com"})
315 | sub := make(map[string][]string)
316 | sub["subKey"] = []string{"subValue"}
317 | header.SetSubstitutions(sub)
318 | sections := make(map[string]string)
319 | sections["testSection"] = "sectionValue"
320 | header.SetSections(sections)
321 | header.SetCategories([]string{"testCategory"})
322 | unique := make(map[string]string)
323 | unique["testUnique"] = "uniqueValue"
324 | header.SetUniqueArgs(unique)
325 | header.AddFilter("testFilter", "filter", "filterValue")
326 | header.SetASMGroupID(1)
327 | header.SetIpPool("testPool")
328 | if h, e := header.JSONString(); e != nil {
329 | t.Errorf("Error! %s", e)
330 | } else {
331 | testHeader, _ := json.Marshal([]byte(h))
332 | if reflect.DeepEqual(testHeader, validHeader) {
333 | t.Logf("Success")
334 | } else {
335 | t.Errorf("Invalid header")
336 | }
337 | }
338 | }
339 |
340 | func TestMarshalUnmarshall(t *testing.T) {
341 | t.Parallel()
342 | header := NewSMTPAPIHeader()
343 | header.SetTos([]string{"test@email.com"})
344 | sub := make(map[string][]string)
345 | sub["subKey"] = []string{"subValue"}
346 | header.SetSubstitutions(sub)
347 | sections := make(map[string]string)
348 | sections["testSection"] = "sectionValue"
349 | header.SetSections(sections)
350 | header.SetCategories([]string{"testCategory"})
351 | unique := make(map[string]string)
352 | unique["testUnique"] = "uniqueValue"
353 | header.SetUniqueArgs(unique)
354 | header.AddFilter("testFilter", "filter", "filterValue")
355 | header.SetASMGroupID(1)
356 | header.SetIpPool("testPool")
357 | header.SetASMGroupsToDisplay([]int{32, 12})
358 |
359 | newHeader := NewSMTPAPIHeader()
360 | b, err := header.JSONString()
361 | if err != nil {
362 | t.Errorf("Error in JSONString %v", err)
363 | }
364 | err = newHeader.Load([]byte(b))
365 | if err != nil {
366 | t.Errorf("Could not load newHeader %v", err)
367 | }
368 | if !reflect.DeepEqual(header, newHeader) {
369 | t.Errorf("Expected %v, but got %v", header, newHeader)
370 | }
371 | }
372 |
373 | func TestRepoFiles(t *testing.T) {
374 | files := []string{".env_sample", ".gitignore", ".github/workflows/test-and-deploy.yml", "CHANGELOG.md", "CODE_OF_CONDUCT.md",
375 | "CONTRIBUTING.md", "LICENSE", "PULL_REQUEST_TEMPLATE.md",
376 | "README.md", "TROUBLESHOOTING.md", "USAGE.md"}
377 |
378 | for _, file := range files {
379 | if _, err := os.Stat(file); os.IsNotExist(err) {
380 | t.Errorf("Repo file does not exist: %v", file)
381 | }
382 | }
383 | }
384 |
385 | func TestLicenseYear(t *testing.T) {
386 | t.Parallel()
387 | dat, err := ioutil.ReadFile("LICENSE")
388 |
389 | currentYear := time.Now().Year()
390 | r := fmt.Sprintf("%d", currentYear)
391 | match, _ := regexp.MatchString(r, string(dat))
392 |
393 | if err != nil {
394 | t.Error("License File Not Found")
395 | }
396 | if !match {
397 | t.Error("Incorrect Year in License Copyright")
398 | }
399 | }
400 |
--------------------------------------------------------------------------------
/smtpapi_test_strings.json:
--------------------------------------------------------------------------------
1 | {
2 | "json_string": "{}",
3 | "add_to": "{\"to\":[\"addTo@mailinator.com\"]}",
4 | "set_tos": "{\"to\":[\"setTos@mailinator.com\"]}",
5 | "add_substitution": "{\"sub\":{\"sub\":[\"val\"]}}",
6 | "set_substitutions": "{\"sub\":{\"sub\":[\"val\"]}}",
7 | "add_unique_arg": "{\"unique_args\":{\"add_unique_argument_key\":\"add_unique_argument_value\",\"add_unique_argument_key_2\":\"add_unique_argument_value_2\"}}",
8 | "set_unique_args": "{\"unique_args\":{\"set_unique_argument_key\":\"set_unique_argument_value\"}}",
9 | "add_category": "{\"category\":[\"addCategory\",\"addCategory2\"]}",
10 | "add_category_unicode": "{\"category\":[\"\\u30ab\\u30c6\\u30b4\\u30eaUnicode\",\"\\u30ab\\u30c6\\u30b4\\u30ea2Unicode\",\"\\ud87e\\ude1b\"]}",
11 | "set_categories": "{\"category\":[\"setCategories\"]}",
12 | "add_section": "{\"section\":{\"set_section_key\":\"set_section_value\",\"set_section_key_2\":\"set_section_value_2\"}}",
13 | "set_sections": "{\"section\":{\"set_section_key\":\"set_section_value\"}}",
14 | "add_filter": "{\"filters\":{\"footer\":{\"settings\":{\"text/html\":\"boo\"}}}}",
15 | "set_filters": "{\"filters\":{\"footer\":{\"settings\":{\"enable\":1,\"text/plain\":\"You can haz footers!\"}}}}",
16 | "set_send_at": "{\"send_at\":1428611024}",
17 | "add_send_each_at": "{\"send_each_at\":[1428611024,1428611025]}",
18 | "set_send_each_at": "{\"send_each_at\":[1428611024,1428611025]}",
19 | "set_asm_group_id": "{\"asm_group_id\":1}",
20 | "add_asm_group": "{\"asm_groups_to_display\":[671332]}",
21 | "add_asm_groups": "{\"asm_groups_to_display\":[45,23]}",
22 | "set_ip_pool": "{\"ip_pool\":\"testPool\"}"
23 | }
24 |
--------------------------------------------------------------------------------
/static/img/github-fork.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendgrid/smtpapi-go/2be719257fe040c6b129028f2b9aa6db7612f679/static/img/github-fork.png
--------------------------------------------------------------------------------
/static/img/github-sign-up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendgrid/smtpapi-go/2be719257fe040c6b129028f2b9aa6db7612f679/static/img/github-sign-up.png
--------------------------------------------------------------------------------
/twilio_sendgrid_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendgrid/smtpapi-go/2be719257fe040c6b129028f2b9aa6db7612f679/twilio_sendgrid_logo.png
--------------------------------------------------------------------------------
/use_cases/README.md:
--------------------------------------------------------------------------------
1 | This directory provides examples for specific use cases. Please [open an issue](https://github.com/sendgrid/smtpapi-go/issues) or make a pull request for any use cases you would like to see here. Thank you!
2 |
3 | # Table of Contents
4 |
--------------------------------------------------------------------------------