diff --git a/.codeclimate.yml b/.codeclimate.yml deleted file mode 100644 index ef7e1cb56..000000000 --- a/.codeclimate.yml +++ /dev/null @@ -1,3 +0,0 @@ -plugins: - pep8: - enabled: true diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE deleted file mode 100644 index 1ac1c9968..000000000 --- a/.github/ISSUE_TEMPLATE +++ /dev/null @@ -1,17 +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, code examples. - - -#### Steps to Reproduce - -1. This is the first step -2. This is the second step -3. Further steps, etc. - -Any other information you want to share that is relevant to the issue being reported. Especially, why do you consider this to be a bug? What do you expect to happen instead? - -#### Technical details: - -* sendgrid-python Version: master (latest commit: [commit number]) -* Python Version: X.X \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE deleted file mode 100644 index 7ad590b42..000000000 --- a/.github/PULL_REQUEST_TEMPLATE +++ /dev/null @@ -1,24 +0,0 @@ - -# Fixes # - -### Checklist -- [ ] I have made a material change to the repo (functionality, testing, spelling, grammar) -- [ ] I have read the [Contribution Guide] and my PR follows them. -- [ ] I updated my branch with the master branch. -- [ ] I have added tests that prove my fix is effective or that my feature works -- [ ] I have added necessary documentation about the functionality in the appropriate .md file -- [ ] I have added in line documentation to the code I modified - -### Short description of what this PR does: -- -- - -If you have questions, please send an email to [Sendgrid](mailto:dx@sendgrid.com), or file a Github Issue in this repository. diff --git a/.github/workflows/lint-python.yml b/.github/workflows/lint-python.yml new file mode 100644 index 000000000..5dcf0328c --- /dev/null +++ b/.github/workflows/lint-python.yml @@ -0,0 +1,16 @@ +# https://beta.ruff.rs +name: ruff +on: + push: + branches: + - main + pull_request: + branches: + - main +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: pip install --user ruff + - run: ruff check --ignore="E722,F40,F841" --line-length=320 --target-version=py37 . diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml new file mode 100644 index 000000000..31520079c --- /dev/null +++ b/.github/workflows/pr-lint.yml @@ -0,0 +1,21 @@ +name: Lint PR +on: + pull_request_target: + types: [ opened, edited, synchronize, reopened ] + +jobs: + validate: + name: Validate title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5 + with: + types: | + chore + docs + fix + feat + misc + test + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml new file mode 100644 index 000000000..98bbeefbd --- /dev/null +++ b/.github/workflows/test-and-deploy.yml @@ -0,0 +1,97 @@ +name: Test and Deploy +on: + push: + branches: [ '*' ] + tags: [ '*' ] + pull_request: + branches: [ main ] + schedule: + # Run automatically at 8AM PST Monday-Friday + - cron: '0 15 * * 1-5' + workflow_dispatch: + +jobs: + test: + name: Test + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + matrix: + python-version: [ '3.9', '3.10', '3.11', '3.12' , '3.13'] + env: + DOCKER_LOGIN: ${{ secrets.DOCKER_USERNAME && secrets.DOCKER_AUTH_TOKEN }} + steps: + - name: Checkout sendgrid-python + uses: actions/checkout@v3 + + - name: Login to Docker Hub + if: env.DOCKER_LOGIN + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_AUTH_TOKEN }} + + - name: Install Docker Compose + run: | + sudo apt-get update + sudo apt-get install -y docker-compose + + - name: Build & Test + run: make test + + deploy: + name: Deploy + if: success() && github.ref_type == 'tag' + needs: [ test ] + runs-on: ubuntu-latest + steps: + - name: Checkout sendgrid-python + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + pip install wheel + python setup.py sdist bdist_wheel + + - name: Create GitHub Release + uses: sendgrid/dx-automator/actions/release@main + with: + footer: '**[pypi](https://pypi.org/project/sendgrid/${version})**' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_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') + needs: [ test, deploy ] + runs-on: ubuntu-latest + steps: + - uses: rtCamp/action-slack-notify@v2 + env: + SLACK_COLOR: failure + SLACK_ICON_EMOJI: ':github:' + 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) }} + SLACK_TITLE: Action Failure - ${{ github.repository }} + SLACK_USERNAME: GitHub Actions + SLACK_MSG_AUTHOR: twilio-dx + SLACK_FOOTER: Posted automatically using GitHub Actions + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + MSG_MINIMAL: true diff --git a/.gitignore b/.gitignore index de9602419..504640243 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,11 @@ README.txt .coverage coverage.xml htmlcov -temp*.py -sendgrid.env - +temp.py +.vscode +live_test.py +__pycache__ +example.pdf +TODO.txt +twilio.env +prism* diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4aceaa352..000000000 --- a/.travis.yml +++ /dev/null @@ -1,52 +0,0 @@ -language: python -sudo: false -cache: pip -python: -- '2.6' -- '2.7' -- '3.4' -- '3.5' -- '3.6' -install: -- if [[ $TRAVIS_PYTHON_VERSION == 2.6* ]]; then pip install unittest2; fi -- python setup.py install -- pip install pyyaml -- pip install flask -- pip install six -- pip install pypandoc -- pip install coverage -- pip install codecov -# - sudo apt-get install -y pandoc -addons: - apt_packages: - - pandoc -before_script: -- . ./test/prism.sh -- prism version -script: -- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then coverage run -m unittest2 discover; else coverage run -m unittest discover; fi -after_script: -- codecov -before_deploy: -- python ./register.py -deploy: - provider: pypi - user: thinkingserious - password: - secure: DoM21KiMKkt/7AS6zOqTs7j3fgInrpswRTPG3cqBNRSzyfkXeXmKecCPruooxvYKLM7fPNDOuIH2phgCjdx/XBtJwghNh34n+TzhNFEiI/6pV0iS4a9gW0+QU+GMYvQmfNlA9DKQ5N20FMy4XeK8QQFarJXQwW1/a5wWftbUYvQ= - skip_cleanup: true - distributions: sdist bdist_wheel - on: - tags: true - python: '3.6' -notifications: - hipchat: - rooms: - secure: Lo3L/YNWpn9ulGX4D2HlWrBOyxMPlLkFcwxbYViG69Ta6BV+c6YE+Pct43tExlL6sZ+nj5p8X4KRTeOM4sqASrebWA25nyUrNTm+vZYFbi5XfmGvvi8TEsgg0MYRQRWWn/R2z0kZW/fqOY6sqJuoIafMBmC3tayTJRiH1Ct2Cw0= - template: - - '%{repository} - Build %{build_number} on branch %{branch} by %{author}: %{message} - View on - GitHub' - format: html - notify: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ec462196..67d327649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,503 @@ # Change Log All notable changes to this project will be documented in this file. +[2025-09-19] Version 6.12.5 +--------------------------- +**Library - Fix** +- [PR #1114](https://github.com/sendgrid/sendgrid-python/pull/1114): #1108 - Replace ecdsa with cryptography. Thanks to [@dacevedo12](https://github.com/dacevedo12)! + +**Library - Chore** +- [PR #1117](https://github.com/sendgrid/sendgrid-python/pull/1117): use make-test instead of make test-docker. Thanks to [@tiwarishubham635](https://github.com/tiwarishubham635)! + + +[2025-06-12] Version 6.12.4 +--------------------------- +**Library - Chore** +- [PR #1109](https://github.com/sendgrid/sendgrid-python/pull/1109): bug-fix. Thanks to [@manisha1997](https://github.com/manisha1997)! + + +[2025-05-29] Version 6.12.3 +--------------------------- +**Library - Chore** +- [PR #1107](https://github.com/sendgrid/sendgrid-python/pull/1107): export EventWebhookHeader. Thanks to [@tiwarishubham635](https://github.com/tiwarishubham635)! +- [PR #1104](https://github.com/sendgrid/sendgrid-python/pull/1104): fix werkzeug lower versions. Thanks to [@eladkal](https://github.com/eladkal)! +- [PR #1103](https://github.com/sendgrid/sendgrid-python/pull/1103): Relax werkzeug version. Thanks to [@eladkal](https://github.com/eladkal)! + + +[2025-05-13] Version 6.12.2 +--------------------------- +**Library - Chore** +- [PR #1102](https://github.com/sendgrid/sendgrid-python/pull/1102): update ecdsa in setup.py. Thanks to [@tiwarishubham635](https://github.com/tiwarishubham635)! + + +[2025-05-13] Version 6.12.1 +--------------------------- +**Library - Fix** +- [PR #1085](https://github.com/sendgrid/sendgrid-python/pull/1085): Vulnerability fix for starkbank-ecdsa 2.2.0 dependency. Thanks to [@ranjanprasad1996](https://github.com/ranjanprasad1996)! + + +[2025-05-05] Version 6.12.0 +--------------------------- +**Library - Chore** +- [PR #1098](https://github.com/sendgrid/sendgrid-python/pull/1098): Add werkzeug package to setup file. Thanks to [@gopidesupavan](https://github.com/gopidesupavan)! +- [PR #1099](https://github.com/sendgrid/sendgrid-python/pull/1099): install docker-compose. Thanks to [@tiwarishubham635](https://github.com/tiwarishubham635)! + +**Library - Feature** +- [PR #1087](https://github.com/sendgrid/sendgrid-python/pull/1087): add support for python3.12 and 3.13. Thanks to [@meysam81](https://github.com/meysam81)! + + +[2023-12-01] Version 6.11.0 +--------------------------- +**Library - Feature** +- [PR #1073](https://github.com/sendgrid/sendgrid-python/pull/1073): geolocation setter in sendgrid-python for GDPR compliance. Thanks to [@manisha1997](https://github.com/manisha1997)! + +**Library - Test** +- [PR #1066](https://github.com/sendgrid/sendgrid-python/pull/1066): removing codedev test dependency. Thanks to [@sethgrid](https://github.com/sethgrid)! + + +[2023-03-22] Version 6.10.0 +--------------------------- +**Library - Feature** +- [PR #1062](https://github.com/sendgrid/sendgrid-python/pull/1062): Add reply_to_list functionality. Thanks to [@thepuzzlemaster](https://github.com/thepuzzlemaster)! +- [PR #1059](https://github.com/sendgrid/sendgrid-python/pull/1059): Add Python 3.11 to the testing. Thanks to [@cclauss](https://github.com/cclauss)! + +**Library - Miscellaneous** +- [PR #1065](https://github.com/sendgrid/sendgrid-python/pull/1065): Create GitHub Action to lint Python code. Thanks to [@cclauss](https://github.com/cclauss)! +- [PR #1064](https://github.com/sendgrid/sendgrid-python/pull/1064): Upgrade GitHub Action test-and-deploy.yml. Thanks to [@cclauss](https://github.com/cclauss)! +- [PR #1063](https://github.com/sendgrid/sendgrid-python/pull/1063): Upgrade GitHub Action pr-lint.yml. Thanks to [@cclauss](https://github.com/cclauss)! + +**Library - Test** +- [PR #1058](https://github.com/sendgrid/sendgrid-python/pull/1058): Adding misc as PR type. Thanks to [@rakatyal](https://github.com/rakatyal)! + +**Library - Docs** +- [PR #1055](https://github.com/sendgrid/sendgrid-python/pull/1055): Modify README.md in alignment with SendGrid Support. Thanks to [@garethpaul](https://github.com/garethpaul)! +- [PR #1052](https://github.com/sendgrid/sendgrid-python/pull/1052): Fix link that has drifted. Thanks to [@jonathanberger](https://github.com/jonathanberger)! + + +[2022-03-09] Version 6.9.7 +-------------------------- +**Library - Chore** +- [PR #1048](https://github.com/sendgrid/sendgrid-python/pull/1048): Update mail_example.py. Thanks to [@vmesel](https://github.com/vmesel)! +- [PR #1049](https://github.com/sendgrid/sendgrid-python/pull/1049): push Datadog Release Metric upon deploy success. Thanks to [@eshanholtz](https://github.com/eshanholtz)! +- [PR #1050](https://github.com/sendgrid/sendgrid-python/pull/1050): fix flask dependency test issues. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + + +[2022-02-09] Version 6.9.6 +-------------------------- +**Library - Chore** +- [PR #1044](https://github.com/sendgrid/sendgrid-python/pull/1044): drop pytest which was not being used. Thanks to [@childish-sambino](https://github.com/childish-sambino)! +- [PR #1043](https://github.com/sendgrid/sendgrid-python/pull/1043): upgrade supported language versions. Thanks to [@childish-sambino](https://github.com/childish-sambino)! +- [PR #1041](https://github.com/sendgrid/sendgrid-python/pull/1041): add gh release to workflow. Thanks to [@shwetha-manvinkurke](https://github.com/shwetha-manvinkurke)! +- [PR #1039](https://github.com/sendgrid/sendgrid-python/pull/1039): merge test and deploy workflows. Thanks to [@Hunga1](https://github.com/Hunga1)! + + +[2022-01-26] Version 6.9.5 +-------------------------- +**Library - Docs** +- [PR #1036](https://github.com/sendgrid/sendgrid-python/pull/1036): Removing unused json import. Thanks to [@vital101](https://github.com/vital101)! + + +[2022-01-12] Version 6.9.4 +-------------------------- +**Library - Chore** +- [PR #1031](https://github.com/sendgrid/sendgrid-python/pull/1031): Remove unused import from distutils. Thanks to [@tirkarthi](https://github.com/tirkarthi)! + +**Library - Docs** +- [PR #1032](https://github.com/sendgrid/sendgrid-python/pull/1032): remove leading spaces on error handling example. Thanks to [@thinkingserious](https://github.com/thinkingserious)! + + +[2021-12-15] Version 6.9.3 +-------------------------- +**Library - Test** +- [PR #1029](https://github.com/sendgrid/sendgrid-python/pull/1029): split up unit and integ tests. Thanks to [@childish-sambino](https://github.com/childish-sambino)! + + +[2021-12-01] Version 6.9.2 +-------------------------- +**Library - Chore** +- [PR #1027](https://github.com/sendgrid/sendgrid-python/pull/1027): migrate to GitHub Actions. Thanks to [@JenniferMah](https://github.com/JenniferMah)! + + +[2021-11-17] Version 6.9.1 +-------------------------- +**Library - Chore** +- [PR #1022](https://github.com/sendgrid/sendgrid-python/pull/1022): fix vulnerability in starbank-ecdsa dependency. Thanks to [@hellno](https://github.com/hellno)! + + +[2021-11-03] Version 6.9.0 +-------------------------- +**Library - Feature** +- [PR #1020](https://github.com/sendgrid/sendgrid-python/pull/1020): allow personalization of the From name and email for each recipient. Thanks to [@beebzz](https://github.com/beebzz)! + + +[2021-10-18] Version 6.8.3 +-------------------------- +**Library - Chore** +- [PR #1016](https://github.com/sendgrid/sendgrid-python/pull/1016): pin starkbank-ecdsa version. Thanks to [@eshanholtz](https://github.com/eshanholtz)! +- [PR #1015](https://github.com/sendgrid/sendgrid-python/pull/1015): pin starkbank-ecdsa version. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + +**Library - Docs** +- [PR #1013](https://github.com/sendgrid/sendgrid-python/pull/1013): improve signed event webhook validation docs. Thanks to [@shwetha-manvinkurke](https://github.com/shwetha-manvinkurke)! + + +[2021-09-22] Version 6.8.2 +-------------------------- +**Library - Chore** +- [PR #1007](https://github.com/sendgrid/sendgrid-python/pull/1007): test against v3.9. Thanks to [@shwetha-manvinkurke](https://github.com/shwetha-manvinkurke)! + + +[2021-08-25] Version 6.8.1 +-------------------------- +**Library - Chore** +- [PR #1003](https://github.com/sendgrid/sendgrid-python/pull/1003): get rid of reply_to in mail helper. Thanks to [@shwetha-manvinkurke](https://github.com/shwetha-manvinkurke)! + + +[2021-08-11] Version 6.8.0 +-------------------------- +**Library - Feature** +- [PR #999](https://github.com/sendgrid/sendgrid-python/pull/999): add reply_to to helpers.Mail. Thanks to [@vindarel](https://github.com/vindarel)! + + +[2021-06-16] Version 6.7.1 +-------------------------- +**Library - Chore** +- [PR #994](https://github.com/sendgrid/sendgrid-python/pull/994): remove logic adding quotes to names containing , and ;. Thanks to [@JenniferMah](https://github.com/JenniferMah)! + + +[2021-04-21] Version 6.7.0 +-------------------------- +**Library - Docs** +- [PR #986](https://github.com/sendgrid/sendgrid-python/pull/986): Update to_emails type. Thanks to [@PyGeek03](https://github.com/PyGeek03)! + +**Library - Feature** +- [PR #983](https://github.com/sendgrid/sendgrid-python/pull/983): add v3 bypass filters. Thanks to [@anarayanan604](https://github.com/anarayanan604)! + + +[2021-02-10] Version 6.6.0 +-------------------------- +**Library - Docs** +- [PR #964](https://github.com/sendgrid/sendgrid-python/pull/964): Use correct pip installation command. Thanks to [@Akasurde](https://github.com/Akasurde)! + +**Library - Fix** +- [PR #971](https://github.com/sendgrid/sendgrid-python/pull/971): replace names in BatchId docstrings. Thanks to [@bennylope](https://github.com/bennylope)! + +**Library - Feature** +- [PR #924](https://github.com/sendgrid/sendgrid-python/pull/924): remove duplicate emails ignoring case in Personalization. Thanks to [@DougCal](https://github.com/DougCal)! + + +[2021-01-13] Version 6.5.0 +-------------------------- +**Library - Feature** +- [PR #945](https://github.com/sendgrid/sendgrid-python/pull/945): Support for AMP HTML Email. Thanks to [@modernwarfareuplink](https://github.com/modernwarfareuplink)! + +**Library - Docs** +- [PR #962](https://github.com/sendgrid/sendgrid-python/pull/962): Sending HTML email example is broken. Thanks to [@mikeckennedy](https://github.com/mikeckennedy)! + + +[2020-12-02] Version 6.4.8 +-------------------------- +**Library - Docs** +- [PR #955](https://github.com/sendgrid/sendgrid-python/pull/955): fixed typo in sendgrid/helpers/mail/file_content.py. Thanks to [@razvandimescu](https://github.com/razvandimescu)! + + +[2020-09-16] Version 6.4.7 +-------------------------- +**Library - Docs** +- [PR #936](https://github.com/sendgrid/sendgrid-python/pull/936): correct attachment example. Thanks to [@Arbitrage0](https://github.com/Arbitrage0)! + + +[2020-08-19] Version 6.4.6 +-------------------------- +**Library - Chore** +- [PR #929](https://github.com/sendgrid/sendgrid-python/pull/929): update GitHub branch references to use HEAD. Thanks to [@thinkingserious](https://github.com/thinkingserious)! + + +[2020-08-05] Version 6.4.5 +-------------------------- +**Library - Docs** +- [PR #926](https://github.com/sendgrid/sendgrid-python/pull/926): remove last references of "whitelabel". Thanks to [@childish-sambino](https://github.com/childish-sambino)! + + +[2020-07-22] Version 6.4.4 +-------------------------- +**Library - Chore** +- [PR #925](https://github.com/sendgrid/sendgrid-python/pull/925): migrate to new default sendgrid-oai branch. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + + +[2020-07-10] Version 6.4.3 +-------------------------- +**Library - Fix** +- [PR #921](https://github.com/sendgrid/sendgrid-python/pull/921): allow general email type for to_emails. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + + +[2020-07-08] Version 6.4.2 +-------------------------- +**Library - Fix** +- [PR #920](https://github.com/sendgrid/sendgrid-python/pull/920): type validation on to_emails parameter on mail object. Thanks to [@DougCal](https://github.com/DougCal)! + +**Library - Docs** +- [PR #915](https://github.com/sendgrid/sendgrid-python/pull/915): document change in top-level dependencies. Thanks to [@honzajavorek](https://github.com/honzajavorek)! + + +[2020-06-25] Version 6.4.1 +-------------------------- +**Library - Fix** +- [PR #914](https://github.com/sendgrid/sendgrid-python/pull/914): add dependency to install requires. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + + +[2020-06-24] Version 6.4.0 +-------------------------- +**Library - Docs** +- [PR #912](https://github.com/sendgrid/sendgrid-python/pull/912): added docstrings to Stats classes. Thanks to [@DougCal](https://github.com/DougCal)! + +**Library - Feature** +- [PR #908](https://github.com/sendgrid/sendgrid-python/pull/908): add support for dynamic template data to Email class. Thanks to [@childish-sambino](https://github.com/childish-sambino)! +- [PR #901](https://github.com/sendgrid/sendgrid-python/pull/901): verify signature from event webhook. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + +**Library - Fix** +- [PR #904](https://github.com/sendgrid/sendgrid-python/pull/904): revert "feat: Add equality to Email". Thanks to [@childish-sambino](https://github.com/childish-sambino)! + + +[2020-05-27] Version 6.3.2 +-------------------------- +**Library - Docs** +- [PR #895](https://github.com/sendgrid/sendgrid-python/pull/895): Fixed Subject typo. Thanks to [@dmitry-krasilnikov](https://github.com/dmitry-krasilnikov)! + + +[2020-05-13] Version 6.3.1 +-------------------------- +**Library - Docs** +- [PR #893](https://github.com/sendgrid/sendgrid-python/pull/893): Update readme supported versions. Thanks to [@PaulMcMillan](https://github.com/PaulMcMillan)! + +**Library - Fix** +- [PR #888](https://github.com/sendgrid/sendgrid-python/pull/888): migrate to common prism setup. Thanks to [@childish-sambino](https://github.com/childish-sambino)! + + +[2020-04-29] Version 6.3.0 +-------------------------- +**Library - Feature** +- [PR #882](https://github.com/sendgrid/sendgrid-python/pull/882): add support for Twilio Email. Thanks to [@childish-sambino](https://github.com/childish-sambino)! + + +[2020-04-15] Version 6.2.2 +-------------------------- +**Library - Fix** +- [PR #881](https://github.com/sendgrid/sendgrid-python/pull/881): correct the User-Agent casing. Thanks to [@childish-sambino](https://github.com/childish-sambino)! + + +[2020-04-01] Version 6.2.1 +-------------------------- +**Library - Docs** +- [PR #880](https://github.com/sendgrid/sendgrid-python/pull/880): support verbiage for login issues. Thanks to [@adamchasetaylor](https://github.com/adamchasetaylor)! + + +[2020-03-18] Version 6.2.0 +-------------------------- +**Library - Docs** +- [PR #878](https://github.com/sendgrid/sendgrid-python/pull/878): fix code snippet in README. Thanks to [@neerajgupta2407](https://github.com/neerajgupta2407)! +- [PR #734](https://github.com/sendgrid/sendgrid-python/pull/734): Further Remove "Whitelabel" References. Thanks to [@crweiner](https://github.com/crweiner)! +- [PR #714](https://github.com/sendgrid/sendgrid-python/pull/714): Give preference to 'to' after visible. Thanks to [@agarwalrounak](https://github.com/agarwalrounak)! +- [PR #669](https://github.com/sendgrid/sendgrid-python/pull/669): Fixed links in examples. Thanks to [@pktrieu](https://github.com/pktrieu)! +- [PR #706](https://github.com/sendgrid/sendgrid-python/pull/706): Fix grammatical errors. Thanks to [@vinayak42](https://github.com/vinayak42)! +- [PR #682](https://github.com/sendgrid/sendgrid-python/pull/682): Updated link to direct to #L9. Thanks to [@vinayak42](https://github.com/vinayak42)! + +**Library - Feature** +- [PR #739](https://github.com/sendgrid/sendgrid-python/pull/739): Add equality to Email. Thanks to [@mcintyre94](https://github.com/mcintyre94)! + +**Library - Chore** +- [PR #731](https://github.com/sendgrid/sendgrid-python/pull/731): Remove unused Python json modules. Thanks to [@gy741](https://github.com/gy741)! + + +[2020-03-04] Version 6.1.3 +-------------------------- +**Library - Chore** +- [PR #844](https://github.com/sendgrid/sendgrid-python/pull/844): Clean up sendgrid.py. Thanks to [@Aman-am](https://github.com/Aman-am)! +- [PR #870](https://github.com/sendgrid/sendgrid-python/pull/870): add Python 3.8 to Travis. Thanks to [@childish-sambino](https://github.com/childish-sambino)! + +**Library - Fix** +- [PR #872](https://github.com/sendgrid/sendgrid-python/pull/872): add config.yml file to pypi distribution files. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + + +[2020-02-19] Version 6.1.2 +-------------------------- +**Library - Fix** +- [PR #838](https://github.com/sendgrid/sendgrid-python/pull/838): Convert integer substitution value to string. Thanks to [@lifez](https://github.com/lifez)! + + +[2020-01-24] Version 6.1.1 +-------------------------- +**Library - Docs** +- [PR #865](https://github.com/sendgrid/sendgrid-python/pull/865): baseline all the templated markdown docs. Thanks to [@childish-sambino](https://github.com/childish-sambino)! + +**Library - Chore** +- [PR #853](https://github.com/sendgrid/sendgrid-python/pull/853): clean up imports. Thanks to [@DucarrougeR](https://github.com/DucarrougeR)! +- [PR #862](https://github.com/sendgrid/sendgrid-python/pull/862): prep the repo for automated releasing. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + +**Library - Fix** +- [PR #863](https://github.com/sendgrid/sendgrid-python/pull/863): improve make test command. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + + +[2019-09-12] Version 6.1.0 +--------------------------- + +### Added +- Bumped dependency on python-http-client to [v3.2.1](https://github.com/sendgrid/python-http-client/releases/tag/v3.2.0) +- [PR #807](https://github.com/sendgrid/sendgrid-python/pull/807): Get version from version.py instead of version.txt. (BIG thanks to [@lipis](https://github.com/lipis)) +- [PR #808](https://github.com/sendgrid/sendgrid-python/pull/808): API key permissions mention in USAGE.md. (BIG thanks to [@int-ua](https://github.com/int-ua)) + +### Fixed +- [PR #763](https://github.com/sendgrid/sendgrid-python/pull/763): Updated Error Message Section. (BIG thanks to [@FFX01](https://github.com/FFX01)) +- [PR #818](https://github.com/sendgrid/sendgrid-python/pull/818): Handle new API in the helper example. (BIG thanks to [@enugentdt](https://github.com/enugentdt)) +- [PR #839](https://github.com/sendgrid/sendgrid-python/pull/839): Fix for ganalytics json builder. + +## [6.0.5] - 2019-05-01 ## + +### Fixed + +- [PR #794](https://github.com/sendgrid/sendgrid-python/pull/794): Update type requirements used for multiple objects (BIG thanks to [@jphilipsen05](https://github.com/jphilipsen05)) +- [PR #797](https://github.com/sendgrid/sendgrid-python/pull/797): API Key typo +- [PR #792](https://github.com/sendgrid/sendgrid-python/pull/792): Fixes #790: TypeError "name must be of type string" regression when name is Unicode string (BIG thanks to [@johnpkennedy](https://github.com/johnpkennedy)) +- [PR #785](https://github.com/sendgrid/sendgrid-python/pull/785): Link for 'Transactional Templates' in use_cases README.md broken (BIG thanks to [@nguyenpk](https://github.com/nguyenpk)) + +## [6.0.3] - 2019-04-05 ## + +### Added +- Twilio SendGrid branding +- [Twilio SMS example](use_cases/sms.md) +- Updated CLA process + +## [6.0.0] - 2019-04-02 ## + +### BREAKING CHANGE + +- The `Mail` helper signature has changed. +- Setting up a `SendGridAPIClient` has changed. + +Please see the [use cases documentation](use_cases/README.md) for implemenation details. + +This refactor was based on [this issue](https://github.com/sendgrid/sendgrid-python/issues/347). BIG thanks to all of those who [participated](https://github.com/sendgrid/sendgrid-python/issues/323) in shaping this release. + +In particular, BIG THANKS to: +[@yothinix](https://github.com/yothinix) +[@jeffoneill](https://github.com/jeffoneill) +[@elbuo8](https://github.com/elbuo8) +[@Jakobovski](https://github.com/Jakobovski) +[@andriisoldatenko](https://github.com/andriisoldatenko) +[@dibyadas](https://github.com/dibyadas) +[@belfazt](https://github.com/belfazt) +[@iandouglas](https://github.com/iandouglas) +[@mehronkugler](https://github.com/mehronkugler) + +### Fixed +- [PR #727](https://github.com/sendgrid/sendgrid-python/pull/727): Use raw-string notation for regex to avoid invalid escape sequence (BIG thanks to [@](https://github.com/)) +- [PR #715](https://github.com/sendgrid/sendgrid-python/pull/715): Correct attribution links formating (BIG thanks to [@hugovk](https://github.com/hugovk)) +- [PR #640](https://github.com/sendgrid/sendgrid-python/pull/640): Changes suggested by grammarly (BIG thanks to [@xeon-zolt](https://github.com/xeon-zolt)) +- [PR #697](https://github.com/sendgrid/sendgrid-python/pull/697): PEP8 Fixes and String Formatting Enhancement (BIG thanks to [@vkmrishad](https://github.com/vkmrishad)) +- [PR #647](https://github.com/sendgrid/sendgrid-python/pull/647): TROUBLESHOOTING.md broken link fix (BIG thanks to [@arshadkazmi42](https://github.com/arshadkazmi42)) +- [PR #638](https://github.com/sendgrid/sendgrid-python/pull/638): Fixed syntax errors in Kitchen sink Python example code (BIG thanks to [@vinayak42](https://github.com/vinayak42)) +- [PR #687](https://github.com/sendgrid/sendgrid-python/pull/687): Remove references to "Whitelabel" (BIG thanks to [@crweiner](https://github.com/crweiner)) +- [PR #690](https://github.com/sendgrid/sendgrid-python/pull/690): Corrected links in CoC (BIG thanks to [@bhavinjawade](https://github.com/bhavinjawade)) +- [PR #656](https://github.com/sendgrid/sendgrid-python/pull/656): Fix helper mail_example redirection link (BIG thanks to [@joshuadeguzman](https://github.com/joshuadeguzman)) +- [PR #636](https://github.com/sendgrid/sendgrid-python/pull/636): Fix broken link for mail example (BIG thanks to [@mattjegan](https://github.com/mattjegan)) +- [PR #630](https://github.com/sendgrid/sendgrid-python/pull/630): Update requirements.txt (BIG thanks to [@rahulkumaran](https://github.com/rahulkumaran)) +- [PR #628](https://github.com/sendgrid/sendgrid-python/pull/628): Update job description in README (BIG thanks to [@Jeremyyang920](https://github.com/Jeremyyang920)) +- [PR #618](https://github.com/sendgrid/sendgrid-python/pull/618): Quote names containing comma or semicolon (BIG thanks to [@cmccandless](https://github.com/cmccandless)) +- [PR #613](https://github.com/sendgrid/sendgrid-python/pull/613): Fix typos (BIG thanks to [@Bharat123rox](https://github.com/Bharat123rox)) +- [PR #616](https://github.com/sendgrid/sendgrid-python/pull/616): Fix typos (BIG thanks to [@hugovk](https://github.com/hugovk)) +- [PR #619](https://github.com/sendgrid/sendgrid-python/pull/619): Fix format of dependency pytest (BIG thanks to [@cmccandless](https://github.com/cmccandless)) +- [PR #611](https://github.com/sendgrid/sendgrid-python/pull/611): Fix broken link (BIG thanks to [@themousepotato](https://github.com/themousepotato)) +- [PR #488](https://github.com/sendgrid/sendgrid-python/pull/488): Fix similar code issue in mail.py helper (BIG thanks to [@adiman9](https://github.com/adiman9)) +- [PR #496](https://github.com/sendgrid/sendgrid-python/pull/496): Fix issues in sendgrid/helpers/mail/mail.py (BIG thanks to [@galihmelon](https://github.com/galihmelon)) +- [PR #510](https://github.com/sendgrid/sendgrid-python/pull/510): Fix similar code issue in sendgrid/helpers/mail/mail.py (BIG thanks to [@nanspro](https://github.com/nanspro)) +- [PR #524](https://github.com/sendgrid/sendgrid-python/pull/524): Fix main failure on travis (relating to ASM raise-assertion). (BIG thanks to [@extemporalgenome](https://github.com/extemporalgenome)) + +### Added +- [PR #666](https://github.com/sendgrid/sendgrid-python/pull/666): Created First-timers.md File (BIG thanks to [@jaykay12](https://github.com/jaykay12)) +- [PR #655](https://github.com/sendgrid/sendgrid-python/pull/655): Update USAGE.md (BIG thanks to [@ChatPion](https://github.com/ChatPion)) +- [PR #665](https://github.com/sendgrid/sendgrid-python/pull/665): Add use case for generation of Plain Text Content from HTML (BIG thanks to [@cmccandless](https://github.com/cmccandless)) +- [PR #718](https://github.com/sendgrid/sendgrid-python/pull/718): Update prerequisites (BIG thanks to [@Rishabh04-02](https://github.com/Rishabh04-02)) +- [PR #722](https://github.com/sendgrid/sendgrid-python/pull/722): Updated README.md (BIG thanks to [@rahulpuroht](https://github.com/rahulpuroht)) +- [PR #711](https://github.com/sendgrid/sendgrid-python/pull/711): Cleanup Dockerfiles (BIG thanks to [@rawkode](https://github.com/rawkode)) +- [PR #709](https://github.com/sendgrid/sendgrid-python/pull/709): Cleanup Env Documentation (BIG thanks to [@rawkode](https://github.com/rawkode)) +- [PR #631](https://github.com/sendgrid/sendgrid-python/pull/631): Allow creation of Mail from EmailMessage (BIG thanks to [@cmccandless](https://github.com/cmccandless)) +- [PR #683](https://github.com/sendgrid/sendgrid-python/pull/683): Create README.md for mail_example.py (BIG thanks to [@tulikavijay](https://github.com/tulikavijay)) +- [PR #663](https://github.com/sendgrid/sendgrid-python/pull/663): Converted README to reStructuredText and version as plain text file (BIG thanks to [@StrikerRUS](https://github.com/StrikerRUS)) +- [PR #643](https://github.com/sendgrid/sendgrid-python/pull/643): Add test to increase test coverage on config.py (BIG thanks to [@zkan](https://github.com/zkan)) +- [PR #692](https://github.com/sendgrid/sendgrid-python/pull/692): Add unit tests for spam check (BIG thanks to [@pyasi](https://github.com/pyasi)) +- [PR #637](https://github.com/sendgrid/sendgrid-python/pull/637): Add support for Python 3.7 (BIG thanks to [@hugovk](https://github.com/hugovk)) +- [PR #626](https://github.com/sendgrid/sendgrid-python/pull/626): Drop support for EOL Python 2.6 and 3.0-3.3 (BIG thanks to [@hugovk](https://github.com/hugovk)) +- [PR #486](https://github.com/sendgrid/sendgrid-python/pull/486): Refactor sengrid get method of Mail class (BIG thanks to [@Prashant-Surya](https://github.com/Prashant-Surya)) +- [PR #493](https://github.com/sendgrid/sendgrid-python/pull/493): Refactor personalization.py (BIG thanks to [@defaults](https://github.com/defaults)) +- [PR #509](https://github.com/sendgrid/sendgrid-python/pull/509): Refactor mail.py (BIG thanks to [@palash16](https://github.com/palash16)) +- [PR #512](https://github.com/sendgrid/sendgrid-python/pull/512): Refactor mail.py (BIG thanks to [@extemporalgenome](https://github.com/extemporalgenome)) + +## [5.4.1] - 2018-06-26 ## +### Fixed +- [PR #585](https://github.com/sendgrid/sendgrid-python/pull/585): Fix typo in `mail_example.py`. Big thanks to [Anurag Anand](https://github.com/theanuraganand) for the PR! +- [PR #583](https://github.com/sendgrid/sendgrid-python/pull/583): Fix `Personalization.substitutions` setter. Trying to set substitutions directly rather than with add_substitution was causing an infinite regress. Big thanks to [Richard Nias](https://github.com/richardnias) for the PR! + +## [5.4.0] - 2018-06-07 ## +### Added +- [PR #384](https://github.com/sendgrid/sendgrid-python/pull/384): Adds how to set up domain whitelabel and how to view email statistics. Big thanks to [Aditya Tandon](https://github.com/adityatandon007) for the PR! +- [PR #427](https://github.com/sendgrid/sendgrid-python/pull/427): Increase config.py coverage. Big thanks to [Jeferson Daniel](https://github.com/jefersondaniel) for the PR! +- [PR #423](https://github.com/sendgrid/sendgrid-python/pull/423): Update config.py with better file handling. Big thanks to [Ajitesh Rai](https://github.com/ajiteshr7) for the PR! +- [PR #449](https://github.com/sendgrid/sendgrid-python/pull/449): Add a .env_sample file and Update README.md. Big thanks to [trangttt](https://github.com/trangttt) for the PR! +- [PR #463](https://github.com/sendgrid/sendgrid-python/pull/449): Add code climate. +- [PR #455](https://github.com/sendgrid/sendgrid-python/pull/455): Use with context manager and a few PEP8 changes. Big thanks to [Tim](https://github.com/The-White-Wolf) for the PR! +- [PR #470](https://github.com/sendgrid/sendgrid-python/pull/470): Modularize lengthy method. Big thanks to [Suprith Kumar Suvarneshwar](https://github.com/suprithIUB) for the PR! +- [PR #425](https://github.com/sendgrid/sendgrid-python/pull/425): Add tests for sendgrid.py apikey and api_key setters. Big thanks to [Krista LaFentres](https://github.com/lafentres) for the PR! +- [PR #446](https://github.com/sendgrid/sendgrid-python/pull/446): Added PULL_REQUEST_TEMPLATE. Big thanks to [Aleksandr Sobolev](https://github.com/s0b0lev) for the PR! +- [PR #472](https://github.com/sendgrid/sendgrid-python/pull/472): Moved mail helper classes into separate files. Big thanks to [Milos Pejanovic](https://github.com/runz0rd) for the PR! +- [PR #481](https://github.com/sendgrid/sendgrid-python/pull/481): Documented the new error handling functionality from python-http-client. Big thanks to [Manjiri Tapaswi](https://github.com/mptap) for the PR! +- [PR #418](https://github.com/sendgrid/sendgrid-python/pull/418): Add test for apps.py. Big thanks to [Sinan Comert](https://github.com/scomert) for the PR! +- [PR #438](https://github.com/sendgrid/sendgrid-python/pull/438): Update docstrings/pydoc/help. Big thanks to [Gabriel Krell](https://github.com/gabrielkrell) for the PR! +- [PR #413](https://github.com/sendgrid/sendgrid-python/pull/413): Error-checking in Mail helper/ASM. Big thanks to [Gabriel Krell](https://github.com/gabrielkrell) for the PR! +- [PR #518](https://github.com/sendgrid/sendgrid-python/pull/518): Announcement about Data Platform Engineer posting. Big thanks to [Marghodk](https://github.com/Marghodk) for the PR! +- [PR #479](https://github.com/sendgrid/sendgrid-python/pull/479): Add Project tests. Big thanks to [Peter Hampton](https://github.com/pjhampton) for the PR! +- [PR #480](https://github.com/sendgrid/sendgrid-python/pull/480): Test to check year in LICENSE.txt. Big thanks to [Navin Pai](https://github.com/navinpai) for the PR! +- [PR #476](https://github.com/sendgrid/sendgrid-python/pull/476): Add tests for Send.py. Big thanks to [Artiem K.](https://github.com/artiemq) for the PR! +- [PR #366](https://github.com/sendgrid/sendgrid-python/pull/366): Add AWS app tutorial to USE_CASES.md. Big thanks to [Mike Vanbuskirk](https://github.com/codevbus) for the PR! +- [PR #365](https://github.com/sendgrid/sendgrid-python/pull/365): Write tutorial to deploy simple Django app on Heroku. Big thanks to [Kan Ouivirach](https://github.com/zkan) for the PR! +- [PR #526](https://github.com/sendgrid/sendgrid-python/pull/526): Include code reviews section. Big thanks to [Jared Scott](https://github.com/jlax47) for the PR! +- [PR #414](https://github.com/sendgrid/sendgrid-python/pull/414): Provide utf-8 as encoding explicitly when opening text files. Big thanks to [Ruslan Shestopalyuk](https://github.com/rshest) for the PR! +- [PR #537](https://github.com/sendgrid/sendgrid-python/pull/537): Add unit testing support to .codeclimate.yml. Big thanks to [Prashu Chaudhary](https://github.com/prashuchaudhary) for the PR! +- [PR #554](https://github.com/sendgrid/sendgrid-python/pull/554): Ensure params are applied independently. Big thanks to [Nino Milenovic](https://github.com/rubyengineer) for the PR! +- [PR #557](https://github.com/sendgrid/sendgrid-python/pull/557): Client cleanup. Big thanks to [Slam](https://github.com/3lnc) for the PR! +- [PR #569](https://github.com/sendgrid/sendgrid-python/pull/569): Make Mail helper parameters truly optional. Big thanks to [Ian Beck](https://github.com/onecrayon) for the PR! + +### Fixed +- [PR #415](https://github.com/sendgrid/sendgrid-python/pull/415): Typos. Big thanks to [Mohd Huzaifa Faruqui](https://github.com/huzaifafaruqui) for the PR! +- [PR #421](https://github.com/sendgrid/sendgrid-python/pull/421): Typos. Big thanks to [Abhishek Bhatt](https://github.com/ab-bh) for the PR! +- [PR #432](https://github.com/sendgrid/sendgrid-python/pull/432): Typos. Big thanks to [Gaurav Arora](https://github.com/gaurav61) for the PR! +- [PR #431](https://github.com/sendgrid/sendgrid-python/pull/431): Typos. Big thanks to [Gaurav Arora](https://github.com/gaurav61) for the PR! +- [PR #430](https://github.com/sendgrid/sendgrid-python/pull/430): Attempt to sync before executing the shell command. Big thanks to [Aditya Narayan](https://github.com/aditnryn) for the PR! +- [PR #429](https://github.com/sendgrid/sendgrid-python/pull/429): Typos. Big thanks to [daluntw](https://github.com/daluntw) for the PR! +- [PR #492](https://github.com/sendgrid/sendgrid-python/pull/492): +Updated date-range in the LICENSE file. Big thanks to [Dhruv Srivastava](https://github.com/dhruvhacks) for the PR! +- [PR #482](https://github.com/sendgrid/sendgrid-python/pull/482): Typos. Big thanks to [Karan Samani](https://github.com/Kimi450) for the PR! +- [PR #504](https://github.com/sendgrid/sendgrid-python/pull/504): Fix .codeclimate.yml. Big thanks to [Matt Bernier](https://github.com/mbernier) for the PR! +- [PR #505](https://github.com/sendgrid/sendgrid-python/pull/505): Remove unnecessary GitHub PR templates. Big thanks to [Alex](https://github.com/pushkyn) for the PR! +- [PR #494](https://github.com/sendgrid/sendgrid-python/pull/494): Remove unused import in register.py. Big thanks to [Alexis Rivera De La Torre](https://github.com/gardlt) for the PR! +- [PR #469](https://github.com/sendgrid/sendgrid-python/pull/469): +Removed the trailing white spaces. Big thanks to [Siddaram Halli](https://github.com/SidduH) for the PR! +- [PR #484](https://github.com/sendgrid/sendgrid-python/pull/484): Python style fixes. Big thanks to [Gabriel Krell](https://github.com/gabrielkrell) for the PR! +- [PR #508](https://github.com/sendgrid/sendgrid-python/pull/508): Typos. Big thanks to [Saksham Gupta](https://github.com/shucon) for the PR! +- [PR #353](https://github.com/sendgrid/sendgrid-python/pull/353): Typos. Big thanks to [Yothin M](https://github.com/yothinix) for the PR! +- [PR #564](https://github.com/sendgrid/sendgrid-python/pull/564): Typos. Big thanks to [Chao](https://github.com/chaoranxie) for the PR! +- [PR #424](https://github.com/sendgrid/sendgrid-python/pull/424): Updating version 2.7.8 to 2.7.11 to match the version in pyenv install instruction. Big thanks to [Krista LaFentres](https://github.com/lafentres) for the PR! +- [PR #454](https://github.com/sendgrid/sendgrid-python/pull/454): Requests to send mail with both plain text and HTML content fail if the HTML content is specified first. Big thanks to [Ryan D'souza](https://github.com/dsouzarc) for the PR! +- [PR #466](https://github.com/sendgrid/sendgrid-python/pull/466): Fixed PEP8 issues. Big thanks to [Piotr Szwarc](https://github.com/blackpioter) for the PR! +- [PR #522](https://github.com/sendgrid/sendgrid-python/pull/522): Typos. Big thanks to [Abhishek J](https://github.com/slashstar) for the PR! +- [PR #514](https://github.com/sendgrid/sendgrid-python/pull/514): Fix method_complexity issue in sendgrid/helpers/mail/ganalytics.py. Big thanks to [Chetan Kumar](https://github.com/chetankm-cs) for the PR! +- [PR #515](https://github.com/sendgrid/sendgrid-python/pull/515): Typos. Big thanks to [Mohd Ali Rizwi](https://github.com/alirizwi) for the PR! +- [PR #519](https://github.com/sendgrid/sendgrid-python/pull/519): Typos. Big thanks to [Aashish Gaba](https://github.com/ishucr7) for the PR! +- [PR #532](https://github.com/sendgrid/sendgrid-python/pull/532): Typos. Big thanks to [~](https://github.com/delirious-lettuce) for the PR! +- [PR #533](https://github.com/sendgrid/sendgrid-python/pull/533): Fix shadowed builtins, `id` -> `id_`. Big thanks to [~](https://github.com/delirious-lettuce) for the PR! +- [PR #581](https://github.com/sendgrid/sendgrid-python/pull/581): Typos. Big thanks to [Silvia Botros](https://github.com/silviabotros) for the PR! +- [PR #513](https://github.com/sendgrid/sendgrid-python/pull/513): Typos. Big thanks to [thepriefy](https://github.com/thepriefy) for the PR! +- [PR #538](https://github.com/sendgrid/sendgrid-python/pull/538): Fix bug in get_mock_personalization_dict(). Big thanks to [PierreMonico](https://github.com/PierreMonico) for the PR! +- [PR #543](https://github.com/sendgrid/sendgrid-python/pull/543): Typos. Big thanks to [Matthieu Bonnefoy](https://github.com/mbonnefoy) for the PR! + ## [5.3.0] - 2017-10-23 ## ### Added - Pull #348: Allows users to submit rfc822 formatted email addresses @@ -23,14 +520,14 @@ All notable changes to this project will be documented in this file. - Big thanks to [belfazt](https://github.com/belfazt) for the pull request! ## [5.0.1] - 2017-08-29 -### Fix +### Fixed - Pull #337, fixes issue #366 - On install, some experienced: `ValueError: ("Expected ',' or end-of-list in", 'python-http-client ==3.0.*', 'at', '*')` ## [5.0.0] - 2017-08-11 ### BREAKING CHANGE - The breaking change actually happened in [version 4.2.1](https://github.com/sendgrid/sendgrid-python/releases/tag/v4.2.1), where I mistakenly applied a patch version bump. See issues #328 and #321 for details. -- This version (5.0.0) replaces error handling via HTTPError from urllib in favor of custom error handling via the [HTTPError class](https://github.com/sendgrid/python-http-client/blob/master/python_http_client/exceptions.py) as was the case in version 4.2.0. +- This version (5.0.0) replaces error handling via HTTPError from urllib in favor of custom error handling via the [HTTPError class](https://github.com/sendgrid/python-http-client/blob/HEAD/python_http_client/exceptions.py) as was the case in version 4.2.0. ## [4.2.1] - 2017-08-03 ## ### Fixed @@ -51,7 +548,7 @@ All notable changes to this project will be documented in this file. ### BREAKING CHANGE - Pull #244 [refactor helpers using property getter/setter](https://github.com/sendgrid/sendgrid-python/pull/244/files) - Big thanks to [Denis Vlasov](https://github.com/denis90) for the pull request! -- The changes break the implementation of the [Mail Helper](https://github.com/sendgrid/sendgrid-python/tree/master/sendgrid/helpers/mail) `Mail()` class +- The changes break the implementation of the [Mail Helper](sendgrid/helpers/mail) `Mail()` class - `set_from()` is now the property `from_email` - `set_subject()` is now the property `subject` - `set_template_id()` is now the property `template_id` @@ -140,7 +637,7 @@ All notable changes to this project will be documented in this file. ## [3.2.2] - 2016-08-23 ## ### Added - Table of Contents in the README -- Added a [USE_CASES.md](https://github.com/sendgrid/sendgrid-python/blob/master/USE_CASES.md) section, with the first use case example for transactional templates +- Added a [USE_CASES.md](USE_CASES.md) section, with the first use case example for transactional templates ## [3.2.1] - 2016-08-17 ## ### Fixed @@ -162,7 +659,7 @@ All notable changes to this project will be documented in this file. ## [3.1.8] - 2016-07-25 ## ### Added -- [Troubleshooting](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md) section +- [Troubleshooting](TROUBLESHOOTING.md) section ## [3.1.7] - 2016-07-25 ## ### Added diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 39ed18bf7..2f0727ed5 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,41 +1,73 @@ -# SendGrid Community Code of Conduct - - The SendGrid open source community is made up of members from around the globe with a diverse set of skills, personalities, and experiences. It is through these differences that our community experiences successes and continued growth. When you're working with members of the community, we encourage you to follow these guidelines, which help steer our interactions and strive to maintain a positive, successful and growing community. - - ### Be Open - Members of the community are open to collaboration, whether it's on pull requests, code reviews, approvals, issues or otherwise. We're receptive to constructive comments and criticism, as the experiences and skill sets of all members contribute to the whole of our efforts. We're accepting of all who wish to take part in our activities, fostering an environment where anyone can participate, and everyone can make a difference. - - ### Be Considerate - Members of the community are considerate of their peers, which include other contributors and users of SendGrid. We're thoughtful when addressing the efforts of others, keeping in mind that often the labor was completed with the intent of the good of the community. We're attentive in our communications, whether in person or online, and we're tactful when approaching differing views. - - ### Be Respectful - Members of the community are respectful. We're respectful of others, their positions, their skills, their commitments and their efforts. We're respectful of the volunteer efforts that permeate the SendGrid community. We're respectful of the processes outlined in the community, and we work within them. When we disagree, we are courteous in raising our issues. Overall, we're good to each other. We contribute to this community not because we have to, but because we want to. If we remember that, these guidelines will come naturally. - - ## Additional Guidance - - ### Disclose Potential Conflicts of Interest - Community discussions often involve interested parties. We expect participants to be aware when they are conflicted due to employment or other projects they are involved in and disclose those interests to other project members. When in doubt, over-disclose. Perceived conflicts of interest are important to address so that the community’s decisions are credible even when unpopular, difficult or favorable to the interests of one group over another. - - ### Interpretation - This Code is not exhaustive or complete. It is not a rulebook; it serves to distill our common understanding of a collaborative, shared environment and goals. We expect it to be followed in spirit as much as in the letter. When in doubt, try to abide by [SendGrid’s cultural values](https://sendgrid.com/blog/employee-engagement-the-4h-way) defined by our “4H’s”: Happy, Hungry, Humble and Honest. - - ### Enforcement - Most members of the SendGrid community always comply with this Code, not because of the existence of this Code, but because they have long experience participating in open source communities where the conduct described above is normal and expected. However, failure to observe this Code may be grounds for suspension, reporting the user for abuse or changing permissions for outside contributors. - - ## If you have concerns about someone’s conduct - **Initiate Direct Contact** - It is always appropriate to email a community member (if contact information is available), mention that you think their behavior was out of line, and (if necessary) point them to this Code. - - **Discuss Publicly** - Discussing publicly is always acceptable. Note, though, that approaching the person directly may be better, as it tends to make them less defensive, and it respects the time of other community members, so you probably want to try direct contact first. - - **Contact the Moderators** - You can reach the SendGrid moderators by emailing dx@sendgrid.com. - - ## Submission to SendGrid Repositories - Finally, just a reminder, changes to the SendGrid repositories will only be accepted upon completion of the [SendGrid Contributor Agreement](https://cla.sendgrid.com). - - ## Attribution - - SendGrid thanks the following, on which it draws for content and inspiration: - - [Python Community Code of Conduct](https://www.python.org/psf/codeofconduct/) - [Open Source Initiative General Code of Conduct](https://opensource.org/codeofconduct) - [Apache Code of Conduct](https://www.apache.org/foundation/policies/conduct.html) +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at open-source@twilio.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index abf93b956..af9507154 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,169 +1,84 @@ -Hello! Thank you for choosing to help contribute to one of the SendGrid open source libraries. There are many ways you can contribute and help is always welcome. We simply ask that you follow the following contribution policies. +Hello! Thank you for choosing to help contribute to one of the Twilio SendGrid open source libraries. There are many ways you can contribute and help is always welcome. We simply ask that you follow the following contribution policies. + +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. -- [CLAs and CCLAs](#cla) -- [Roadmap & Milestones](#roadmap) -- [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) + - [Development Environment](#development-environment) + - [Prerequisites](#prerequisites) + - [Initial setup](#initial-setup) + - [Environment Variables](#environment-variables) + - [Execute:](#execute) +- [Understanding the Code Base](#understanding-the-code-base) - [Testing](#testing) -- [Style Guidelines & Naming Conventions](#style-guidelines-and-naming-conventions) +- [Style Guidelines & Naming Conventions](#style-guidelines--naming-conventions) - [Creating a Pull Request](#creating-a-pull-request) - [Code Reviews](#code-reviews) - -We use [Milestones](https://github.com/sendgrid/sendgrid-python/milestones) to help define current roadmaps, please feel free to grab an issue from the current milestone. Please indicate that you have begun work on it to avoid collisions. Once a PR is made, community review, comments, suggestions and additional PRs are welcomed and encouraged. - - -## CLAs and CCLAs - -Before you get started, SendGrid requires that a SendGrid Contributor License Agreement (CLA) be filled out by every contributor to a SendGrid open source project. - -Our goal with the CLA is to clarify the rights of our contributors and reduce other risks arising from inappropriate contributions. The CLA also clarifies the rights SendGrid holds in each contribution and helps to avoid misunderstandings over what rights each contributor is required to grant to SendGrid when making a contribution. In this way the CLA encourages broad participation by our open source community and helps us build strong open source projects, free from any individual contributor withholding or revoking rights to any contribution. - -SendGrid does not merge a pull request made against a SendGrid open source project until that pull request is associated with a signed CLA. Copies of the CLA are available [here](https://gist.github.com/SendGridDX/98b42c0a5d500058357b80278fde3be8#file-sendgrid_cla). - -When you create a Pull Request, after a few seconds, a comment will appear with a link to the CLA. Click the link and fill out the brief form and then click the "I agree" button and you are all set. You will not be asked to re-sign the CLA unless we make a change. - 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 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]((https://github.com/sendgrid/sendgrid-python/.github/ISSUE_TEMPLATE)) (borrowed from [Ghost](https://github.com/TryGhost/Ghost/)). The template uses [GitHub flavored markdown](https://help.github.com/articles/github-flavored-markdown/) for formatting. - - ## Improvements to the Codebase We welcome direct contributions to the sendgrid-python code base. Thank you! -### Development Environment ### - -#### Using Docker #### -You can use our Docker image to avoid setting up the development environment yourself. See [USAGE.md](https://github.com/sendgrid/sendgrid-python/blob/master/docker/USAGE.md). +### Development Environment -#### Install and Run Locally #### +#### Prerequisites -##### Prerequisites ##### - -- Python 2.6 through 3.6 +- Python version 2.7, 3.5, 3.6, 3.7, or 3.8 - [python_http_client](https://github.com/sendgrid/python-http-client) +- [cryptography](https://github.com/pyca/cryptography) +- [pyenv](https://github.com/yyuu/pyenv) +- [tox](https://pypi.python.org/pypi/tox) -##### Initial setup: ##### +#### Initial setup ```bash git clone https://github.com/sendgrid/sendgrid-python.git cd sendgrid-python ``` -## Environment Variables +#### Environment Variables -First, get your free SendGrid account [here](https://sendgrid.com/free?source=sendgrid-python). +First, get your free Twilio SendGrid account [here](https://sendgrid.com/free?source=sendgrid-python). Next, update your environment with your [SENDGRID_API_KEY](https://app.sendgrid.com/settings/api_keys). ```bash -echo "export SENDGRID_API_KEY='YOUR_API_KEY'" > sendgrid.env -echo "sendgrid.env" >> .gitignore -source ./sendgrid.env +cp .env_sample .env ``` -##### Execute: ##### - -See the [examples folder](https://github.com/sendgrid/sendgrid-python/tree/master/examples) to get started quickly. - -If testing from the root directory of this repo, create a new file (e.g. test.py) and replace `import sendgrid` with `from sendgrid import *` - - -## Understanding the Code Base +Then edit `.env` and insert your API key. -**/examples** +```bash +# You do not need to do this when using Docker Compose +source .env +``` -Working examples that demonstrate usage. +#### Execute -**/tests** +See the [examples folder](examples) to get started quickly. -Currently we have unit and profiling tests. +If testing from the root directory of this repo, create a new file (e.g. test.py) and replace `import sendgrid` with `from sendgrid import *` -**/sendgrid** +## Understanding the Code Base -The Web API v3 client is `sendgrid.py`, the other files are legacy code for our mail send v2 endpoint. +- **/examples** + - Working examples that demonstrate usage. +- **/tests** + - Currently, we have unit and profiling tests. +- **/sendgrid** + - The Web API v3 client is `sendgrid.py`, the other files are legacy code for our mail send v2 endpoint. - ## Testing -All PRs require passing tests before the PR will be reviewed. - -All test files are in the [`test`](https://github.com/sendgrid/sendgrid-python/test) directory. - -For the purposes of contributing to this repo, please update the [`test_sendgrid.py`](https://github.com/sendgrid/sendgrid-python/tree/master/test/test_sendgrid.py) file with unit tests as you modify the code. - -For Python 2.6.*: - -`unit2 discover -v` - -For Python 2.7.* and up: - -`python -m unittest discover -v` - -### Testing Multiple Versions of Python - -All PRs require passing tests before the PR will be reviewed. +The PR must pass all the tests before it is reviewed. -#### Prerequisites: #### +All test files are in the [`test`](test) directory. For the purposes of contributing to this repo, please update the [`test_sendgrid.py`](test/test_sendgrid.py) file with unit tests as you modify the code. -The above local "Initial setup" is complete +The integration tests require a Twilio SendGrid mock API in order to execute. We've simplified setting this up using Docker to run the tests. You will just need [Docker Desktop](https://docs.docker.com/get-docker/) and `make`. -* [pyenv](https://github.com/yyuu/pyenv) -* [tox](https://pypi.python.org/pypi/tox) -* [prism](https://github.com/stoplightio/prism) v0.6 - It should be available in your PATH, but unittest script -will try to install it locally if not found. Apart from PATH env variable it will check in `~/bin` and `/usr/local/bin`. -You can install by yourself it in user dir by calling `source test/prism.sh`. +Once these are available, simply execute the Docker test target to run all tests: `make test-docker`. This command can also be used to open an interactive shell into the container where this library is installed. To start a *bash* shell for example, use this command: `command=bash make test-docker`. -#### Initial setup: #### - -Add ```eval "$(pyenv init -)"``` to your shell environment (.profile, .bashrc, etc) after installing tox, you only need to do this once. - -``` -pyenv install 2.6.9 -pyenv install 2.7.11 -pyenv install 3.4.3 -pyenv install 3.5.0 -python setup.py install -pyenv local 3.5.0 3.4.3 2.7.8 2.6.9 -pyenv rehash -``` - -#### Execute: #### - -``` -source venv/bin/activate -tox -``` - - ## Style Guidelines & Naming Conventions 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. @@ -176,7 +91,7 @@ Please run your code through: - [pylint](https://www.pylint.org/) - [pep8](https://pypi.python.org/pypi/pep8) -## Creating a Pull Request +## Creating a Pull Request 1. [Fork](https://help.github.com/fork-a-repo/) the project, clone your fork, and configure the remotes: @@ -184,8 +99,10 @@ Please run your code through: ```bash # Clone your fork of the repo into the current directory git clone https://github.com/sendgrid/sendgrid-python + # Navigate to the newly cloned directory cd sendgrid-python + # Assign the original repo to a remote called "upstream" git remote add upstream https://github.com/sendgrid/sendgrid-python ``` @@ -197,7 +114,7 @@ Please run your code through: git pull upstream ``` -3. Create a new topic branch (off the main project development branch) to +3. Create a new topic branch (of the main project development branch) to contain your feature, change, or fix: ```bash @@ -217,7 +134,7 @@ Please run your code through: 5. Locally merge (or rebase) the upstream development branch into your topic branch: ```bash - git pull [--rebase] upstream master + git pull [--rebase] upstream main ``` 6. Push your topic branch up to your fork: @@ -227,10 +144,7 @@ Please run your code through: ``` 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) - with a clear title and description against the `master` branch. All tests must be passing before we will review the PR. - -If you have any additional questions, please feel free to [email](mailto:dx@sendgrid.com) us or create an issue in this repo. + with a clear title and description against the `main` branch. All tests must be passing before we will review the PR. - ## Code Reviews -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/). +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/). diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..6f75cd98f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +ARG version=latest +FROM python:$version + +RUN pip install virtualenv + +COPY prism/prism/nginx/cert.crt /usr/local/share/ca-certificates/cert.crt +RUN update-ca-certificates + +WORKDIR /app +COPY . . + +RUN make install diff --git a/FIRST_TIMERS.md b/FIRST_TIMERS.md new file mode 100644 index 000000000..a1d563a75 --- /dev/null +++ b/FIRST_TIMERS.md @@ -0,0 +1,53 @@ +# How To Contribute to Twilio SendGrid Repositories via GitHub +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. + +To make a pull request, follow these steps: + +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. + + + +2. __[Fork](https://help.github.com/fork-a-repo/)__ the [sendgrid-python](https://github.com/sendgrid/sendgrid-python) repository: + + + +3. __Clone__ your fork via the following commands: + +```bash +# Clone your fork of the repo into the current directory +git clone https://github.com/your_username/sendgrid-python +# Navigate to the newly cloned directory +cd sendgrid-python +# Assign the original repo to a remote called "upstream" +git remote add upstream https://github.com/sendgrid/sendgrid-python +``` + +> Don't forget to replace *your_username* in the URL by your real GitHub username. + +4. __Create a new topic branch__ (off the main project development branch) to contain your feature, change, or fix: + +```bash +git checkout -b +``` + +5. __Commit your changes__ in logical chunks. + +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. + +6. __Locally merge (or rebase)__ the upstream development branch into your topic branch: + +```bash +git pull [--rebase] upstream main +``` + +7. __Push__ your topic branch up to your fork: + +```bash +git push origin +``` + +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. + +## 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. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..126ceb1a3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (C) 2025, 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 +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 73b58cfa9..000000000 --- a/LICENSE.txt +++ /dev/null @@ -1,15 +0,0 @@ -Copyright (c) 2012-2017 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 the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of -the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index 94d2153e7..bf5b007ea 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,8 @@ -include README.rst -include LICENSE.txt -include app.json -include Procfile -include requirements.txt -recursive-exclude test * +include README.rst +include LICENSE +include app.json +include Procfile +include requirements.txt +include sendgrid/helpers/inbound/config.yml +recursive-include sendgrid *.py *.txt +recursive-exclude test * diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..96161106d --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +.PHONY: venv install test-install test test-integ test-docker clean nopyc + +venv: clean + @python --version || (echo "Python is not installed, please install Python 2 or Python 3"; exit 1); + pip install virtualenv + virtualenv --python=python venv + +install: venv + . venv/bin/activate; pip install -r test/requirements.txt + . venv/bin/activate; python setup.py install + . venv/bin/activate; pip install -r requirements.txt + +test: install + . venv/bin/activate; coverage run -m unittest discover -s test/unit + +test-integ: test + . venv/bin/activate; coverage run -m unittest discover -s test/integ + +version ?= latest +test-docker: + curl -s https://raw.githubusercontent.com/sendgrid/sendgrid-oai/HEAD/prism/prism.sh -o prism.sh + version=$(version) bash ./prism.sh + +clean: nopyc + rm -rf venv + +nopyc: + find . -name \*.pyc -delete diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..f9448a3b1 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,31 @@ + + +# Fixes # + +A short description of what this PR does. + +### Checklist +- [x] I acknowledge that all my contributions will be made under the project's license +- [ ] I have made a material change to the repo (functionality, testing, spelling, grammar) +- [ ] I have read the [Contribution Guidelines](https://github.com/sendgrid/sendgrid-python/blob/main/CONTRIBUTING.md) and my PR follows them +- [ ] I have titled the PR appropriately +- [ ] I have updated my branch with the main branch +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] 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). \ No newline at end of file diff --git a/README.md b/README.md index f1366eae8..b1b36686f 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,21 @@ -![SendGrid Logo](https://uiux.s3.amazonaws.com/2016-logos/email-logo%402x.png) +![SendGrid Logo](twilio_sendgrid_logo.png) -[![Travis Badge](https://travis-ci.org/sendgrid/sendgrid-python.svg?branch=master)](https://travis-ci.org/sendgrid/sendgrid-python) -[![codecov](https://img.shields.io/codecov/c/github/sendgrid/sendgrid-python/master.svg?style=flat-square&label=Codecov+Coverage)](https://codecov.io/gh/sendgrid/sendgrid-python) +[![BuildStatus](https://github.com/sendgrid/sendgrid-python/actions/workflows/test-and-deploy.yml/badge.svg)](https://github.com/sendgrid/sendgrid-python/actions/workflows/test-and-deploy.yml) [![Docker Badge](https://img.shields.io/docker/automated/sendgrid/sendgrid-python.svg)](https://hub.docker.com/r/sendgrid/sendgrid-python/) -[![Email Notifications Badge](https://dx.sendgrid.com/badge/python)](https://dx.sendgrid.com/newsletter/python) -[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE.txt) +[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Twitter Follow](https://img.shields.io/twitter/follow/sendgrid.svg?style=social&label=Follow)](https://twitter.com/sendgrid) [![GitHub contributors](https://img.shields.io/github/contributors/sendgrid/sendgrid-python.svg)](https://github.com/sendgrid/sendgrid-python/graphs/contributors) - -**NEW:** - -* Subscribe to email [notifications](https://dx.sendgrid.com/newsletter/python) for releases and breaking changes. -* Quickly get started with [Docker](https://github.com/sendgrid/sendgrid-python/tree/master/docker). +[![Open Source Helpers](https://www.codetriage.com/sendgrid/sendgrid-python/badges/users.svg)](https://www.codetriage.com/sendgrid/sendgrid-python) **This library allows you to quickly and easily use the SendGrid Web API v3 via Python.** Version 3.X.X+ of this library provides full support for all 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). -This library represents the beginning of a new path for SendGrid. We want this library to be community driven and 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-python/issues) and [pull requests](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md) or simply upvote or comment on existing issues or pull requests. +This library represents the beginning of a new path for SendGrid. We want this library to be community driven and 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-python/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. +**If you need help using SendGrid, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com).** -We appreciate your continued support, thank you! +Please browse the rest of this README for further detail. # Table of Contents @@ -31,34 +25,44 @@ We appreciate your continued support, thank you! * [Usage](#usage) * [Use Cases](#use-cases) * [Announcements](#announcements) -* [Roadmap](#roadmap) * [How to Contribute](#contribute) * [Troubleshooting](#troubleshooting) * [About](#about) +* [Support](#support) * [License](#license) + # Installation ## Prerequisites -- Python version 2.6, 2.7, 3.4, 3.5 or 3.6 +- Python version 2.7+ - The SendGrid service, starting at the [free level](https://sendgrid.com/free?source=sendgrid-python) ## Setup Environment Variables +### Mac -Update the development environment with your [SENDGRID_API_KEY](https://app.sendgrid.com/settings/api_keys), for example: +Update the development environment with your [SENDGRID_API_KEY](https://app.sendgrid.com/settings/api_keys) (more info [here](https://sendgrid.com/docs/User_Guide/Settings/api_keys.html)), for example: ```bash echo "export SENDGRID_API_KEY='YOUR_API_KEY'" > sendgrid.env echo "sendgrid.env" >> .gitignore source ./sendgrid.env ``` +SendGrid also supports local environment file `.env`. Copy or rename `.env_sample` into `.env` and update [SENDGRID_API_KEY](https://app.sendgrid.com/settings/api_keys) with your key. -Sendgrid also supports local enviroment file `.env`. Copy or rename `.env_sample` into `.env` and update [SENDGRID_API_KEY](https://app.sendgrid.com/settings/api_keys) with your key. +### Windows +Temporarily set the environment variable(accessible only during the current cli session): +```bash +set SENDGRID_API_KEY=YOUR_API_KEY +``` +Permanently set the environment variable(accessible in all subsequent cli sessions): +```bash +setx SENDGRID_API_KEY "YOUR_API_KEY" +``` ## Install Package - ```bash pip install sendgrid ``` @@ -66,13 +70,15 @@ pip install sendgrid ## Dependencies - [Python-HTTP-Client](https://github.com/sendgrid/python-http-client) +- [Cryptography](https://github.com/pyca/cryptography) + # Quick Start ## Hello Email -The following is the minimum needed code to send an email with the [/mail/send Helper](https://github.com/sendgrid/sendgrid-python/tree/master/sendgrid/helpers/mail) ([here](https://github.com/sendgrid/sendgrid-python/blob/master/examples/helpers/mail/mail_example.py#L20) is a full example): +The following is the minimum needed code to send an email with the [/mail/send Helper](sendgrid/helpers/mail) ([here](examples/helpers/mail_example.py#L9) is a full example): ### With Mail Helper Class @@ -81,29 +87,29 @@ import sendgrid import os from sendgrid.helpers.mail import * -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) from_email = Email("test@example.com") -to_email = Email("test@example.com") +to_email = To("test@example.com") subject = "Sending with SendGrid is Fun" content = Content("text/plain", "and easy to do anywhere, even with Python") -mail = Mail(from_email, subject, to_email, content) +mail = Mail(from_email, to_email, subject, content) response = sg.client.mail.send.post(request_body=mail.get()) print(response.status_code) print(response.body) print(response.headers) ``` -The `Mail` constructor creates a [personalization object](https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/personalizations.html) for you. [Here](https://github.com/sendgrid/sendgrid-python/blob/master/examples/helpers/mail/mail_example.py#L16) is an example of how to add it. +The `Mail` constructor creates a [personalization object](https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/personalizations.html) for you. [Here](examples/helpers/mail_example.py#L28) is an example of how to add it. ### Without Mail Helper Class -The following is the minimum needed code to send an email without the /mail/send Helper ([here](https://github.com/sendgrid/sendgrid-python/blob/master/examples/mail/mail.py#L27) is a full example): +The following is the minimum needed code to send an email without the /mail/send Helper ([here](examples/mail/mail.py#L27) is a full example): ```python import sendgrid import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) data = { "personalizations": [ { @@ -137,7 +143,7 @@ print(response.headers) import sendgrid import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) response = sg.client.suppression.bounces.get() print(response.status_code) print(response.body) @@ -150,7 +156,7 @@ print(response.headers) import sendgrid import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) response = sg.client._("suppression/bounces").get() print(response.status_code) print(response.body) @@ -160,62 +166,55 @@ print(response.headers) # Processing Inbound Email -Please see [our helper](https://github.com/sendgrid/sendgrid-python/tree/master/sendgrid/helpers/inbound) for utilizing our Inbound Parse webhook. +Please see [our helper](sendgrid/helpers/inbound) for utilizing our Inbound Parse webhook. # Usage - [SendGrid Documentation](https://sendgrid.com/docs/API_Reference/index.html) -- [Library Usage Documentation](https://github.com/sendgrid/sendgrid-python/tree/master/USAGE.md) -- [Example Code](https://github.com/sendgrid/sendgrid-python/tree/master/examples) +- [Library Usage Documentation](USAGE.md) +- [Example Code](examples) - [How-to: Migration from v2 to v3](https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/how_to_migrate_from_v2_to_v3_mail_send.html) -- [v3 Web API Mail Send Helper](https://github.com/sendgrid/sendgrid-python/tree/master/sendgrid/helpers/mail) - build a request object payload for a v3 /mail/send API call. -- [Processing Inbound Email](https://github.com/sendgrid/sendgrid-python/tree/master/sendgrid/helpers/inbound) +- [v3 Web API Mail Send Helper](sendgrid/helpers/mail) - build a request object payload for a v3 /mail/send API call. +- [Processing Inbound Email](sendgrid/helpers/inbound) # Use Cases -[Examples of common API use cases](https://github.com/sendgrid/sendgrid-python/blob/master/USE_CASES.md), such as how to send an email with a transactional template. +[Examples of common API use cases](use_cases/README.md), such as how to send an email with a transactional template. # Announcements -Join an experienced and passionate team that focuses on making an impact. Opportunities abound to grow the product - and grow your career! Check out our [Data Platform Engineer role](http://grnh.se/wbx1701) - -Please see our announcement regarding [breaking changes](https://github.com/sendgrid/sendgrid-python/issues/217). Your support is appreciated! - -All updates to this library are documented in our [CHANGELOG](https://github.com/sendgrid/sendgrid-python/blob/master/CHANGELOG.md) and [releases](https://github.com/sendgrid/sendgrid-python/releases). You may also subscribe to email [release notifications](https://dx.sendgrid.com/newsletter/java) for releases and breaking changes. - - -# Roadmap - -If you are interested in the future direction of this project, please take a look at our open [issues](https://github.com/sendgrid/sendgrid-python/issues) and [pull requests](https://github.com/sendgrid/sendgrid-python/pulls). We would love to hear your feedback. +All updates to this library are documented in our [CHANGELOG](CHANGELOG.md) and [releases](https://github.com/sendgrid/sendgrid-python/releases). # How to Contribute -We encourage contribution to our libraries (you might even score some nifty swag), please see our [CONTRIBUTING](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md) guide for details. +We encourage contribution to our libraries (you might even score some nifty swag), please see our [CONTRIBUTING](CONTRIBUTING.md) guide for details. Quick links: -- [Feature Request](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md#feature-request) -- [Bug Reports](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md#submit-a-bug-report) -- [Sign the CLA to Create a Pull Request](https://cla.sendgrid.com/sendgrid/sendgrid-python) -- [Improvements to the Codebase](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md#improvements-to-the-codebase) -- [Review Pull Requests](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md#code-reviews) +- [Feature Request](CONTRIBUTING.md#feature-request) +- [Bug Reports](CONTRIBUTING.md#submit-a-bug-report) +- [Improvements to the Codebase](CONTRIBUTING.md#improvements-to-the-codebase) +- [Review Pull Requests](CONTRIBUTING.md#code-reviews) # Troubleshooting -Please see our [troubleshooting guide](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md) for common library issues. +Please see our [troubleshooting guide](TROUBLESHOOTING.md) for common library issues. # About -sendgrid-python is guided and supported by the SendGrid [Developer Experience Team](mailto:dx@sendgrid.com). +sendgrid-python is maintained and funded by Twilio SendGrid, Inc. The names and logos for sendgrid-python are trademarks of Twilio SendGrid, Inc. + + +# Support -sendgrid-python is maintained and funded by SendGrid, Inc. The names and logos for sendgrid-python are trademarks of SendGrid, Inc. +If you need support, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com). # License -[The MIT License (MIT)](LICENSE.txt) +[The MIT License (MIT)](LICENSE) diff --git a/README.rst b/README.rst new file mode 100644 index 000000000..526c4ca40 --- /dev/null +++ b/README.rst @@ -0,0 +1,300 @@ +.. image:: https://github.com/sendgrid/sendgrid-python/raw/HEAD/twilio_sendgrid_logo.png + :target: https://www.sendgrid.com + + + +|Tests Badge| |Python Versions| |PyPI Version| |Docker Badge| |MIT licensed| |Twitter Follow| |GitHub contributors| |Open Source Helpers| + +**This library allows you to quickly and easily use the Twilio SendGrid Web API v3 via Python.** + +**NEW:** + +- Version 6.X release is a BREAKING CHANGE from version 5.X, please see the `release notes`_ for details. +- Send SMS messages with `Twilio`_. + +This library provides full support for all Twilio SendGrid `Web API v3`_ endpoints, including `v3 /mail/send`_. + +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`_ and `pull requests`_ 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! + +Table of Contents +================= + +- `Installation <#installation>`__ +- `Quick Start <#quick-start>`__ +- `Common Use Cases <#use-cases>`__ +- `General Usage <#usage>`__ +- `Processing Inbound Email <#processing-inbound-email>`__ +- `Announcements <#announcements>`__ +- `How to Contribute <#how-to-contribute>`__ +- `Troubleshooting <#troubleshooting>`__ +- `About <#about>`__ +- `License <#license>`__ + +Installation +============ + +Prerequisites +------------- + +- Python version 2.7 and 3.5+ +- For email, you will need a Twilio SendGrid account, starting at the `free level`_ +- For SMS messages, you will need a free `Twilio account`_ + +Setup Environment Variables +--------------------------- + +Mac +~~~ + +Update the development environment with your `SENDGRID_API_KEY`_ (more info `here `__), for example: + +.. code:: bash + + echo "export SENDGRID_API_KEY='YOUR_API_KEY'" > sendgrid.env + echo "sendgrid.env" >> .gitignore + source ./sendgrid.env + +Twilio SendGrid also supports local environment file ``.env``. +Copy or rename ``.env_sample`` into ``.env`` and update `SENDGRID_API_KEY`_ with your key. + +Windows +~~~~~~~ + +Temporarily set the environment variable (accessible only during the current CLI session): + +.. code:: bash + + set SENDGRID_API_KEY=YOUR_API_KEY + +Permanently set the environment variable (accessible in all subsequent CLI sessions): + +.. code:: bash + + setx SENDGRID_API_KEY "YOUR_API_KEY" + +Install Package +--------------- + +.. code:: bash + + pip install sendgrid + +Dependencies +------------ + +- `Python-HTTP-Client`_ +- `Cryptography`_ + +Quick Start +=========== + +Hello Email +----------- + +The following is the minimum needed code to send an email with the `/mail/send Helper`_ +(`here `__ is a full example): + +With Mail Helper Class +~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: python + + import os + from sendgrid import SendGridAPIClient + from sendgrid.helpers.mail import Mail + + message = Mail( + from_email='from_email@example.com', + to_emails='to@example.com', + subject='Sending with Twilio SendGrid is Fun', + html_content='and easy to do anywhere, even with Python') + try: + sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sg.send(message) + print(response.status_code) + print(response.body) + print(response.headers) + except Exception as e: + print(str(e)) + +The ``Mail`` constructor creates a `personalization object`_ for you. +`Here `__ is an example of how to add it. + +Without Mail Helper Class +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following is the minimum needed code to send an email without the /mail/send Helper +(`here `__ is a full example): + +.. code:: python + + import os + from sendgrid import SendGridAPIClient + + message = { + 'personalizations': [ + { + 'to': [ + { + 'email': 'test@example.com' + } + ], + 'subject': 'Sending with Twilio SendGrid is Fun' + } + ], + 'from': { + 'email': 'test@example.com' + }, + 'content': [ + { + 'type': 'text/plain', + 'value': 'and easy to do anywhere, even with Python' + } + ] + } + try: + sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sg.send(message) + print(response.status_code) + print(response.body) + print(response.headers) + except Exception as e: + print(str(e)) + +General v3 Web API Usage (With `Fluent Interface`_) +--------------------------------------------------- + +.. code:: python + + import os + from sendgrid import SendGridAPIClient + + sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sg.client.suppression.bounces.get() + print(response.status_code) + print(response.body) + print(response.headers) + +General v3 Web API Usage (Without `Fluent Interface`_) +------------------------------------------------------ + +.. code:: python + + import os + from sendgrid import SendGridAPIClient + + sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sg.client._('suppression/bounces').get() + print(response.status_code) + print(response.body) + print(response.headers) + +Processing Inbound Email +======================== + +Please see `our helper`_ for utilizing our Inbound Parse webhook. + +Usage +===== + +- `Twilio SendGrid Documentation`_ +- `Library Usage Documentation`_ +- `Example Code`_ +- `How-to: Migration from v2 to v3`_ +- `v3 Web API Mail Send Helper`_ - build a request object payload for a v3 /mail/send API call. +- `Processing Inbound Email`_ + +Use Cases +========= + +`Examples of common API use cases`_, such as how to send an email with a transactional template or add an attachment or send an SMS message. + +Announcements +============= + +All updates to this library are documented in our `CHANGELOG`_ and `releases`_. + +How to Contribute +================= + +We encourage contribution to our libraries (you might even score some nifty swag), please see our `CONTRIBUTING`_ guide for details. + +Quick links: + +- `Feature Request`_ +- `Bug Reports`_ +- `Improvements to the Codebase`_ +- `Review Pull Requests`_ + +Troubleshooting +=============== + +Please see our `troubleshooting guide`_ for common library issues. + +About +===== + +**sendgrid-python** is maintained and funded by Twilio SendGrid, Inc. +The names and logos for **sendgrid-python** are trademarks of Twilio SendGrid, Inc. + +License +======= + +`The MIT License (MIT)`_ + +.. _Twilio: https://github.com/sendgrid/sendgrid-python/blob/HEAD/use_cases/sms.md +.. _release notes: https://github.com/sendgrid/sendgrid-python/releases/tag/v6.0.0 +.. _Web API v3: https://sendgrid.com/docs/API_Reference/Web_API_v3/index.html +.. _v3 /mail/send: https://sendgrid.com/blog/introducing-v3mailsend-sendgrids-new-mail-endpoint +.. _issues: https://github.com/sendgrid/sendgrid-python/issues +.. _pull requests: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md +.. _free level: https://sendgrid.com/free?source=sendgrid-python +.. _Twilio account: https://www.twilio.com/try-twilio?source=sendgrid-python +.. _SENDGRID_API_KEY: https://app.sendgrid.com/settings/api_keys +.. _Python-HTTP-Client: https://github.com/sendgrid/python-http-client +.. _Cryptography: https://github.com/pyca/cryptography +.. _/mail/send Helper: https://github.com/sendgrid/sendgrid-python/tree/HEAD/sendgrid/helpers/mail +.. _personalization object: https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/personalizations.html +.. _Fluent Interface: https://sendgrid.com/blog/using-python-to-implement-a-fluent-interface-to-any-rest-api/ +.. _our helper: https://github.com/sendgrid/sendgrid-python/tree/HEAD/sendgrid/helpers/inbound +.. _Twilio SendGrid Documentation: https://sendgrid.com/docs/API_Reference/index.html +.. _Library Usage Documentation: https://github.com/sendgrid/sendgrid-python/tree/HEAD/USAGE.md +.. _Example Code: https://github.com/sendgrid/sendgrid-python/tree/HEAD/examples +.. _`How-to: Migration from v2 to v3`: https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/how_to_migrate_from_v2_to_v3_mail_send.html +.. _v3 Web API Mail Send Helper: https://github.com/sendgrid/sendgrid-python/tree/HEAD/sendgrid/helpers/mail +.. _Processing Inbound Email: https://github.com/sendgrid/sendgrid-python/tree/HEAD/sendgrid/helpers/inbound +.. _Examples of common API use cases: https://github.com/sendgrid/sendgrid-python/blob/HEAD/use_cases/README.md +.. _breaking changes: https://github.com/sendgrid/sendgrid-python/issues/217 +.. _CHANGELOG: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CHANGELOG.md +.. _releases: https://github.com/sendgrid/sendgrid-python/releases +.. _CONTRIBUTING: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md +.. _Feature Request: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md#feature-request +.. _Bug Reports: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md#submit-a-bug-report +.. _Improvements to the Codebase: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md#improvements-to-the-codebase +.. _Review Pull Requests: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md#code-reviews +.. _troubleshooting guide: https://github.com/sendgrid/sendgrid-python/blob/HEAD/TROUBLESHOOTING.md +.. _The MIT License (MIT): https://github.com/sendgrid/sendgrid-python/blob/HEAD/LICENSE + +.. |Tests Badge| image:: https://github.com/sendgrid/sendgrid-python/actions/workflows/test.yml/badge.svg + :target: https://github.com/sendgrid/sendgrid-python/actions/workflows/test.yml +.. |Python Versions| image:: https://img.shields.io/pypi/pyversions/sendgrid.svg + :target: https://pypi.org/project/sendgrid/ +.. |PyPI Version| image:: https://img.shields.io/pypi/v/sendgrid.svg + :target: https://pypi.org/project/sendgrid/ +.. |Docker Badge| image:: https://img.shields.io/docker/automated/sendgrid/sendgrid-python.svg + :target: https://hub.docker.com/r/sendgrid/sendgrid-python/ +.. |MIT licensed| image:: https://img.shields.io/badge/license-MIT-blue.svg + :target: ./LICENSE +.. |Twitter Follow| image:: https://img.shields.io/twitter/follow/sendgrid.svg?style=social&label=Follow + :target: https://twitter.com/sendgrid +.. |GitHub contributors| image:: https://img.shields.io/github/contributors/sendgrid/sendgrid-python.svg + :target: https://github.com/sendgrid/sendgrid-python/graphs/contributors +.. |Open Source Helpers| image:: https://www.codetriage.com/sendgrid/sendgrid-python/badges/users.svg + :target: https://www.codetriage.com/sendgrid/sendgrid-python diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index a68431ae5..0a7f54c69 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -1,10 +1,12 @@ -If you have a non-library SendGrid issue, please contact our [support team](https://support.sendgrid.com). +If you have an issue logging into your Twilio SendGrid account, please read this [document](https://sendgrid.com/docs/ui/account-and-settings/troubleshooting-login/). For any questions regarding login issues, please contact our [support team](https://support.sendgrid.com). + +If you have a non-library Twilio SendGrid issue, please contact our [support team](https://support.sendgrid.com). If you can't find a solution below, please open an [issue](https://github.com/sendgrid/sendgrid-python/issues). ## Table of Contents -* [Environment Variables and Your SendGrid API Key](#environment) +* [Environment Variables and Your Twilio SendGrid API Key](#environment) * [Error Messages](#error) * [Migrating from v2 to v3](#migrating) * [Continue Using v2](#v2) @@ -13,44 +15,48 @@ If you can't find a solution below, please open an [issue](https://github.com/se * [Version Convention](#versions) * [Viewing the Request Body](#request-body) * [Error Handling](#error-handling) +* [Verifying Event Webhooks](#signed-webhooks) -## Environment Variables and Your SendGrid API Key +## Environment Variables and Your Twilio SendGrid API Key -All of our examples assume you are using [environment variables](https://github.com/sendgrid/sendgrid-python#setup-environment-variables) to hold your SendGrid API key. +All of our examples assume you are using [environment variables](https://github.com/sendgrid/sendgrid-python#setup-environment-variables) to hold your Twilio SendGrid API key. -If you choose to add your SendGrid API key directly (not recommended): +If you choose to add your Twilio SendGrid API key directly (not recommended): -`apikey=os.environ.get('SENDGRID_API_KEY')` +`api_key=os.environ.get('SENDGRID_API_KEY')` becomes -`apikey='SENDGRID_API_KEY'` +`api_key='SENDGRID_API_KEY'` -In the first case SENDGRID_API_KEY is in reference to the name of the environment variable, while the second case references the actual SendGrid API Key. +In the first case, SENDGRID_API_KEY is in reference to the name of the environment variable, while the second case references the actual Twilio SendGrid API Key. ## Error Messages +HTTP exceptions are defined in the [`python_http_client` package](https://github.com/sendgrid/python-http-client/blob/HEAD/python_http_client/exceptions.py). + To read the error message returned by SendGrid's API in Python 2.X: ```python -import urllib2 +from python_http_client.exceptions import HTTPError try: response = sg.client.mail.send.post(request_body=mail.get()) -except urllib2.HTTPError as e: - print e.read() +except HTTPError as e: + print e.to_dict ``` -To read the error message returned by SendGrid's API in Python 3.X: +To read the error message returned by Twilio SendGrid's API in Python 3.X: ```python -import urllib +from python_http_client.exceptions import HTTPError + try: response = sg.client.mail.send.post(request_body=mail.get()) -except urllib.error.HTTPError as e: - print e.read() +except HTTPError as e: + print(e.to_dict) ``` @@ -67,7 +73,7 @@ Using pip: ```bash pip uninstall sendgrid -pip install sendgrid=1.6.22 +pip install sendgrid==1.6.22 ``` Download: @@ -77,7 +83,7 @@ Click the "Clone or download" green button in [GitHub](https://github.com/sendgr ## Testing v3 /mail/send Calls Directly -[Here](https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/curl_examples.html) are some cURL examples for common use cases. +[Here](https://sendgrid.com/docs/for-developers/sending-email/curl-examples) are some cURL examples for common use cases. ## Using the Package Manager @@ -95,7 +101,7 @@ If you are using a [requirements file](https://pip.readthedocs.io/en/1.1/require ## Versioning Convention -We follow the MAJOR.MINOR.PATCH versioning scheme as described by [SemVer.org](http://semver.org). Therefore, we recommend that you always pin (or vendor) the particular version you are working with to your code and never auto-update to the latest version. Especially when there is a MAJOR point release, since that is guaranteed to be a breaking change. Changes are documented in the [CHANGELOG](https://github.com/sendgrid/sendgrid-python/blob/master/CHANGELOG.md) and [releases](https://github.com/sendgrid/sendgrid-python/releases) section. +We follow the MAJOR.MINOR.PATCH versioning scheme as described by [SemVer.org](http://semver.org). Therefore, we recommend that you always pin (or vendor) the particular version you are working with to your code and never auto-update to the latest version. Especially when there is a MAJOR point release, since that is guaranteed to be a breaking change. Changes are documented in the [CHANGELOG](CHANGELOG.md) and [releases](https://github.com/sendgrid/sendgrid-python/releases) section. ## Viewing the Request Body @@ -105,10 +111,21 @@ When debugging or testing, it may be useful to examine the raw request body to c You can do this right before you call `response = sg.client.mail.send.post(request_body=mail.get())` like so: ```python -print mail.get() + print(json.dumps(message.get(), sort_keys=True, indent=4)) ``` # Error Handling -Please review [our use_cases](https://github.com/sendgrid/sendgrid-python/USE_CASES.md) for examples of error handling. +Please review [our use_cases](use_cases/README.md) for examples of error handling. + + +## Signed Webhook Verification + +Twilio SendGrid's Event Webhook will notify a URL via HTTP POST with information about events that occur as your mail is processed. [This](https://docs.sendgrid.com/for-developers/tracking-events/getting-started-event-webhook-security-features) article covers all you need to know to secure the Event Webhook, allowing you to verify that incoming requests originate from Twilio SendGrid. The sendgrid-python library can help you verify these Signed Event Webhooks. + +You can find the usage example [here](examples/helpers/eventwebhook/eventwebhook_example.py) and the tests [here](test/test_eventwebhook.py). +If you are still having trouble getting the validation to work, follow the following instructions: +- Be sure to use the *raw* payload for validation +- Be sure to include a trailing carriage return and newline in your payload +- In case of multi-event webhooks, make sure you include the trailing newline and carriage return after *each* event diff --git a/USAGE.md b/USAGE.md index 1229ee1b1..978b675ab 100644 --- a/USAGE.md +++ b/USAGE.md @@ -2,12 +2,14 @@ This documentation is based on our [OAI specification](https://github.com/sendgr # INITIALIZATION +Make sure your API key has the [required permissions](https://sendgrid.com/docs/ui/account-and-settings/api-keys/). + ```python -import sendgrid +from sendgrid import SendGridAPIClient import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ``` # Table of Contents @@ -30,14 +32,13 @@ sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) * [PARTNER SETTINGS](#partner-settings) * [SCOPES](#scopes) * [SENDERS](#senders) +* [SENDER AUTHENTICATION](#sender-authentication) * [STATS](#stats) * [SUBUSERS](#subusers) * [SUPPRESSION](#suppression) * [TEMPLATES](#templates) * [TRACKING SETTINGS](#tracking-settings) * [USER](#user) -* [WHITELABEL](#whitelabel) - # ACCESS SETTINGS @@ -56,9 +57,9 @@ For more information, please see our [User Guide](http://sendgrid.com/docs/User_ ```python params = {'limit': 1} response = sg.client.access_settings.activity.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Add one or more IPs to the whitelist @@ -88,9 +89,9 @@ data = { ] } response = sg.client.access_settings.whitelist.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve a list of currently whitelisted IPs @@ -105,9 +106,9 @@ For more information, please see our [User Guide](http://sendgrid.com/docs/User_ ```python response = sg.client.access_settings.whitelist.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Remove one or more IPs from the whitelist @@ -131,9 +132,9 @@ data = { ] } response = sg.client.access_settings.whitelist.delete(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve a specific whitelisted IP @@ -151,9 +152,9 @@ For more information, please see our [User Guide](http://sendgrid.com/docs/User_ ```python rule_id = "test_url_param" response = sg.client.access_settings.whitelist._(rule_id).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Remove a specific IP from the whitelist @@ -171,9 +172,9 @@ For more information, please see our [User Guide](http://sendgrid.com/docs/User_ ```python rule_id = "test_url_param" response = sg.client.access_settings.whitelist._(rule_id).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # ALERTS @@ -198,9 +199,9 @@ data = { "type": "stats_notification" } response = sg.client.alerts.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all alerts @@ -217,9 +218,9 @@ For more information about alerts, please see our [User Guide](https://sendgrid. ```python response = sg.client.alerts.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update an alert @@ -240,9 +241,9 @@ data = { } alert_id = "test_url_param" response = sg.client.alerts._(alert_id).patch(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve a specific alert @@ -260,9 +261,9 @@ For more information about alerts, please see our [User Guide](https://sendgrid. ```python alert_id = "test_url_param" response = sg.client.alerts._(alert_id).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete an alert @@ -280,9 +281,9 @@ For more information about alerts, please see our [User Guide](https://sendgrid. ```python alert_id = "test_url_param" response = sg.client.alerts._(alert_id).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # API KEYS @@ -291,7 +292,7 @@ print response.headers **This endpoint allows you to create a new random API Key for the user.** -A JSON request body containing a "name" property is required. If number of maximum keys is reached, HTTP 403 will be returned. +A JSON request body containing a "name" property is required. If the number of maximum keys is reached, HTTP 403 will be returned. There is a limit of 100 API Keys on your account. @@ -313,9 +314,9 @@ data = { ] } response = sg.client.api_keys.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all API Keys belonging to the authenticated user @@ -329,16 +330,16 @@ The API Keys feature allows customers to generate an API Key credential which ca ```python params = {'limit': 1} response = sg.client.api_keys.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update the name & scopes of an API Key **This endpoint allows you to update the name and scopes of a given API key.** A JSON request body with a "name" property is required. -Most provide the list of all the scopes an api key should have. +Most provide the list of all the scopes an API key should have. The API Keys feature allows customers to be able to generate an API Key credential which can be used for authentication with the SendGrid v3 Web API or the [Mail API Endpoint](https://sendgrid.com/docs/API_Reference/Web_API/mail.html). @@ -356,9 +357,9 @@ data = { } api_key_id = "test_url_param" response = sg.client.api_keys._(api_key_id).put(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update API keys @@ -366,7 +367,7 @@ print response.headers A JSON request body with a "name" property is required. -The API Keys feature allows customers to be able to generate an API Key credential which can be used for authentication with the SendGrid v3 Web API or the [Mail API Endpoint](https://sendgrid.com/docs/API_Reference/Web_API/mail.html). +The API Keys feature allows customers to be able to generate an API Key credential which can be used for authentication with the Twilio SendGrid v3 Web API or the [Mail API Endpoint](https://sendgrid.com/docs/API_Reference/Web_API/mail.html). ## URI Parameters @@ -383,9 +384,9 @@ data = { } api_key_id = "test_url_param" response = sg.client.api_keys._(api_key_id).patch(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve an existing API Key @@ -399,17 +400,17 @@ If the API Key ID does not exist an HTTP 404 will be returned. ```python api_key_id = "test_url_param" response = sg.client.api_keys._(api_key_id).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete API keys **This endpoint allows you to revoke an existing API Key.** -Authentications using this API Key will fail after this request is made, with some small propagation delay.If the API Key ID does not exist an HTTP 404 will be returned. +Authentications using this API Key will fail after this request is made, with some small propagation delay. If the API Key ID does not exist an HTTP 404 will be returned. -The API Keys feature allows customers to be able to generate an API Key credential which can be used for authentication with the SendGrid v3 Web API or the [Mail API Endpoint](https://sendgrid.com/docs/API_Reference/Web_API/mail.html). +The API Keys feature allows customers to be able to generate an API Key credential which can be used for authentication with the Twilio SendGrid v3 Web API or the [Mail API Endpoint](https://sendgrid.com/docs/API_Reference/Web_API/mail.html). ## URI Parameters @@ -423,9 +424,9 @@ The API Keys feature allows customers to be able to generate an API Key credenti ```python api_key_id = "test_url_param" response = sg.client.api_keys._(api_key_id).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # ASM @@ -434,9 +435,9 @@ print response.headers **This endpoint allows you to create a new suppression group.** -Suppression groups, or unsubscribe groups, are specific types or categories of email that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. +Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. -The **name** and **description** of the unsubscribe group will be visible by recipients when they are managing their subscriptions. +The **name** and **description** of the unsubscribe group will be visible to recipients when they are managing their subscriptions. Each user can create up to 25 different suppression groups. @@ -450,9 +451,9 @@ data = { "name": "Product Suggestions" } response = sg.client.asm.groups.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve information about multiple suppression groups @@ -470,17 +471,17 @@ Suppression groups, or [unsubscribe groups](https://sendgrid.com/docs/API_Refere ```python params = {'id': 1} response = sg.client.asm.groups.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update a suppression group. **This endpoint allows you to update or change a suppression group.** -Suppression groups, or unsubscribe groups, are specific types or categories of email that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. +Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. -The **name** and **description** of the unsubscribe group will be visible by recipients when they are managing their subscriptions. +The **name** and **description** of the unsubscribe group will be visible to recipients when they are managing their subscriptions. Each user can create up to 25 different suppression groups. @@ -495,17 +496,17 @@ data = { } group_id = "test_url_param" response = sg.client.asm.groups._(group_id).patch(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Get information on a single suppression group. **This endpoint allows you to retrieve a single suppression group.** -Suppression groups, or unsubscribe groups, are specific types or categories of email that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. +Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. -The **name** and **description** of the unsubscribe group will be visible by recipients when they are managing their subscriptions. +The **name** and **description** of the unsubscribe group will be visible to recipients when they are managing their subscriptions. Each user can create up to 25 different suppression groups. @@ -515,9 +516,9 @@ Each user can create up to 25 different suppression groups. ```python group_id = "test_url_param" response = sg.client.asm.groups._(group_id).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a suppression group. @@ -525,9 +526,9 @@ print response.headers You can only delete groups that have not been attached to sent mail in the last 60 days. If a recipient uses the "one-click unsubscribe" option on an email associated with a deleted group, that recipient will be added to the global suppression list. -Suppression groups, or unsubscribe groups, are specific types or categories of email that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. +Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. -The **name** and **description** of the unsubscribe group will be visible by recipients when they are managing their subscriptions. +The **name** and **description** of the unsubscribe group will be visible to recipients when they are managing their subscriptions. Each user can create up to 25 different suppression groups. @@ -537,9 +538,9 @@ Each user can create up to 25 different suppression groups. ```python group_id = "test_url_param" response = sg.client.asm.groups._(group_id).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Add suppressions to a suppression group @@ -561,9 +562,9 @@ data = { } group_id = "test_url_param" response = sg.client.asm.groups._(group_id).suppressions.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all suppressions for a suppression group @@ -577,9 +578,9 @@ Suppressions are recipient email addresses that are added to [unsubscribe groups ```python group_id = "test_url_param" response = sg.client.asm.groups._(group_id).suppressions.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Search for suppressions within a group @@ -602,9 +603,9 @@ data = { } group_id = "test_url_param" response = sg.client.asm.groups._(group_id).suppressions.search.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a suppression from a suppression group @@ -619,9 +620,9 @@ Suppressions are recipient email addresses that are added to [unsubscribe groups group_id = "test_url_param" email = "test_url_param" response = sg.client.asm.groups._(group_id).suppressions._(email).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all suppressions @@ -634,9 +635,9 @@ Suppressions are a list of email addresses that will not receive content sent un ```python response = sg.client.asm.suppressions.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Add recipient addresses to the global suppression group. @@ -655,9 +656,9 @@ data = { ] } response = sg.client.asm.suppressions._("global").post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve a Global Suppression @@ -673,9 +674,9 @@ A global suppression (or global unsubscribe) is an email address of a recipient ```python email = "test_url_param" response = sg.client.asm.suppressions._("global")._(email).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a Global Suppression @@ -689,9 +690,9 @@ A global suppression (or global unsubscribe) is an email address of a recipient ```python email = "test_url_param" response = sg.client.asm.suppressions._("global")._(email).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all suppression groups for an email address @@ -705,9 +706,9 @@ Suppressions are a list of email addresses that will not receive content sent un ```python email = "test_url_param" response = sg.client.asm.suppressions._(email).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # BROWSERS @@ -726,9 +727,9 @@ Advanced Stats provide a more in-depth view of your email statistics and the act ```python params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'browsers': 'test_string', 'limit': 'test_string', 'offset': 'test_string', 'start_date': '2016-01-01'} response = sg.client.browsers.stats.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # CAMPAIGNS @@ -739,7 +740,7 @@ print response.headers Our Marketing Campaigns API lets you create, manage, send, and schedule campaigns. -Note: In order to send or schedule the campaign, you will be required to provide a subject, sender ID, content (we suggest both html and plain text), and at least one list or segment ID. This information is not required when you create a campaign. +Note: In order to send or schedule the campaign, you will be required to provide a subject, sender ID, content (we suggest both HTML and plain text), and at least one list or segment ID. This information is not required when you create a campaign. For more information: @@ -770,9 +771,9 @@ data = { "title": "March Newsletter" } response = sg.client.campaigns.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all Campaigns @@ -792,9 +793,9 @@ For more information: ```python params = {'limit': 10, 'offset': 0} response = sg.client.campaigns.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update a Campaign @@ -819,9 +820,9 @@ data = { } campaign_id = "test_url_param" response = sg.client.campaigns._(campaign_id).patch(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve a single campaign @@ -839,9 +840,9 @@ For more information: ```python campaign_id = "test_url_param" response = sg.client.campaigns._(campaign_id).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a Campaign @@ -859,9 +860,9 @@ For more information: ```python campaign_id = "test_url_param" response = sg.client.campaigns._(campaign_id).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update a Scheduled Campaign @@ -880,9 +881,9 @@ data = { } campaign_id = "test_url_param" response = sg.client.campaigns._(campaign_id).schedules.patch(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Schedule a Campaign @@ -901,9 +902,9 @@ data = { } campaign_id = "test_url_param" response = sg.client.campaigns._(campaign_id).schedules.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## View Scheduled Time of a Campaign @@ -919,9 +920,9 @@ For more information: ```python campaign_id = "test_url_param" response = sg.client.campaigns._(campaign_id).schedules.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Unschedule a Scheduled Campaign @@ -940,9 +941,9 @@ For more information: ```python campaign_id = "test_url_param" response = sg.client.campaigns._(campaign_id).schedules.delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Send a Campaign @@ -960,9 +961,9 @@ For more information: ```python campaign_id = "test_url_param" response = sg.client.campaigns._(campaign_id).schedules.now.post() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Send a Test Campaign @@ -983,9 +984,9 @@ data = { } campaign_id = "test_url_param" response = sg.client.campaigns._(campaign_id).schedules.test.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # CATEGORIES @@ -1002,9 +1003,9 @@ Categories can help organize your email analytics by enabling you to tag emails ```python params = {'category': 'test_string', 'limit': 1, 'offset': 1} response = sg.client.categories.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve Email Statistics for Categories @@ -1020,9 +1021,9 @@ Categories allow you to group your emails together according to broad topics tha ```python params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01', 'categories': 'test_string'} response = sg.client.categories.stats.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve sums of email stats for each category [Needs: Stats object defined, has category ID?] @@ -1038,9 +1039,9 @@ Categories allow you to group your emails together according to broad topics tha ```python params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} response = sg.client.categories.stats.sums.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # CLIENTS @@ -1059,9 +1060,9 @@ Advanced Stats provide a more in-depth view of your email statistics and the act ```python params = {'aggregated_by': 'day', 'start_date': '2016-01-01', 'end_date': '2016-04-01'} response = sg.client.clients.stats.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve stats by a specific client type. @@ -1084,9 +1085,9 @@ Advanced Stats provide a more in-depth view of your email statistics and the act params = {'aggregated_by': 'day', 'start_date': '2016-01-01', 'end_date': '2016-04-01'} client_type = "test_url_param" response = sg.client.clients._(client_type).stats.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # CONTACTDB @@ -1095,7 +1096,7 @@ print response.headers **This endpoint allows you to create a custom field.** -The contactdb is a database of your contacts for [SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). +The contactdb is a database of your contacts for [Twilio SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). ### POST /contactdb/custom_fields @@ -1106,30 +1107,30 @@ data = { "type": "text" } response = sg.client.contactdb.custom_fields.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all custom fields **This endpoint allows you to retrieve all custom fields.** -The contactdb is a database of your contacts for [SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). +The contactdb is a database of your contacts for [Twilio SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). ### GET /contactdb/custom_fields ```python response = sg.client.contactdb.custom_fields.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve a Custom Field **This endpoint allows you to retrieve a custom field by ID.** -The contactdb is a database of your contacts for [SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). +The contactdb is a database of your contacts for [Twilio SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). ### GET /contactdb/custom_fields/{custom_field_id} @@ -1137,15 +1138,15 @@ The contactdb is a database of your contacts for [SendGrid Marketing Campaigns]( ```python custom_field_id = "test_url_param" response = sg.client.contactdb.custom_fields._(custom_field_id).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a Custom Field **This endpoint allows you to delete a custom field by ID.** -The contactdb is a database of your contacts for [SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). +The contactdb is a database of your contacts for [Twilio SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). ### DELETE /contactdb/custom_fields/{custom_field_id} @@ -1153,9 +1154,9 @@ The contactdb is a database of your contacts for [SendGrid Marketing Campaigns]( ```python custom_field_id = "test_url_param" response = sg.client.contactdb.custom_fields._(custom_field_id).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Create a List @@ -1171,9 +1172,9 @@ data = { "name": "your list name" } response = sg.client.contactdb.lists.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all lists @@ -1186,9 +1187,9 @@ The Contacts API helps you manage your [Marketing Campaigns](https://sendgrid.co ```python response = sg.client.contactdb.lists.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete Multiple lists @@ -1207,9 +1208,9 @@ data = [ 4 ] response = sg.client.contactdb.lists.delete(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update a List @@ -1228,9 +1229,9 @@ data = { params = {'list_id': 1} list_id = "test_url_param" response = sg.client.contactdb.lists._(list_id).patch(request_body=data, query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve a single list @@ -1245,9 +1246,9 @@ The Contacts API helps you manage your [Marketing Campaigns](https://sendgrid.co params = {'list_id': 1} list_id = "test_url_param" response = sg.client.contactdb.lists._(list_id).get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a List @@ -1262,9 +1263,9 @@ The Contacts API helps you manage your [Marketing Campaigns](https://sendgrid.co params = {'delete_contacts': 'true'} list_id = "test_url_param" response = sg.client.contactdb.lists._(list_id).delete(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Add Multiple Recipients to a List @@ -1284,9 +1285,9 @@ data = [ ] list_id = "test_url_param" response = sg.client.contactdb.lists._(list_id).recipients.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all recipients on a List @@ -1301,9 +1302,9 @@ The Contacts API helps you manage your [Marketing Campaigns](https://sendgrid.co params = {'page': 1, 'page_size': 1, 'list_id': 1} list_id = "test_url_param" response = sg.client.contactdb.lists._(list_id).recipients.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Add a Single Recipient to a List @@ -1318,9 +1319,9 @@ The Contacts API helps you manage your [Marketing Campaigns](https://sendgrid.co list_id = "test_url_param" recipient_id = "test_url_param" response = sg.client.contactdb.lists._(list_id).recipients._(recipient_id).post() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a Single Recipient from a Single List @@ -1336,9 +1337,9 @@ params = {'recipient_id': 1, 'list_id': 1} list_id = "test_url_param" recipient_id = "test_url_param" response = sg.client.contactdb.lists._(list_id).recipients._(recipient_id).delete(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update Recipient @@ -1348,7 +1349,7 @@ The body of an API call to this endpoint must include an array of one or more re It is of note that you can add custom field data as parameters on recipient objects. We have provided an example using some of the default custom fields SendGrid provides. -The contactdb is a database of your contacts for [SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). +The contactdb is a database of your contacts for [Twilio SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). ### PATCH /contactdb/recipients @@ -1362,9 +1363,9 @@ data = [ } ] response = sg.client.contactdb.recipients.patch(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Add recipients @@ -1393,9 +1394,9 @@ data = [ } ] response = sg.client.contactdb.recipients.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve recipients @@ -1412,17 +1413,17 @@ The Contacts API helps you manage your [Marketing Campaigns](https://sendgrid.co ```python params = {'page': 1, 'page_size': 1} response = sg.client.contactdb.recipients.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete Recipient -**This endpoint allows you to deletes one or more recipients.** +**This endpoint allows you to delete one or more recipients.** The body of an API call to this endpoint must include an array of recipient IDs of the recipients you want to delete. -The contactdb is a database of your contacts for [SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). +The contactdb is a database of your contacts for [Twilio SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). ### DELETE /contactdb/recipients @@ -1433,9 +1434,9 @@ data = [ "recipient_id2" ] response = sg.client.contactdb.recipients.delete(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve the count of billable recipients @@ -1450,24 +1451,24 @@ The Contacts API helps you manage your [Marketing Campaigns](https://sendgrid.co ```python response = sg.client.contactdb.recipients.billable_count.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve a Count of Recipients **This endpoint allows you to retrieve the total number of Marketing Campaigns recipients.** -The contactdb is a database of your contacts for [SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). +The contactdb is a database of your contacts for [Twilio SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). ### GET /contactdb/recipients/count ```python response = sg.client.contactdb.recipients.count.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve recipients matching search criteria @@ -1476,13 +1477,13 @@ print response.headers field_name: * is a variable that is substituted for your actual custom field name from your recipient. -* Text fields must be url-encoded. Date fields are searchable only by unix timestamp (e.g. 2/2/2015 becomes 1422835200) +* Text fields must be url-encoded. Date fields are searchable only by UNIX timestamp (e.g. 2/2/2015 becomes 1422835200) * If field_name is a 'reserved' date field, such as created_at or updated_at, the system will internally convert your epoch time to a date range encompassing the entire day. For example, an epoch time of 1422835600 converts to Mon, 02 Feb 2015 00:06:40 GMT, but internally the system will search from Mon, 02 Feb 2015 00:00:00 GMT through Mon, 02 Feb 2015 23:59:59 GMT. -The contactdb is a database of your contacts for [SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). +The contactdb is a database of your contacts for [Twilio SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). ### GET /contactdb/recipients/search @@ -1490,9 +1491,9 @@ The contactdb is a database of your contacts for [SendGrid Marketing Campaigns]( ```python params = {'{field_name}': 'test_string'} response = sg.client.contactdb.recipients.search.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve a single recipient @@ -1506,9 +1507,9 @@ The Contacts API helps you manage your [Marketing Campaigns](https://sendgrid.co ```python recipient_id = "test_url_param" response = sg.client.contactdb.recipients._(recipient_id).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a Recipient @@ -1522,9 +1523,9 @@ The Contacts API helps you manage your [Marketing Campaigns](https://sendgrid.co ```python recipient_id = "test_url_param" response = sg.client.contactdb.recipients._(recipient_id).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve the lists that a recipient is on @@ -1540,24 +1541,24 @@ The Contacts API helps you manage your [Marketing Campaigns](https://sendgrid.co ```python recipient_id = "test_url_param" response = sg.client.contactdb.recipients._(recipient_id).lists.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve reserved fields **This endpoint allows you to list all fields that are reserved and can't be used for custom field names.** -The contactdb is a database of your contacts for [SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). +The contactdb is a database of your contacts for [Twilio SendGrid Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html). ### GET /contactdb/reserved_fields ```python response = sg.client.contactdb.reserved_fields.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Create a Segment @@ -1614,9 +1615,9 @@ data = { "name": "Last Name Miller" } response = sg.client.contactdb.segments.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all segments @@ -1631,9 +1632,9 @@ For more information about segments in Marketing Campaigns, please see our [User ```python response = sg.client.contactdb.segments.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update a segment @@ -1662,9 +1663,9 @@ data = { params = {'segment_id': 'test_string'} segment_id = "test_url_param" response = sg.client.contactdb.segments._(segment_id).patch(request_body=data, query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve a segment @@ -1681,13 +1682,13 @@ For more information about segments in Marketing Campaigns, please see our [User params = {'segment_id': 1} segment_id = "test_url_param" response = sg.client.contactdb.segments._(segment_id).get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a segment -**This endpoint allows you to delete a segment from your recipients database.** +**This endpoint allows you to delete a segment from your recipient's database.** You also have the option to delete all the contacts from your Marketing Campaigns recipient database who were in this segment. @@ -1702,9 +1703,9 @@ For more information about segments in Marketing Campaigns, please see our [User params = {'delete_contacts': 'true'} segment_id = "test_url_param" response = sg.client.contactdb.segments._(segment_id).delete(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve recipients on a segment @@ -1721,9 +1722,9 @@ For more information about segments in Marketing Campaigns, please see our [User params = {'page': 1, 'page_size': 1} segment_id = "test_url_param" response = sg.client.contactdb.segments._(segment_id).recipients.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # DEVICES @@ -1737,10 +1738,10 @@ print response.headers ## Available Device Types | **Device** | **Description** | **Example** | |---|---|---| -| Desktop | Email software on desktop computer. | I.E., Outlook, Sparrow, or Apple Mail. | +| Desktop | Email software on a desktop computer. | I.E., Outlook, Sparrow, or Apple Mail. | | Webmail | A web-based email client. | I.E., Yahoo, Google, AOL, or Outlook.com. | -| Phone | A smart phone. | iPhone, Android, Blackberry, etc. -| Tablet | A tablet computer. | iPad, android based tablet, etc. | +| Phone | A smartphone. | iPhone, Android, Blackberry, etc. +| Tablet | A tablet computer. | iPad, Android-based tablet, etc. | | Other | An unrecognized device. | Advanced Stats provide a more in-depth view of your email statistics and the actions taken by your recipients. You can segment these statistics by geographic location, device type, client type, browser, and mailbox provider. For more information about statistics, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/index.html). @@ -1751,9 +1752,9 @@ Advanced Stats provide a more in-depth view of your email statistics and the act ```python params = {'aggregated_by': 'day', 'limit': 1, 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} response = sg.client.devices.stats.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # GEO @@ -1772,9 +1773,9 @@ Advanced Stats provide a more in-depth view of your email statistics and the act ```python params = {'end_date': '2016-04-01', 'country': 'US', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01'} response = sg.client.geo.stats.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # IPS @@ -1783,7 +1784,7 @@ print response.headers **This endpoint allows you to retrieve a list of all assigned and unassigned IPs.** -Response includes warm up status, pools, assigned subusers, and whitelabel info. The start_date field corresponds to when warmup started for that IP. +The response includes warm-up status, pools, assigned subusers, and authentication info. The start_date field corresponds to when warmup started for that IP. A single IP address or a range of IP addresses may be dedicated to an account in order to send email for multiple domains. The reputation of this IP is based on the aggregate performance of all the senders who use it. @@ -1793,9 +1794,9 @@ A single IP address or a range of IP addresses may be dedicated to an account in ```python params = {'subuser': 'test_string', 'ip': 'test_string', 'limit': 1, 'exclude_whitelabels': 'true', 'offset': 1} response = sg.client.ips.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all assigned IPs @@ -1808,9 +1809,9 @@ A single IP address or a range of IP addresses may be dedicated to an account in ```python response = sg.client.ips.assigned.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Create an IP pool. @@ -1818,9 +1819,9 @@ print response.headers **Each user can create up to 10 different IP pools.** -IP Pools allow you to group your dedicated SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. +IP Pools allow you to group your dedicated Twilio SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. -IP pools can only be used with whitelabeled IP addresses. +IP pools can only be used with authenticated IP addresses. If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. @@ -1832,9 +1833,9 @@ data = { "name": "marketing" } response = sg.client.ips.pools.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all IP pools. @@ -1842,7 +1843,7 @@ print response.headers IP Pools allow you to group your dedicated SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. -IP pools can only be used with whitelabeled IP addresses. +IP pools can only be used with authenticated IP addresses. If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. @@ -1851,17 +1852,17 @@ If an IP pool is NOT specified for an email, it will use any IP available, inclu ```python response = sg.client.ips.pools.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update an IP pools name. **This endpoint allows you to update the name of an IP pool.** -IP Pools allow you to group your dedicated SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. +IP Pools allow you to group your dedicated Twilio SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. -IP pools can only be used with whitelabeled IP addresses. +IP pools can only be used with authenticated IP addresses. If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. @@ -1874,17 +1875,17 @@ data = { } pool_name = "test_url_param" response = sg.client.ips.pools._(pool_name).put(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all IPs in a specified pool. **This endpoint allows you to list all of the IP addresses that are in a specific IP pool.** -IP Pools allow you to group your dedicated SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. +IP Pools allow you to group your dedicated Twilio SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. -IP pools can only be used with whitelabeled IP addresses. +IP pools can only be used with authenticated IP addresses. If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. @@ -1894,17 +1895,17 @@ If an IP pool is NOT specified for an email, it will use any IP available, inclu ```python pool_name = "test_url_param" response = sg.client.ips.pools._(pool_name).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete an IP pool. **This endpoint allows you to delete an IP pool.** -IP Pools allow you to group your dedicated SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. +IP Pools allow you to group your dedicated Twilio SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. -IP pools can only be used with whitelabeled IP addresses. +IP pools can only be used with authenticated IP addresses. If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. @@ -1914,9 +1915,9 @@ If an IP pool is NOT specified for an email, it will use any IP available, inclu ```python pool_name = "test_url_param" response = sg.client.ips.pools._(pool_name).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Add an IP address to a pool @@ -1935,9 +1936,9 @@ data = { } pool_name = "test_url_param" response = sg.client.ips.pools._(pool_name).ips.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Remove an IP address from a pool. @@ -1954,15 +1955,15 @@ A single IP address or a range of IP addresses may be dedicated to an account in pool_name = "test_url_param" ip = "test_url_param" response = sg.client.ips.pools._(pool_name).ips._(ip).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Add an IP to warmup **This endpoint allows you to enter an IP address into warmup mode.** -SendGrid can automatically warm up dedicated IP addresses by limiting the amount of mail that can be sent through them per hour, with the limit determined by how long the IP address has been in warmup. See the [warmup schedule](https://sendgrid.com/docs/API_Reference/Web_API_v3/IP_Management/ip_warmup_schedule.html) for more details on how SendGrid limits your email traffic for IPs in warmup. +Twilio SendGrid can automatically warm up dedicated IP addresses by limiting the amount of mail that can be sent through them per hour, with the limit determined by how long the IP address has been in warmup. See the [warmup schedule](https://sendgrid.com/docs/API_Reference/Web_API_v3/IP_Management/ip_warmup_schedule.html) for more details on how Twilio SendGrid limits your email traffic for IPs in warmup. For more general information about warming up IPs, please see our [Classroom](https://sendgrid.com/docs/Classroom/Deliver/Delivery_Introduction/warming_up_ips.html). @@ -1974,15 +1975,15 @@ data = { "ip": "0.0.0.0" } response = sg.client.ips.warmup.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all IPs currently in warmup **This endpoint allows you to retrieve all of your IP addresses that are currently warming up.** -SendGrid can automatically warm up dedicated IP addresses by limiting the amount of mail that can be sent through them per hour, with the limit determined by how long the IP address has been in warmup. See the [warmup schedule](https://sendgrid.com/docs/API_Reference/Web_API_v3/IP_Management/ip_warmup_schedule.html) for more details on how SendGrid limits your email traffic for IPs in warmup. +Twilio SendGrid can automatically warm up dedicated IP addresses by limiting the amount of mail that can be sent through them per hour, with the limit determined by how long the IP address has been in warmup. See the [warmup schedule](https://sendgrid.com/docs/API_Reference/Web_API_v3/IP_Management/ip_warmup_schedule.html) for more details on how Twilio SendGrid limits your email traffic for IPs in warmup. For more general information about warming up IPs, please see our [Classroom](https://sendgrid.com/docs/Classroom/Deliver/Delivery_Introduction/warming_up_ips.html). @@ -1991,15 +1992,15 @@ For more general information about warming up IPs, please see our [Classroom](ht ```python response = sg.client.ips.warmup.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve warmup status for a specific IP address **This endpoint allows you to retrieve the warmup status for a specific IP address.** -SendGrid can automatically warm up dedicated IP addresses by limiting the amount of mail that can be sent through them per hour, with the limit determined by how long the IP address has been in warmup. See the [warmup schedule](https://sendgrid.com/docs/API_Reference/Web_API_v3/IP_Management/ip_warmup_schedule.html) for more details on how SendGrid limits your email traffic for IPs in warmup. +Twilio SendGrid can automatically warm up dedicated IP addresses by limiting the amount of mail that can be sent through them per hour, with the limit determined by how long the IP address has been in warmup. See the [warmup schedule](https://sendgrid.com/docs/API_Reference/Web_API_v3/IP_Management/ip_warmup_schedule.html) for more details on how Twilio SendGrid limits your email traffic for IPs in warmup. For more general information about warming up IPs, please see our [Classroom](https://sendgrid.com/docs/Classroom/Deliver/Delivery_Introduction/warming_up_ips.html). @@ -2009,15 +2010,15 @@ For more general information about warming up IPs, please see our [Classroom](ht ```python ip_address = "test_url_param" response = sg.client.ips.warmup._(ip_address).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Remove an IP from warmup **This endpoint allows you to remove an IP address from warmup mode.** -SendGrid can automatically warm up dedicated IP addresses by limiting the amount of mail that can be sent through them per hour, with the limit determined by how long the IP address has been in warmup. See the [warmup schedule](https://sendgrid.com/docs/API_Reference/Web_API_v3/IP_Management/ip_warmup_schedule.html) for more details on how SendGrid limits your email traffic for IPs in warmup. +Twilio SendGrid can automatically warm up dedicated IP addresses by limiting the amount of mail that can be sent through them per hour, with the limit determined by how long the IP address has been in warmup. See the [warmup schedule](https://sendgrid.com/docs/API_Reference/Web_API_v3/IP_Management/ip_warmup_schedule.html) for more details on how Twilio SendGrid limits your email traffic for IPs in warmup. For more general information about warming up IPs, please see our [Classroom](https://sendgrid.com/docs/Classroom/Deliver/Delivery_Introduction/warming_up_ips.html). @@ -2027,9 +2028,9 @@ For more general information about warming up IPs, please see our [Classroom](ht ```python ip_address = "test_url_param" response = sg.client.ips.warmup._(ip_address).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all IP pools an IP address belongs to @@ -2045,9 +2046,9 @@ A single IP address or a range of IP addresses may be dedicated to an account in ```python ip_address = "test_url_param" response = sg.client.ips._(ip_address).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # MAIL @@ -2067,9 +2068,9 @@ More Information: ```python response = sg.client.mail.batch.post() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Validate batch ID @@ -2087,13 +2088,13 @@ More Information: ```python batch_id = "test_url_param" response = sg.client.mail.batch._(batch_id).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## v3 Mail Send -This endpoint allows you to send email over SendGrids v3 Web API, the most recent version of our API. If you are looking for documentation about the v2 Mail Send endpoint, please see our [v2 API Reference](https://sendgrid.com/docs/API_Reference/Web_API/mail.html). +This endpoint allows you to send email over Twilio SendGrid's v3 Web API, the most recent version of our API. If you are looking for documentation about the v2 Mail Send endpoint, please see our [v2 API Reference](https://sendgrid.com/docs/API_Reference/Web_API/mail.html). * Top level parameters are referred to as "global". * Individual fields within the personalizations array will override any other global, or message level, parameters that are defined outside of personalizations. @@ -2104,7 +2105,7 @@ For more detailed information about how to use the v3 Mail Send endpoint, please ### POST /mail/send -This endpoint has a helper, check it out [here](https://github.com/sendgrid/sendgrid-python/blob/master/sendgrid/helpers/mail/README.md). +This endpoint has a helper, check it out [here](sendgrid/helpers/mail/README.md). ```python data = { @@ -2158,8 +2159,8 @@ data = { }, "footer": { "enable": True, - "html": "

Thanks
The SendGrid Team

", - "text": "Thanks,/n The SendGrid Team" + "html": "

Thanks
The Twilio SendGrid Team

", + "text": "Thanks,/n The Twilio SendGrid Team" }, "sandbox_mode": { "enable": False @@ -2246,9 +2247,9 @@ data = { } } response = sg.client.mail.send.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # MAIL SETTINGS @@ -2257,7 +2258,7 @@ print response.headers **This endpoint allows you to retrieve a list of all mail settings.** -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### GET /mail_settings @@ -2265,9 +2266,9 @@ Mail settings allow you to tell SendGrid specific things to do to every email th ```python params = {'limit': 1, 'offset': 1} response = sg.client.mail_settings.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update address whitelist mail settings @@ -2275,7 +2276,7 @@ print response.headers The address whitelist setting whitelists a specified email address or domain for which mail should never be suppressed. For example, you own the domain example.com, and one or more of your recipients use email@example.com addresses, by placing example.com in the address whitelist setting, all bounces, blocks, and unsubscribes logged for that domain will be ignored and sent as if under normal sending conditions. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### PATCH /mail_settings/address_whitelist @@ -2289,9 +2290,9 @@ data = { ] } response = sg.client.mail_settings.address_whitelist.patch(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve address whitelist mail settings @@ -2299,24 +2300,24 @@ print response.headers The address whitelist setting whitelists a specified email address or domain for which mail should never be suppressed. For example, you own the domain example.com, and one or more of your recipients use email@example.com addresses, by placing example.com in the address whitelist setting, all bounces, blocks, and unsubscribes logged for that domain will be ignored and sent as if under normal sending conditions. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### GET /mail_settings/address_whitelist ```python response = sg.client.mail_settings.address_whitelist.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update BCC mail settings **This endpoint allows you to update your current BCC mail settings.** -When the BCC mail setting is enabled, SendGrid will automatically send a blind carbon copy (BCC) to an address for every email sent without adding that address to the header. Please note that only one email address may be entered in this field, if you wish to distribute BCCs to multiple addresses you will need to create a distribution group or use forwarding rules. +When the BCC mail setting is enabled, Twilio SendGrid will automatically send a blind carbon copy (BCC) to an address for every email sent without adding that address to the header. Please note that only one email address may be entered in this field, if you wish to distribute BCCs to multiple addresses you will need to create a distribution group or use forwarding rules. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### PATCH /mail_settings/bcc @@ -2327,34 +2328,34 @@ data = { "enabled": False } response = sg.client.mail_settings.bcc.patch(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all BCC mail settings **This endpoint allows you to retrieve your current BCC mail settings.** -When the BCC mail setting is enabled, SendGrid will automatically send a blind carbon copy (BCC) to an address for every email sent without adding that address to the header. Please note that only one email address may be entered in this field, if you wish to distribute BCCs to multiple addresses you will need to create a distribution group or use forwarding rules. +When the BCC mail setting is enabled, Twilio SendGrid will automatically send a blind carbon copy (BCC) to an address for every email sent without adding that address to the header. Please note that only one email address may be entered in this field, if you wish to distribute BCCs to multiple addresses you will need to create a distribution group or use forwarding rules. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### GET /mail_settings/bcc ```python response = sg.client.mail_settings.bcc.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update bounce purge mail settings **This endpoint allows you to update your current bounce purge settings.** -This setting allows you to set a schedule for SendGrid to automatically delete contacts from your soft and hard bounce suppression lists. +This setting allows you to set a schedule for Twilio SendGrid to automatically delete contacts from your soft and hard bounce suppression lists. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### PATCH /mail_settings/bounce_purge @@ -2366,26 +2367,26 @@ data = { "soft_bounces": 5 } response = sg.client.mail_settings.bounce_purge.patch(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve bounce purge mail settings **This endpoint allows you to retrieve your current bounce purge settings.** -This setting allows you to set a schedule for SendGrid to automatically delete contacts from your soft and hard bounce suppression lists. +This setting allows you to set a schedule for Twilio SendGrid to automatically delete contacts from your soft and hard bounce suppression lists. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### GET /mail_settings/bounce_purge ```python response = sg.client.mail_settings.bounce_purge.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update footer mail settings @@ -2393,7 +2394,7 @@ print response.headers The footer setting will insert a custom footer at the bottom of the text and HTML bodies. Use the embedded HTML editor and plain text entry fields to create the content of the footers to be inserted into your emails. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### PATCH /mail_settings/footer @@ -2405,9 +2406,9 @@ data = { "plain_content": "..." } response = sg.client.mail_settings.footer.patch(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve footer mail settings @@ -2415,16 +2416,16 @@ print response.headers The footer setting will insert a custom footer at the bottom of the text and HTML bodies. Use the embedded HTML editor and plain text entry fields to create the content of the footers to be inserted into your emails. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### GET /mail_settings/footer ```python response = sg.client.mail_settings.footer.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update forward bounce mail settings @@ -2432,7 +2433,7 @@ print response.headers Activating this setting allows you to specify an email address to which bounce reports are forwarded. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### PATCH /mail_settings/forward_bounce @@ -2443,9 +2444,9 @@ data = { "enabled": True } response = sg.client.mail_settings.forward_bounce.patch(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve forward bounce mail settings @@ -2453,16 +2454,16 @@ print response.headers Activating this setting allows you to specify an email address to which bounce reports are forwarded. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### GET /mail_settings/forward_bounce ```python response = sg.client.mail_settings.forward_bounce.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update forward spam mail settings @@ -2470,7 +2471,7 @@ print response.headers Enabling the forward spam setting allows you to specify an email address to which spam reports will be forwarded. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### PATCH /mail_settings/forward_spam @@ -2481,9 +2482,9 @@ data = { "enabled": False } response = sg.client.mail_settings.forward_spam.patch(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve forward spam mail settings @@ -2491,16 +2492,16 @@ print response.headers Enabling the forward spam setting allows you to specify an email address to which spam reports will be forwarded. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### GET /mail_settings/forward_spam ```python response = sg.client.mail_settings.forward_spam.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update plain content mail settings @@ -2508,7 +2509,7 @@ print response.headers The plain content setting will automatically convert any plain text emails that you send to HTML before sending. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### PATCH /mail_settings/plain_content @@ -2518,9 +2519,9 @@ data = { "enabled": False } response = sg.client.mail_settings.plain_content.patch(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve plain content mail settings @@ -2528,16 +2529,16 @@ print response.headers The plain content setting will automatically convert any plain text emails that you send to HTML before sending. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### GET /mail_settings/plain_content ```python response = sg.client.mail_settings.plain_content.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update spam check mail settings @@ -2545,7 +2546,7 @@ print response.headers The spam checker filter notifies you when emails are detected that exceed a predefined spam threshold. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### PATCH /mail_settings/spam_check @@ -2557,9 +2558,9 @@ data = { "url": "url" } response = sg.client.mail_settings.spam_check.patch(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve spam check mail settings @@ -2567,16 +2568,16 @@ print response.headers The spam checker filter notifies you when emails are detected that exceed a predefined spam threshold. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### GET /mail_settings/spam_check ```python response = sg.client.mail_settings.spam_check.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update template mail settings @@ -2586,7 +2587,7 @@ This setting refers to our original email templates. We currently support more f The legacy email template setting wraps an HTML template around your email content. This can be useful for sending out marketing email and/or other HTML formatted messages. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### PATCH /mail_settings/template @@ -2597,9 +2598,9 @@ data = { "html_content": "<% body %>" } response = sg.client.mail_settings.template.patch(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve legacy template mail settings @@ -2609,16 +2610,16 @@ This setting refers to our original email templates. We currently support more f The legacy email template setting wraps an HTML template around your email content. This can be useful for sending out marketing email and/or other HTML formatted messages. -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). +Mail settings allow you to tell Twilio SendGrid specific things to do to every email that you send to your recipients over Twilio SendGrid's [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). ### GET /mail_settings/template ```python response = sg.client.mail_settings.template.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # MAILBOX PROVIDERS @@ -2637,9 +2638,9 @@ Advanced Stats provide a more in-depth view of your email statistics and the act ```python params = {'end_date': '2016-04-01', 'mailbox_providers': 'test_string', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01'} response = sg.client.mailbox_providers.stats.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # PARTNER SETTINGS @@ -2648,7 +2649,7 @@ print response.headers **This endpoint allows you to retrieve a list of all partner settings that you can enable.** -Our partner settings allow you to integrate your SendGrid account with our partners to increase your SendGrid experience and functionality. For more information about our partners, and how you can begin integrating with them, please visit our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/partners.html). +Our partner settings allow you to integrate your Twilio SendGrid account with our partners to increase your Twilio SendGrid experience and functionality. For more information about our partners, and how you can begin integrating with them, please visit our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/partners.html). ### GET /partner_settings @@ -2656,17 +2657,17 @@ Our partner settings allow you to integrate your SendGrid account with our partn ```python params = {'limit': 1, 'offset': 1} response = sg.client.partner_settings.get(query_params=params) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Updates New Relic partner settings. **This endpoint allows you to update or change your New Relic partner settings.** -Our partner settings allow you to integrate your SendGrid account with our partners to increase your SendGrid experience and functionality. For more information about our partners, and how you can begin integrating with them, please visit our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/partners.html). +Our partner settings allow you to integrate your Twilio SendGrid account with our partners to increase your Twilio SendGrid experience and functionality. For more information about our partners, and how you can begin integrating with them, please visit our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/partners.html). -By integrating with New Relic, you can send your SendGrid email statistics to your New Relic Dashboard. If you enable this setting, your stats will be sent to New Relic every 5 minutes. You will need your New Relic License Key to enable this setting. For more information, please see our [Classroom](https://sendgrid.com/docs/Classroom/Track/Collecting_Data/new_relic.html). +By integrating with New Relic, you can send your Twilio SendGrid email statistics to your New Relic Dashboard. If you enable this setting, your stats will be sent to New Relic every 5 minutes. You will need your New Relic License Key to enable this setting. For more information, please see our [Classroom](https://sendgrid.com/docs/Classroom/Track/Collecting_Data/new_relic.html). ### PATCH /partner_settings/new_relic @@ -2678,26 +2679,26 @@ data = { "license_key": "" } response = sg.client.partner_settings.new_relic.patch(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Returns all New Relic partner settings. **This endpoint allows you to retrieve your current New Relic partner settings.** -Our partner settings allow you to integrate your SendGrid account with our partners to increase your SendGrid experience and functionality. For more information about our partners, and how you can begin integrating with them, please visit our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/partners.html). +Our partner settings allow you to integrate your Twilio SendGrid account with our partners to increase your Twilio SendGrid experience and functionality. For more information about our partners, and how you can begin integrating with them, please visit our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/partners.html). -By integrating with New Relic, you can send your SendGrid email statistics to your New Relic Dashboard. If you enable this setting, your stats will be sent to New Relic every 5 minutes. You will need your New Relic License Key to enable this setting. For more information, please see our [Classroom](https://sendgrid.com/docs/Classroom/Track/Collecting_Data/new_relic.html). +By integrating with New Relic, you can send your Twilio SendGrid email statistics to your New Relic Dashboard. If you enable this setting, your stats will be sent to New Relic every 5 minutes. You will need your New Relic License Key to enable this setting. For more information, please see our [Classroom](https://sendgrid.com/docs/Classroom/Track/Collecting_Data/new_relic.html). ### GET /partner_settings/new_relic ```python response = sg.client.partner_settings.new_relic.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # SCOPES @@ -2706,16 +2707,16 @@ print response.headers **This endpoint returns a list of all scopes that this user has access to.** -API Keys can be used to authenticate the use of [SendGrids v3 Web API](https://sendgrid.com/docs/API_Reference/Web_API_v3/index.html), or the [Mail API Endpoint](https://sendgrid.com/docs/API_Reference/Web_API/mail.html). API Keys may be assigned certain permissions, or scopes, that limit which API endpoints they are able to access. For a more detailed explanation of how you can use API Key permissions, please visit our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/api_keys.html#-API-Key-Permissions) or [Classroom](https://sendgrid.com/docs/Classroom/Basics/API/api_key_permissions.html). +API Keys can be used to authenticate the use of [Twilio SendGrid's v3 Web API](https://sendgrid.com/docs/API_Reference/Web_API_v3/index.html), or the [Mail API Endpoint](https://sendgrid.com/docs/API_Reference/Web_API/mail.html). API Keys may be assigned certain permissions, or scopes, that limit which API endpoints they are able to access. For a more detailed explanation of how you can use API Key permissions, please visit our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/api_keys.html#-API-Key-Permissions) or [Classroom](https://sendgrid.com/docs/Classroom/Basics/API/api_key_permissions.html). ### GET /scopes ```python response = sg.client.scopes.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # SENDERS @@ -2726,7 +2727,7 @@ print response.headers *You may create up to 100 unique sender identities.* -Sender Identities are required to be verified before use. If your domain has been whitelabeled it will auto verify on creation. Otherwise an email will be sent to the `from.email`. +Sender Identities are required to be verified before use. If your domain has been authenticated it will auto verify on creation. Otherwise, an email will be sent to the `from.email`. ### POST /senders @@ -2750,30 +2751,30 @@ data = { "zip": "80202" } response = sg.client.senders.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Get all Sender Identities **This endpoint allows you to retrieve a list of all sender identities that have been created for your account.** -Sender Identities are required to be verified before use. If your domain has been whitelabeled it will auto verify on creation. Otherwise an email will be sent to the `from.email`. +Sender Identities are required to be verified before use. If your domain has been authenticated it will auto verify on creation. Otherwise an email will be sent to the `from.email`. ### GET /senders ```python response = sg.client.senders.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update a Sender Identity **This endpoint allows you to update a sender identity.** -Updates to `from.email` require re-verification. If your domain has been whitelabeled it will auto verify on creation. Otherwise an email will be sent to the `from.email`. +Updates to `from.email` require re-verification. If your domain has been authenticated it will auto verify on creation. Otherwise an email will be sent to the `from.email`. Partial updates are allowed, but fields that are marked as "required" in the POST (create) endpoint must not be nil if that field is included in the PATCH request. @@ -2800,15 +2801,15 @@ data = { } sender_id = "test_url_param" response = sg.client.senders._(sender_id).patch(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## View a Sender Identity **This endpoint allows you to retrieve a specific sender identity.** -Sender Identities are required to be verified before use. If your domain has been whitelabeled it will auto verify on creation. Otherwise an email will be sent to the `from.email`. +Sender Identities are required to be verified before use. If your domain has been authenticated it will auto verify on creation. Otherwise an email will be sent to the `from.email`. ### GET /senders/{sender_id} @@ -2816,15 +2817,15 @@ Sender Identities are required to be verified before use. If your domain has bee ```python sender_id = "test_url_param" response = sg.client.senders._(sender_id).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a Sender Identity **This endpoint allows you to delete one of your sender identities.** -Sender Identities are required to be verified before use. If your domain has been whitelabeled it will auto verify on creation. Otherwise an email will be sent to the `from.email`. +Sender Identities are required to be verified before use. If your domain has been authenticated it will auto verify on creation. Otherwise an email will be sent to the `from.email`. ### DELETE /senders/{sender_id} @@ -2832,15 +2833,15 @@ Sender Identities are required to be verified before use. If your domain has bee ```python sender_id = "test_url_param" response = sg.client.senders._(sender_id).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Resend Sender Identity Verification **This endpoint allows you to resend a sender identity verification email.** -Sender Identities are required to be verified before use. If your domain has been whitelabeled it will auto verify on creation. Otherwise an email will be sent to the `from.email`. +Sender Identities are required to be verified before use. If your domain has been authenticated it will auto verify on creation. Otherwise an email will be sent to the `from.email`. ### POST /senders/{sender_id}/resend_verification @@ -2848,2189 +2849,2193 @@ Sender Identities are required to be verified before use. If your domain has bee ```python sender_id = "test_url_param" response = sg.client.senders._(sender_id).resend_verification.post() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` - -# STATS - -## Retrieve global email statistics - -**This endpoint allows you to retrieve all of your global email statistics between a given date range.** - -Parent accounts will see aggregated stats for their account and all subuser accounts. Subuser accounts will only see their own stats. - -### GET /stats + +# SENDER AUTHENTICATION -```python -params = {'aggregated_by': 'day', 'limit': 1, 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} -response = sg.client.stats.get(query_params=params) -print response.status_code -print response.body -print response.headers -``` - -# SUBUSERS +## Create an authenticated domain. -## Create Subuser +**This endpoint allows you to create a domain authentication for one of your domains.** -This endpoint allows you to retrieve a list of all of your subusers. You can choose to retrieve specific subusers as well as limit the results that come back from the API. +If you are creating a domain authentication that you would like a subuser to use, you have two options: +1. Use the "username" parameter. This allows you to create a domain authentication on behalf of your subuser. This means the subuser is able to see and modify the created authentication. +2. Use the Association workflow (see Associate Domain section). This allows you to assign a domain authentication created by the parent to a subuser. This means the subuser will default to the assigned domain authentication, but will not be able to see or modify that authentication. However, if the subuser creates their own domain authentication it will overwrite the assigned domain authentication. -For more information about Subusers: +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) -* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -### POST /subusers +### POST /whitelabel/domains ```python data = { - "email": "John@example.com", + "automatic_security": False, + "custom_spf": True, + "default": True, + "domain": "example.com", "ips": [ - "1.1.1.1", - "2.2.2.2" + "192.168.1.1", + "192.168.1.2" ], - "password": "johns_password", - "username": "John@example.com" + "subdomain": "news", + "username": "john@example.com" } -response = sg.client.subusers.post(request_body=data) -print response.status_code -print response.body -print response.headers +response = sg.client.whitelabel.domains.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## List all Subusers - -This endpoint allows you to retrieve a list of all of your subusers. You can choose to retrieve specific subusers as well as limit the results that come back from the API. - -For more information about Subusers: - -* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) -* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) - -### GET /subusers +## List all domain authentications. +**This endpoint allows you to retrieve a list of all domain authentications you have created.** -```python -params = {'username': 'test_string', 'limit': 1, 'offset': 1} -response = sg.client.subusers.get(query_params=params) -print response.status_code -print response.body -print response.headers -``` -## Retrieve Subuser Reputations +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -Subuser sender reputations give a good idea how well a sender is doing with regards to how recipients and recipient servers react to the mail that is being received. When a bounce, spam report, or other negative action happens on a sent email, it will effect your sender rating. +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -This endpoint allows you to request the reputations for your subusers. -### GET /subusers/reputations +### GET /whitelabel/domains ```python -params = {'usernames': 'test_string'} -response = sg.client.subusers.reputations.get(query_params=params) -print response.status_code -print response.body -print response.headers +params = {'username': 'test_string', 'domain': 'test_string', 'exclude_subusers': 'true', 'limit': 1, 'offset': 1} +response = sg.client.whitelabel.domains.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve email statistics for your subusers. +## Get the default domain authentication. -**This endpoint allows you to retrieve the email statistics for the given subusers.** +**This endpoint allows you to retrieve the default default authentication for a domain.** -You may retrieve statistics for up to 10 different subusers by including an additional _subusers_ parameter for each additional subuser. +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| domain | string |The domain to find a default domain authentication for. | -### GET /subusers/stats +### GET /whitelabel/domains/default ```python -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01', 'subusers': 'test_string'} -response = sg.client.subusers.stats.get(query_params=params) -print response.status_code -print response.body -print response.headers +response = sg.client.whitelabel.domains.default.get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve monthly stats for all subusers +## List the domain authentication associated with the given user. -**This endpoint allows you to retrieve the monthly email statistics for all subusers over the given date range.** +**This endpoint allows you to retrieve all of the domain authentications that have been assigned to a specific subuser.** -While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats for your subusers. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -When using the `sort_by_metric` to sort your stats by a specific metric, you can not sort by the following metrics: -`bounce_drops`, `deferred`, `invalid_emails`, `processed`, `spam_report_drops`, `spam_reports`, or `unsubscribe_drops`. +Domain authentications can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's authenticated domains. To associate a domain authentication with a subuser, the parent account must first create the default authentication and validate it. The parent may then associate the default authentication via the subuser management tools. -For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -### GET /subusers/stats/monthly +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| username | string | Username of the subuser to find associated domain authentications for. | + +### GET /whitelabel/domains/subuser ```python -params = {'subuser': 'test_string', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'date': 'test_string', 'sort_by_direction': 'asc'} -response = sg.client.subusers.stats.monthly.get(query_params=params) -print response.status_code -print response.body -print response.headers +response = sg.client.whitelabel.domains.subuser.get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve the totals for each email statistic metric for all subusers. +## Disassociate a domain authentication from a given user. -**This endpoint allows you to retrieve the total sums of each email statistic metric for all subusers over the given date range.** +**This endpoint allows you to disassociate a specific default authentication from a subuser.** +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. +Domain authentications can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's authenticated domains. To associate a domain authentication with a subuser, the parent account must first create the default authentication and validate it. The parent may then associate the default authentication via the subuser management tools. -For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -### GET /subusers/stats/sums +## URI Parameters +| URI Parameter | Type | Required? | Description | +|---|---|---|---| +| username | string | required | Username for the subuser to find associated domain authentications for. | + +### DELETE /whitelabel/domains/subuser ```python -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} -response = sg.client.subusers.stats.sums.get(query_params=params) -print response.status_code -print response.body -print response.headers +response = sg.client.whitelabel.domains.subuser.delete() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Enable/disable a subuser +## Update a domain authentication. -This endpoint allows you to enable or disable a subuser. +**This endpoint allows you to update the settings for a domain authentication.** -For more information about Subusers: +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) -* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -### PATCH /subusers/{subuser_name} +### PATCH /whitelabel/domains/{domain_id} ```python data = { - "disabled": False + "custom_spf": True, + "default": False } -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).patch(request_body=data) -print response.status_code -print response.body -print response.headers +domain_id = "test_url_param" +response = sg.client.whitelabel.domains._(domain_id).patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Delete a subuser +## Retrieve a domain authentication. -This endpoint allows you to delete a subuser. This is a permanent action, once you delete a subuser it cannot be retrieved. +**This endpoint allows you to retrieve a specific domain authentication.** -For more information about Subusers: +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) -* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -### DELETE /subusers/{subuser_name} + +### GET /whitelabel/domains/{domain_id} ```python -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).delete() -print response.status_code -print response.body -print response.headers +domain_id = "test_url_param" +response = sg.client.whitelabel.domains._(domain_id).get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Update IPs assigned to a subuser +## Delete a domain authentication. -Each subuser should be assigned to an IP address, from which all of this subuser's mail will be sent. Often, this is the same IP as the parent account, but each subuser can have their own, or multiple, IP addresses as well. +**This endpoint allows you to delete a domain authentication.** -More information: +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -* [How to request more IPs](https://sendgrid.com/docs/Classroom/Basics/Account/adding_an_additional_dedicated_ip_to_your_account.html) -* [IPs can be whitelabeled](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/ips.html) +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -### PUT /subusers/{subuser_name}/ips +### DELETE /whitelabel/domains/{domain_id} ```python -data = [ - "127.0.0.1" -] -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).ips.put(request_body=data) -print response.status_code -print response.body -print response.headers +domain_id = "test_url_param" +response = sg.client.whitelabel.domains._(domain_id).delete() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Update Monitor Settings for a subuser +## Associate a domain authentication with a given user. -Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. +**This endpoint allows you to associate a specific domain authentication with a subuser.** -### PUT /subusers/{subuser_name}/monitor +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Domain authentications can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's authenticated domains. To associate a domain authentication with a subuser, the parent account must first create the default authentication and validate it. The parent may then associate the default authentication via the subuser management tools. -```python -data = { - "email": "example@example.com", - "frequency": 500 -} -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.put(request_body=data) -print response.status_code -print response.body -print response.headers -``` -## Create monitor settings +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| domain_id | integer | ID of the domain authentication to associate with the subuser. | -### POST /subusers/{subuser_name}/monitor +### POST /whitelabel/domains/{domain_id}/subuser ```python data = { - "email": "example@example.com", - "frequency": 50000 + "username": "jane@example.com" } -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.post(request_body=data) -print response.status_code -print response.body -print response.headers +domain_id = "test_url_param" +response = sg.client.whitelabel.domains._(domain_id).subuser.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve monitor settings for a subuser - -Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. +## Add an IP to a domain authentication. -### GET /subusers/{subuser_name}/monitor +**This endpoint allows you to add an IP address to a domain authentication.** +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -```python -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.get() -print response.status_code -print response.body -print response.headers -``` -## Delete monitor settings +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| id | integer | ID of the domain to which you are adding an IP | -### DELETE /subusers/{subuser_name}/monitor +### POST /whitelabel/domains/{id}/ips ```python -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.delete() -print response.status_code -print response.body -print response.headers +data = { + "ip": "192.168.0.1" +} +id = "test_url_param" +response = sg.client.whitelabel.domains._(id).ips.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve the monthly email statistics for a single subuser +## Remove an IP from a domain authentication. -**This endpoint allows you to retrieve the monthly email statistics for a specific subuser.** +**This endpoint allows you to remove a domain's IP address from that domain's authentication.** -While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats for your subusers. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -When using the `sort_by_metric` to sort your stats by a specific metric, you can not sort by the following metrics: -`bounce_drops`, `deferred`, `invalid_emails`, `processed`, `spam_report_drops`, `spam_reports`, or `unsubscribe_drops`. +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| id | integer | ID of the domain authentication to delete the IP from. | +| ip | string | IP to remove from the domain authentication. | -### GET /subusers/{subuser_name}/stats/monthly +### DELETE /whitelabel/domains/{id}/ips/{ip} ```python -params = {'date': 'test_string', 'sort_by_direction': 'asc', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1} -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).stats.monthly.get(query_params=params) -print response.status_code -print response.body -print response.headers +id = "test_url_param" +ip = "test_url_param" +response = sg.client.whitelabel.domains._(id).ips._(ip).delete() +print(response.status_code) +print(response.body) +print(response.headers) ``` - -# SUPPRESSION +## Validate a domain authentication. -## Retrieve all blocks +**This endpoint allows you to validate a domain authentication. If it fails, it will return an error message describing why the default authentication could not be validated.** -**This endpoint allows you to retrieve a list of all email addresses that are currently on your blocks list.** +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| id | integer |ID of the domain authentication to validate. | -### GET /suppression/blocks +### POST /whitelabel/domains/{id}/validate ```python -params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} -response = sg.client.suppression.blocks.get(query_params=params) -print response.status_code -print response.body -print response.headers +id = "test_url_param" +response = sg.client.whitelabel.domains._(id).validate.post() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Delete blocks -**This endpoint allows you to delete all email addresses on your blocks list.** +## Create reverse DNS record -There are two options for deleting blocked emails: +**This endpoint allows you to create a reverse DNS record.** -1. You can delete all blocked emails by setting `delete_all` to true in the request body. -2. You can delete some blocked emails by specifying the email addresses in an array in the request body. +When creating a reverse DNS record, you should use the same subdomain that you used when you created a domain authentication. -[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. +Reverse DNS consists of a subdomain and domain that will be used to generate a record for a given IP. Once Twilio SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-reverse-dns/). -### DELETE /suppression/blocks +### POST /whitelabel/ips ```python data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] + "domain": "example.com", + "ip": "192.168.1.1", + "subdomain": "email" } -response = sg.client.suppression.blocks.delete(request_body=data) -print response.status_code -print response.body -print response.headers +response = sg.client.whitelabel.ips.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve a specific block -**This endpoint allows you to retrieve a specific email address from your blocks list.** +## Retrieve all reverse DNS records -[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. +**This endpoint allows you to retrieve all of the reverse DNS records that have been created by this account.** -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). +You may include a search key by using the "ip" parameter. This enables you to perform a prefix search for a given IP segment (e.g. "192."). -### GET /suppression/blocks/{email} +Reverse DNS consists of a subdomain and domain that will be used to generate a record for a given IP. Once Twilio SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-reverse-dns/). -```python -email = "test_url_param" -response = sg.client.suppression.blocks._(email).get() -print response.status_code -print response.body -print response.headers +### GET /whitelabel/ips + + +```python +params = {'ip': 'test_string', 'limit': 1, 'offset': 1} +response = sg.client.whitelabel.ips.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Delete a specific block +## Retrieve a reverse DNS record -**This endpoint allows you to delete a specific email address from your blocks list.** +**This endpoint allows you to retrieve a reverse DNS record.** -[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. +A reverse DNS record consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once Twilio SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). -### DELETE /suppression/blocks/{email} +### GET /whitelabel/ips/{id} ```python -email = "test_url_param" -response = sg.client.suppression.blocks._(email).delete() -print response.status_code -print response.body -print response.headers +id = "test_url_param" +response = sg.client.whitelabel.ips._(id).get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve all bounces - -**This endpoint allows you to retrieve all of your bounces.** +## Delete a reverse DNS record -Bounces are messages that are returned to the server that sent it. +**This endpoint allows you to delete a reverse DNS record.** -For more information see: +A reverse DNS record consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once Twilio SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. -* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information -* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). -### GET /suppression/bounces +### DELETE /whitelabel/ips/{id} ```python -params = {'start_time': 1, 'end_time': 1} -response = sg.client.suppression.bounces.get(query_params=params) -print response.status_code -print response.body -print response.headers +id = "test_url_param" +response = sg.client.whitelabel.ips._(id).delete() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Delete bounces - -**This endpoint allows you to delete all of your bounces. You can also use this endpoint to remove a specific email address from your bounce list.** - -Bounces are messages that are returned to the server that sent it. +## Validate a reverse DNS record -For more information see: +**This endpoint allows you to validate a reverse DNS record.** -* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information -* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) -* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) +A reverse DNS record consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once Twilio SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. -Note: the `delete_all` and `emails` parameters should be used independently of each other as they have different purposes. +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). -### DELETE /suppression/bounces +### POST /whitelabel/ips/{id}/validate ```python -data = { - "delete_all": True, - "emails": [ - "example@example.com", - "example2@example.com" - ] -} -response = sg.client.suppression.bounces.delete(request_body=data) -print response.status_code -print response.body -print response.headers +id = "test_url_param" +response = sg.client.whitelabel.ips._(id).validate.post() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve a Bounce +## Create a Link Branding -**This endpoint allows you to retrieve a specific bounce for a given email address.** - -Bounces are messages that are returned to the server that sent it. +**This endpoint allows you to create a new link branding.** -For more information see: +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information -* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) -* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### GET /suppression/bounces/{email} +### POST /whitelabel/links ```python -email = "test_url_param" -response = sg.client.suppression.bounces._(email).get() -print response.status_code -print response.body -print response.headers +data = { + "default": True, + "domain": "example.com", + "subdomain": "mail" +} +params = {'limit': 1, 'offset': 1} +response = sg.client.whitelabel.links.post(request_body=data, query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Delete a bounce +## Retrieve all link brandings -**This endpoint allows you to remove an email address from your bounce list.** - -Bounces are messages that are returned to the server that sent it. This endpoint allows you to delete a single email address from your bounce list. +**This endpoint allows you to retrieve all link brandings.** -For more information see: +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information -* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) -* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### DELETE /suppression/bounces/{email} +### GET /whitelabel/links ```python -params = {'email_address': 'example@example.com'} -email = "test_url_param" -response = sg.client.suppression.bounces._(email).delete(query_params=params) -print response.status_code -print response.body -print response.headers +params = {'limit': 1} +response = sg.client.whitelabel.links.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve all invalid emails +## Retrieve a Default Link Branding -**This endpoint allows you to retrieve a list of all invalid email addresses.** +**This endpoint allows you to retrieve the default link branding.** -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. +Default link branding is the actual link branding to be used when sending messages. If there are multiple link brandings, the default is determined by the following order: +
    +
  • Validated link brandings marked as "default"
  • +
  • Legacy link brands (migrated from the whitelabel wizard)
  • +
  • Default Twilio SendGrid link branding (i.e. 100.ct.sendgrid.net)
  • +
-Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### GET /suppression/invalid_emails +### GET /whitelabel/links/default ```python -params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} -response = sg.client.suppression.invalid_emails.get(query_params=params) -print response.status_code -print response.body -print response.headers +params = {'domain': 'test_string'} +response = sg.client.whitelabel.links.default.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Delete invalid emails - -**This endpoint allows you to remove email addresses from your invalid email address list.** +## Retrieve Associated Link Branding -There are two options for deleting invalid email addresses: - -1) You can delete all invalid email addresses by setting `delete_all` to true in the request body. -2) You can delete some invalid email addresses by specifying certain addresses in an array in the request body. +**This endpoint allows you to retrieve the associated link branding for a subuser.** -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. +Link whitelables can be associated with subusers from the parent account. This functionality allows +subusers to send mail using their parent's link brandings. To associate a link branding, the parent account +must first create a domain authentication and validate it. The parent may then associate that branded link with a subuser via the API or the Subuser Management page in the user interface. -Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### DELETE /suppression/invalid_emails +### GET /whitelabel/links/subuser ```python -data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] -} -response = sg.client.suppression.invalid_emails.delete(request_body=data) -print response.status_code -print response.body -print response.headers +params = {'username': 'test_string'} +response = sg.client.whitelabel.links.subuser.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve a specific invalid email +## Disassociate a Link Branding -**This endpoint allows you to retrieve a specific invalid email addresses.** +**This endpoint allows you to disassociate a link branding from a subuser.** -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. +Link whitelables can be associated with subusers from the parent account. This functionality allows +subusers to send mail using their parent's link brandings. To associate a link branding, the parent account +must first create a domain authentication and validate it. The parent may then associate that branded link with a subuser via the API or the Subuser Management page in the user interface. -Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### GET /suppression/invalid_emails/{email} +### DELETE /whitelabel/links/subuser ```python -email = "test_url_param" -response = sg.client.suppression.invalid_emails._(email).get() -print response.status_code -print response.body -print response.headers +params = {'username': 'test_string'} +response = sg.client.whitelabel.links.subuser.delete(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Delete a specific invalid email - -**This endpoint allows you to remove a specific email address from the invalid email address list.** +## Update a Link Branding -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. +**This endpoint allows you to update a specific link branding. You can use this endpoint to change a link branding's default status.** -Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### DELETE /suppression/invalid_emails/{email} +### PATCH /whitelabel/links/{id} ```python -email = "test_url_param" -response = sg.client.suppression.invalid_emails._(email).delete() -print response.status_code -print response.body -print response.headers +data = { + "default": True +} +id = "test_url_param" +response = sg.client.whitelabel.links._(id).patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve a specific spam report +## Retrieve a Link Branding -**This endpoint allows you to retrieve a specific spam report.** +**This endpoint allows you to retrieve a specific link branding.** -[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to SendGrid. +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### GET /suppression/spam_report/{email} +### GET /whitelabel/links/{id} ```python -email = "test_url_param" -response = sg.client.suppression.spam_report._(email).get() -print response.status_code -print response.body -print response.headers +id = "test_url_param" +response = sg.client.whitelabel.links._(id).get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Delete a specific spam report +## Delete a Link Branding -**This endpoint allows you to delete a specific spam report.** +**This endpoint allows you to delete a link branding.** -[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to SendGrid. +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### DELETE /suppression/spam_report/{email} +### DELETE /whitelabel/links/{id} ```python -email = "test_url_param" -response = sg.client.suppression.spam_report._(email).delete() -print response.status_code -print response.body -print response.headers +id = "test_url_param" +response = sg.client.whitelabel.links._(id).delete() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve all spam reports +## Validate a Link Branding -**This endpoint allows you to retrieve all spam reports.** +**This endpoint allows you to validate a link branding.** -[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to SendGrid. +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### GET /suppression/spam_reports +### POST /whitelabel/links/{id}/validate ```python -params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} -response = sg.client.suppression.spam_reports.get(query_params=params) -print response.status_code -print response.body -print response.headers +id = "test_url_param" +response = sg.client.whitelabel.links._(id).validate.post() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Delete spam reports - -**This endpoint allows you to delete your spam reports.** +## Associate a Link Branding -There are two options for deleting spam reports: +**This endpoint allows you to associate a link branding with a subuser account.** -1) You can delete all spam reports by setting "delete_all" to true in the request body. -2) You can delete some spam reports by specifying the email addresses in an array in the request body. +Link whitelables can be associated with subusers from the parent account. This functionality allows +subusers to send mail using their parent's link brandings. To associate a link branding, the parent account +must first create a domain authentication and validate it. The parent may then associate that branded link with a subuser via the API or the Subuser Management page in the user interface. -[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to SendGrid. +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### DELETE /suppression/spam_reports +### POST /whitelabel/links/{link_id}/subuser ```python data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] + "username": "jane@example.com" } -response = sg.client.suppression.spam_reports.delete(request_body=data) -print response.status_code -print response.body -print response.headers +link_id = "test_url_param" +response = sg.client.whitelabel.links._(link_id).subuser.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve all global suppressions -**This endpoint allows you to retrieve a list of all email address that are globally suppressed.** + +# STATS -A global suppression (or global unsubscribe) is an email address of a recipient who does not want to receive any of your messages. A globally suppressed recipient will be removed from any email you send. For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/global_unsubscribes.html). +## Retrieve global email statistics -### GET /suppression/unsubscribes +**This endpoint allows you to retrieve all of your global email statistics between a given date range.** + +Parent accounts will see aggregated stats for their account and all subuser accounts. Subuser accounts will only see their own stats. + +### GET /stats ```python -params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} -response = sg.client.suppression.unsubscribes.get(query_params=params) -print response.status_code -print response.body -print response.headers +params = {'aggregated_by': 'day', 'limit': 1, 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} +response = sg.client.stats.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` - -# TEMPLATES + +# SUBUSERS -## Create a transactional template. +## Create Subuser -**This endpoint allows you to create a transactional template.** +This endpoint allows you to retrieve a list of all of your subusers. You can choose to retrieve specific subusers as well as limit the results that come back from the API. -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. +For more information about Subusers: -Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) +* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) -### POST /templates +### POST /subusers ```python data = { - "name": "example_name" + "email": "John@example.com", + "ips": [ + "1.1.1.1", + "2.2.2.2" + ], + "password": "johns_password", + "username": "John@example.com" } -response = sg.client.templates.post(request_body=data) -print response.status_code -print response.body -print response.headers +response = sg.client.subusers.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve all transactional templates. +## List all Subusers -**This endpoint allows you to retrieve all transactional templates.** +This endpoint allows you to retrieve a list of all of your subusers. You can choose to retrieve specific subusers as well as limit the results that come back from the API. -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. +For more information about Subusers: -Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) +* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) -### GET /templates +### GET /subusers ```python -response = sg.client.templates.get() -print response.status_code -print response.body -print response.headers +params = {'username': 'test_string', 'limit': 1, 'offset': 1} +response = sg.client.subusers.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Edit a transactional template. - -**This endpoint allows you to edit a transactional template.** - -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. +## Retrieve Subuser Reputations -Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +Subuser sender reputations give a good idea how well a sender is doing with regards to how recipients and recipient servers react to the mail that is being received. When a bounce, spam report, or other negative action happens on a sent email, it will effect your sender rating. +This endpoint allows you to request the reputations for your subusers. -### PATCH /templates/{template_id} +### GET /subusers/reputations ```python -data = { - "name": "new_example_name" -} -template_id = "test_url_param" -response = sg.client.templates._(template_id).patch(request_body=data) -print response.status_code -print response.body -print response.headers +params = {'usernames': 'test_string'} +response = sg.client.subusers.reputations.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve a single transactional template. +## Retrieve email statistics for your subusers. -**This endpoint allows you to retrieve a single transactional template.** +**This endpoint allows you to retrieve the email statistics for the given subusers.** -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. +You may retrieve statistics for up to 10 different subusers by including an additional _subusers_ parameter for each additional subuser. -Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. +For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). -### GET /templates/{template_id} +### GET /subusers/stats ```python -template_id = "test_url_param" -response = sg.client.templates._(template_id).get() -print response.status_code -print response.body -print response.headers +params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01', 'subusers': 'test_string'} +response = sg.client.subusers.stats.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Delete a template. +## Retrieve monthly stats for all subusers -**This endpoint allows you to delete a transactional template.** +**This endpoint allows you to retrieve the monthly email statistics for all subusers over the given date range.** -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. +While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats for your subusers. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. -Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +When using the `sort_by_metric` to sort your stats by a specific metric, you can not sort by the following metrics: +`bounce_drops`, `deferred`, `invalid_emails`, `processed`, `spam_report_drops`, `spam_reports`, or `unsubscribe_drops`. +For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). -### DELETE /templates/{template_id} +### GET /subusers/stats/monthly ```python -template_id = "test_url_param" -response = sg.client.templates._(template_id).delete() -print response.status_code -print response.body -print response.headers +params = {'subuser': 'test_string', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'date': 'test_string', 'sort_by_direction': 'asc'} +response = sg.client.subusers.stats.monthly.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Create a new transactional template version. +## Retrieve the totals for each email statistic metric for all subusers. -**This endpoint allows you to create a new version of a template.** +**This endpoint allows you to retrieve the total sums of each email statistic metric for all subusers over the given date range.** -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. -For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. +For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). -### POST /templates/{template_id}/versions +### GET /subusers/stats/sums ```python -data = { - "active": 1, - "html_content": "<%body%>", - "name": "example_version_name", - "plain_content": "<%body%>", - "subject": "<%subject%>", - "template_id": "ddb96bbc-9b92-425e-8979-99464621b543" -} -template_id = "test_url_param" -response = sg.client.templates._(template_id).versions.post(request_body=data) -print response.status_code -print response.body -print response.headers +params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} +response = sg.client.subusers.stats.sums.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Edit a transactional template version. - -**This endpoint allows you to edit a version of one of your transactional templates.** +## Enable/disable a subuser -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +This endpoint allows you to enable or disable a subuser. -For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +For more information about Subusers: -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| template_id | string | The ID of the original template | -| version_id | string | The ID of the template version | +* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) +* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) -### PATCH /templates/{template_id}/versions/{version_id} +### PATCH /subusers/{subuser_name} ```python data = { - "active": 1, - "html_content": "<%body%>", - "name": "updated_example_name", - "plain_content": "<%body%>", - "subject": "<%subject%>" + "disabled": False } -template_id = "test_url_param" -version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).patch(request_body=data) -print response.status_code -print response.body -print response.headers +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve a specific transactional template version. - -**This endpoint allows you to retrieve a specific version of a template.** +## Delete a subuser -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +This endpoint allows you to delete a subuser. This is a permanent action, once you delete a subuser it cannot be retrieved. -For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +For more information about Subusers: -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| template_id | string | The ID of the original template | -| version_id | string | The ID of the template version | +* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) +* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) -### GET /templates/{template_id}/versions/{version_id} +### DELETE /subusers/{subuser_name} ```python -template_id = "test_url_param" -version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).get() -print response.status_code -print response.body -print response.headers +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).delete() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Delete a transactional template version. - -**This endpoint allows you to delete one of your transactional template versions.** +## Update IPs assigned to a subuser -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +Each subuser should be assigned to an IP address, from which all of this subuser's mail will be sent. Often, this is the same IP as the parent account, but each subuser can have their own, or multiple, IP addresses as well. -For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +More information: -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| template_id | string | The ID of the original template | -| version_id | string | The ID of the template version | +* [How to request more IPs](https://sendgrid.com/docs/Classroom/Basics/Account/adding_an_additional_dedicated_ip_to_your_account.html) +* [How to set up reverse DNS](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-reverse-dns/) -### DELETE /templates/{template_id}/versions/{version_id} +### PUT /subusers/{subuser_name}/ips ```python -template_id = "test_url_param" -version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).delete() -print response.status_code -print response.body -print response.headers +data = [ + "127.0.0.1" +] +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).ips.put(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Activate a transactional template version. +## Update Monitor Settings for a subuser -**This endpoint allows you to activate a version of one of your templates.** +Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +### PUT /subusers/{subuser_name}/monitor -For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +```python +data = { + "email": "example@example.com", + "frequency": 500 +} +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.put(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) +``` +## Create monitor settings -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| template_id | string | The ID of the original template | -| version_id | string | The ID of the template version | +Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. -### POST /templates/{template_id}/versions/{version_id}/activate +### POST /subusers/{subuser_name}/monitor ```python -template_id = "test_url_param" -version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).activate.post() -print response.status_code -print response.body -print response.headers +data = { + "email": "example@example.com", + "frequency": 50000 +} +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` - -# TRACKING SETTINGS +## Retrieve monitor settings for a subuser -## Retrieve Tracking Settings +Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. -**This endpoint allows you to retrieve a list of all tracking settings that you can enable on your account.** +### GET /subusers/{subuser_name}/monitor -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +```python +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.get() +print(response.status_code) +print(response.body) +print(response.headers) +``` +## Delete monitor settings -### GET /tracking_settings +Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. + +### DELETE /subusers/{subuser_name}/monitor ```python -params = {'limit': 1, 'offset': 1} -response = sg.client.tracking_settings.get(query_params=params) -print response.status_code -print response.body -print response.headers +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.delete() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Update Click Tracking Settings +## Retrieve the monthly email statistics for a single subuser -**This endpoint allows you to change your current click tracking setting. You can enable, or disable, click tracking using this endpoint.** +**This endpoint allows you to retrieve the monthly email statistics for a specific subuser.** -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats for your subusers. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +When using the `sort_by_metric` to sort your stats by a specific metric, you can not sort by the following metrics: +`bounce_drops`, `deferred`, `invalid_emails`, `processed`, `spam_report_drops`, `spam_reports`, or `unsubscribe_drops`. -### PATCH /tracking_settings/click +For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). + +### GET /subusers/{subuser_name}/stats/monthly ```python -data = { - "enabled": True -} -response = sg.client.tracking_settings.click.patch(request_body=data) -print response.status_code -print response.body -print response.headers +params = {'date': 'test_string', 'sort_by_direction': 'asc', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1} +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).stats.monthly.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve Click Track Settings + +# SUPPRESSION -**This endpoint allows you to retrieve your current click tracking setting.** +## Retrieve all blocks -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +**This endpoint allows you to retrieve a list of all email addresses that are currently on your blocks list.** -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. -### GET /tracking_settings/click +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). + +### GET /suppression/blocks ```python -response = sg.client.tracking_settings.click.get() -print response.status_code -print response.body -print response.headers +params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} +response = sg.client.suppression.blocks.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Update Google Analytics Settings +## Delete blocks -**This endpoint allows you to update your current setting for Google Analytics.** +**This endpoint allows you to delete all email addresses on your blocks list.** -For more information about using Google Analytics, please refer to [Googles URL Builder](https://support.google.com/analytics/answer/1033867?hl=en) and their article on ["Best Practices for Campaign Building"](https://support.google.com/analytics/answer/1037445). +There are two options for deleting blocked emails: -We default the settings to Googles recommendations. For more information, see [Google Analytics Demystified](https://sendgrid.com/docs/Classroom/Track/Collecting_Data/google_analytics_demystified_ga_statistics_vs_sg_statistics.html). +1. You can delete all blocked emails by setting `delete_all` to true in the request body. +2. You can delete some blocked emails by specifying the email addresses in an array in the request body. -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). -### PATCH /tracking_settings/google_analytics +### DELETE /suppression/blocks ```python data = { - "enabled": True, - "utm_campaign": "website", - "utm_content": "", - "utm_medium": "email", - "utm_source": "sendgrid.com", - "utm_term": "" + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] } -response = sg.client.tracking_settings.google_analytics.patch(request_body=data) -print response.status_code -print response.body -print response.headers +response = sg.client.suppression.blocks.delete(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve Google Analytics Settings +## Retrieve a specific block -**This endpoint allows you to retrieve your current setting for Google Analytics.** +**This endpoint allows you to retrieve a specific email address from your blocks list.** -For more information about using Google Analytics, please refer to [Googles URL Builder](https://support.google.com/analytics/answer/1033867?hl=en) and their article on ["Best Practices for Campaign Building"](https://support.google.com/analytics/answer/1037445). +[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. -We default the settings to Googles recommendations. For more information, see [Google Analytics Demystified](https://sendgrid.com/docs/Classroom/Track/Collecting_Data/google_analytics_demystified_ga_statistics_vs_sg_statistics.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +### GET /suppression/blocks/{email} -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). -### GET /tracking_settings/google_analytics +```python +email = "test_url_param" +response = sg.client.suppression.blocks._(email).get() +print(response.status_code) +print(response.body) +print(response.headers) +``` +## Delete a specific block + +**This endpoint allows you to delete a specific email address from your blocks list.** + +[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. + +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). + +### DELETE /suppression/blocks/{email} ```python -response = sg.client.tracking_settings.google_analytics.get() -print response.status_code -print response.body -print response.headers +email = "test_url_param" +response = sg.client.suppression.blocks._(email).delete() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Update Open Tracking Settings +## Retrieve all bounces -**This endpoint allows you to update your current settings for open tracking.** +**This endpoint allows you to retrieve all of your bounces.** -Open Tracking adds an invisible image at the end of the email which can track email opens. If the email recipient has images enabled on their email client, a request to SendGrids server for the invisible image is executed and an open event is logged. These events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook. +Bounces are messages that are returned to the server that sent it. -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +For more information see: -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information +* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) -### PATCH /tracking_settings/open +### GET /suppression/bounces + + +```python +params = {'start_time': 1, 'end_time': 1} +response = sg.client.suppression.bounces.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) +``` +## Delete bounces + +**This endpoint allows you to delete all of your bounces. You can also use this endpoint to remove a specific email address from your bounce list.** + +Bounces are messages that are returned to the server that sent it. + +For more information see: + +* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information +* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) +* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) + +Note: the `delete_all` and `emails` parameters should be used independently of each other as they have different purposes. + +### DELETE /suppression/bounces ```python data = { - "enabled": True + "delete_all": True, + "emails": [ + "example@example.com", + "example2@example.com" + ] } -response = sg.client.tracking_settings.open.patch(request_body=data) -print response.status_code -print response.body -print response.headers +response = sg.client.suppression.bounces.delete(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Get Open Tracking Settings +## Retrieve a Bounce -**This endpoint allows you to retrieve your current settings for open tracking.** +**This endpoint allows you to retrieve a specific bounce for a given email address.** -Open Tracking adds an invisible image at the end of the email which can track email opens. If the email recipient has images enabled on their email client, a request to SendGrids server for the invisible image is executed and an open event is logged. These events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook. +Bounces are messages that are returned to the server that sent it. -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +For more information see: -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information +* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) +* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) -### GET /tracking_settings/open +### GET /suppression/bounces/{email} ```python -response = sg.client.tracking_settings.open.get() -print response.status_code -print response.body -print response.headers +email = "test_url_param" +response = sg.client.suppression.bounces._(email).get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Update Subscription Tracking Settings +## Delete a bounce -**This endpoint allows you to update your current settings for subscription tracking.** +**This endpoint allows you to remove an email address from your bounce list.** -Subscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails. +Bounces are messages that are returned to the server that sent it. This endpoint allows you to delete a single email address from your bounce list. -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +For more information see: -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information +* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) +* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) -### PATCH /tracking_settings/subscription +### DELETE /suppression/bounces/{email} ```python -data = { - "enabled": True, - "html_content": "html content", - "landing": "landing page html", - "plain_content": "text content", - "replace": "replacement tag", - "url": "url" -} -response = sg.client.tracking_settings.subscription.patch(request_body=data) -print response.status_code -print response.body -print response.headers +params = {'email_address': 'example@example.com'} +email = "test_url_param" +response = sg.client.suppression.bounces._(email).delete(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve Subscription Tracking Settings +## Retrieve all invalid emails -**This endpoint allows you to retrieve your current settings for subscription tracking.** +**This endpoint allows you to retrieve a list of all invalid email addresses.** -Subscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails. +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). -### GET /tracking_settings/subscription +### GET /suppression/invalid_emails ```python -response = sg.client.tracking_settings.subscription.get() -print response.status_code -print response.body -print response.headers +params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} +response = sg.client.suppression.invalid_emails.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` - -# USER +## Delete invalid emails -## Get a user's account information. +**This endpoint allows you to remove email addresses from your invalid email address list.** -**This endpoint allows you to retrieve your user account details.** +There are two options for deleting invalid email addresses: -Your user's account information includes the user's account type and reputation. +1) You can delete all invalid email addresses by setting `delete_all` to true in the request body. +2) You can delete some invalid email addresses by specifying certain addresses in an array in the request body. -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. -For more information about your user profile: +Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). -### GET /user/account +### DELETE /suppression/invalid_emails ```python -response = sg.client.user.account.get() -print response.status_code -print response.body -print response.headers +data = { + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] +} +response = sg.client.suppression.invalid_emails.delete(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve your credit balance +## Retrieve a specific invalid email -**This endpoint allows you to retrieve the current credit balance for your account.** +**This endpoint allows you to retrieve a specific invalid email addresses.** -Your monthly credit allotment limits the number of emails you may send before incurring overage charges. For more information about credits and billing, please visit our [Classroom](https://sendgrid.com/docs/Classroom/Basics/Billing/billing_info_and_faqs.html). +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. -### GET /user/credits +Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. + +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). + +### GET /suppression/invalid_emails/{email} ```python -response = sg.client.user.credits.get() -print response.status_code -print response.body -print response.headers +email = "test_url_param" +response = sg.client.suppression.invalid_emails._(email).get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Update your account email address +## Delete a specific invalid email -**This endpoint allows you to update the email address currently on file for your account.** +**This endpoint allows you to remove a specific email address from the invalid email address list.** -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. -For more information about your user profile: +Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). -### PUT /user/email +### DELETE /suppression/invalid_emails/{email} ```python -data = { - "email": "example@example.com" -} -response = sg.client.user.email.put(request_body=data) -print response.status_code -print response.body -print response.headers +email = "test_url_param" +response = sg.client.suppression.invalid_emails._(email).delete() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve your account email address +## Retrieve a specific spam report -**This endpoint allows you to retrieve the email address currently on file for your account.** +**This endpoint allows you to retrieve a specific spam report.** -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. +[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to Twilio SendGrid. -For more information about your user profile: +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). + +### GET /suppression/spam_report/{email} + + +```python +email = "test_url_param" +response = sg.client.suppression.spam_report._(email).get() +print(response.status_code) +print(response.body) +print(response.headers) +``` +## Delete a specific spam report + +**This endpoint allows you to delete a specific spam report.** + +[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to Twilio SendGrid. -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). -### GET /user/email +### DELETE /suppression/spam_report/{email} ```python -response = sg.client.user.email.get() -print response.status_code -print response.body -print response.headers +email = "test_url_param" +response = sg.client.suppression.spam_report._(email).delete() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Update your password - -**This endpoint allows you to update your password.** +## Retrieve all spam reports -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. +**This endpoint allows you to retrieve all spam reports.** -For more information about your user profile: +[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to Twilio SendGrid. -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). -### PUT /user/password +### GET /suppression/spam_reports ```python -data = { - "new_password": "new_password", - "old_password": "old_password" -} -response = sg.client.user.password.put(request_body=data) -print response.status_code -print response.body -print response.headers +params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} +response = sg.client.suppression.spam_reports.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Update a user's profile +## Delete spam reports -**This endpoint allows you to update your current profile details.** +**This endpoint allows you to delete your spam reports.** -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. +There are two options for deleting spam reports: -For more information about your user profile: +1) You can delete all spam reports by setting "delete_all" to true in the request body. +2) You can delete some spam reports by specifying the email addresses in an array in the request body. -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to Twilio SendGrid. -It should be noted that any one or more of the parameters can be updated via the PATCH /user/profile endpoint. The only requirement is that you include at least one when you PATCH. +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). -### PATCH /user/profile +### DELETE /suppression/spam_reports ```python data = { - "city": "Orange", - "first_name": "Example", - "last_name": "User" + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] } -response = sg.client.user.profile.patch(request_body=data) -print response.status_code -print response.body -print response.headers +response = sg.client.suppression.spam_reports.delete(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Get a user's profile - -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. +## Retrieve all global suppressions -For more information about your user profile: +**This endpoint allows you to retrieve a list of all email address that are globally suppressed.** -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +A global suppression (or global unsubscribe) is an email address of a recipient who does not want to receive any of your messages. A globally suppressed recipient will be removed from any email you send. For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/global_unsubscribes.html). -### GET /user/profile +### GET /suppression/unsubscribes ```python -response = sg.client.user.profile.get() -print response.status_code -print response.body -print response.headers +params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} +response = sg.client.suppression.unsubscribes.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Cancel or pause a scheduled send + +# TEMPLATES -**This endpoint allows you to cancel or pause an email that has been scheduled to be sent.** +## Create a transactional template. -If the maximum number of cancellations/pauses are added, HTTP 400 will -be returned. +**This endpoint allows you to create a transactional template.** -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. -### POST /user/scheduled_sends +Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). + +### POST /templates ```python data = { - "batch_id": "YOUR_BATCH_ID", - "status": "pause" + "name": "example_name" } -response = sg.client.user.scheduled_sends.post(request_body=data) -print response.status_code -print response.body -print response.headers +response = sg.client.templates.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve all scheduled sends +## Retrieve all transactional templates. -**This endpoint allows you to retrieve all cancel/paused scheduled send information.** +**This endpoint allows you to retrieve all transactional templates.** -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. -### GET /user/scheduled_sends +Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). + +### GET /templates ```python -response = sg.client.user.scheduled_sends.get() -print response.status_code -print response.body -print response.headers +response = sg.client.templates.get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Update user scheduled send information +## Edit a transactional template. -**This endpoint allows you to update the status of a scheduled send for the given `batch_id`.** +**This endpoint allows you to edit a transactional template.** -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. -### PATCH /user/scheduled_sends/{batch_id} +Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). + + +### PATCH /templates/{template_id} ```python data = { - "status": "pause" + "name": "new_example_name" } -batch_id = "test_url_param" -response = sg.client.user.scheduled_sends._(batch_id).patch(request_body=data) -print response.status_code -print response.body -print response.headers +template_id = "test_url_param" +response = sg.client.templates._(template_id).patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve scheduled send - -**This endpoint allows you to retrieve the cancel/paused scheduled send information for a specific `batch_id`.** - -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. - -### GET /user/scheduled_sends/{batch_id} +## Retrieve a single transactional template. +**This endpoint allows you to retrieve a single transactional template.** -```python -batch_id = "test_url_param" -response = sg.client.user.scheduled_sends._(batch_id).get() -print response.status_code -print response.body -print response.headers -``` -## Delete a cancellation or pause of a scheduled send +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. -**This endpoint allows you to delete the cancellation/pause of a scheduled send.** +Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### DELETE /user/scheduled_sends/{batch_id} +### GET /templates/{template_id} ```python -batch_id = "test_url_param" -response = sg.client.user.scheduled_sends._(batch_id).delete() -print response.status_code -print response.body -print response.headers +template_id = "test_url_param" +response = sg.client.templates._(template_id).get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Update Enforced TLS settings +## Delete a template. -**This endpoint allows you to update your current Enforced TLS settings.** +**This endpoint allows you to delete a transactional template.** -The Enforced TLS settings specify whether or not the recipient is required to support TLS or have a valid certificate. See the [SMTP Ports User Guide](https://sendgrid.com/docs/Classroom/Basics/Email_Infrastructure/smtp_ports.html) for more information on opportunistic TLS. +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. -**Note:** If either setting is enabled and the recipient does not support TLS or have a valid certificate, we drop the message and send a block event with TLS required but not supported as the description. +Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -### PATCH /user/settings/enforced_tls + +### DELETE /templates/{template_id} ```python -data = { - "require_tls": True, - "require_valid_cert": False -} -response = sg.client.user.settings.enforced_tls.patch(request_body=data) -print response.status_code -print response.body -print response.headers +template_id = "test_url_param" +response = sg.client.templates._(template_id).delete() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve current Enforced TLS settings. +## Create a new transactional template version. -**This endpoint allows you to retrieve your current Enforced TLS settings.** +**This endpoint allows you to create a new version of a template.** -The Enforced TLS settings specify whether or not the recipient is required to support TLS or have a valid certificate. See the [SMTP Ports User Guide](https://sendgrid.com/docs/Classroom/Basics/Email_Infrastructure/smtp_ports.html) for more information on opportunistic TLS. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. -**Note:** If either setting is enabled and the recipient does not support TLS or have a valid certificate, we drop the message and send a block event with TLS required but not supported as the description. +For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -### GET /user/settings/enforced_tls + +### POST /templates/{template_id}/versions ```python -response = sg.client.user.settings.enforced_tls.get() -print response.status_code -print response.body -print response.headers +data = { + "active": 1, + "html_content": "<%body%>", + "name": "example_version_name", + "plain_content": "<%body%>", + "subject": "<%subject%>", + "template_id": "ddb96bbc-9b92-425e-8979-99464621b543" +} +template_id = "test_url_param" +response = sg.client.templates._(template_id).versions.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Update your username +## Edit a transactional template version. -**This endpoint allows you to update the username for your account.** +**This endpoint allows you to edit a version of one of your transactional templates.** -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. -For more information about your user profile: +For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| template_id | string | The ID of the original template | +| version_id | string | The ID of the template version | -### PUT /user/username +### PATCH /templates/{template_id}/versions/{version_id} ```python data = { - "username": "test_username" + "active": 1, + "html_content": "<%body%>", + "name": "updated_example_name", + "plain_content": "<%body%>", + "subject": "<%subject%>" } -response = sg.client.user.username.put(request_body=data) -print response.status_code -print response.body -print response.headers +template_id = "test_url_param" +version_id = "test_url_param" +response = sg.client.templates._(template_id).versions._(version_id).patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve your username +## Retrieve a specific transactional template version. -**This endpoint allows you to retrieve your current account username.** +**This endpoint allows you to retrieve a specific version of a template.** -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. -For more information about your user profile: +For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| template_id | string | The ID of the original template | +| version_id | string | The ID of the template version | -### GET /user/username +### GET /templates/{template_id}/versions/{version_id} ```python -response = sg.client.user.username.get() -print response.status_code -print response.body -print response.headers +template_id = "test_url_param" +version_id = "test_url_param" +response = sg.client.templates._(template_id).versions._(version_id).get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Update Event Notification Settings +## Delete a transactional template version. -**This endpoint allows you to update your current event webhook settings.** +**This endpoint allows you to delete one of your transactional template versions.** -If an event type is marked as `true`, then the event webhook will include information about that event. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. -SendGrids Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email. +For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| template_id | string | The ID of the original template | +| version_id | string | The ID of the template version | -### PATCH /user/webhooks/event/settings +### DELETE /templates/{template_id}/versions/{version_id} ```python -data = { - "bounce": True, - "click": True, - "deferred": True, - "delivered": True, - "dropped": True, - "enabled": True, - "group_resubscribe": True, - "group_unsubscribe": True, - "open": True, - "processed": True, - "spam_report": True, - "unsubscribe": True, - "url": "url" -} -response = sg.client.user.webhooks.event.settings.patch(request_body=data) -print response.status_code -print response.body -print response.headers +template_id = "test_url_param" +version_id = "test_url_param" +response = sg.client.templates._(template_id).versions._(version_id).delete() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve Event Webhook settings +## Activate a transactional template version. -**This endpoint allows you to retrieve your current event webhook settings.** +**This endpoint allows you to activate a version of one of your templates.** + +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. -If an event type is marked as `true`, then the event webhook will include information about that event. -SendGrids Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email. +For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| template_id | string | The ID of the original template | +| version_id | string | The ID of the template version | -### GET /user/webhooks/event/settings +### POST /templates/{template_id}/versions/{version_id}/activate ```python -response = sg.client.user.webhooks.event.settings.get() -print response.status_code -print response.body -print response.headers +template_id = "test_url_param" +version_id = "test_url_param" +response = sg.client.templates._(template_id).versions._(version_id).activate.post() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Test Event Notification Settings + +# TRACKING SETTINGS -**This endpoint allows you to test your event webhook by sending a fake event notification post to the provided URL.** +## Retrieve Tracking Settings -SendGrids Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email. +**This endpoint allows you to retrieve a list of all tracking settings that you can enable on your account.** -Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -### POST /user/webhooks/event/test +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). + +### GET /tracking_settings ```python -data = { - "url": "url" -} -response = sg.client.user.webhooks.event.test.post(request_body=data) -print response.status_code -print response.body -print response.headers +params = {'limit': 1, 'offset': 1} +response = sg.client.tracking_settings.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Create a parse setting +## Update Click Tracking Settings -**This endpoint allows you to create a new inbound parse setting.** +**This endpoint allows you to change your current click tracking setting. You can enable, or disable, click tracking using this endpoint.** + +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). -### POST /user/webhooks/parse/settings +### PATCH /tracking_settings/click ```python data = { - "hostname": "myhostname.com", - "send_raw": False, - "spam_check": True, - "url": "http://email.myhosthame.com" + "enabled": True } -response = sg.client.user.webhooks.parse.settings.post(request_body=data) -print response.status_code -print response.body -print response.headers +response = sg.client.tracking_settings.click.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve all parse settings +## Retrieve Click Track Settings -**This endpoint allows you to retrieve all of your current inbound parse settings.** +**This endpoint allows you to retrieve your current click tracking setting.** + +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). -### GET /user/webhooks/parse/settings +### GET /tracking_settings/click ```python -response = sg.client.user.webhooks.parse.settings.get() -print response.status_code -print response.body -print response.headers +response = sg.client.tracking_settings.click.get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Update a parse setting +## Update Google Analytics Settings -**This endpoint allows you to update a specific inbound parse setting.** +**This endpoint allows you to update your current setting for Google Analytics.** -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). +For more information about using Google Analytics, please refer to [Googles URL Builder](https://support.google.com/analytics/answer/1033867?hl=en) and their article on ["Best Practices for Campaign Building"](https://support.google.com/analytics/answer/1037445). -### PATCH /user/webhooks/parse/settings/{hostname} +We default the settings to Googles recommendations. For more information, see [Google Analytics Demystified](https://sendgrid.com/docs/Classroom/Track/Collecting_Data/google_analytics_demystified_ga_statistics_vs_sg_statistics.html). + +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. + +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). + +### PATCH /tracking_settings/google_analytics ```python data = { - "send_raw": True, - "spam_check": False, - "url": "http://newdomain.com/parse" + "enabled": True, + "utm_campaign": "website", + "utm_content": "", + "utm_medium": "email", + "utm_source": "sendgrid.com", + "utm_term": "" } -hostname = "test_url_param" -response = sg.client.user.webhooks.parse.settings._(hostname).patch(request_body=data) -print response.status_code -print response.body -print response.headers +response = sg.client.tracking_settings.google_analytics.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve a specific parse setting - -**This endpoint allows you to retrieve a specific inbound parse setting.** - -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). +## Retrieve Google Analytics Settings -### GET /user/webhooks/parse/settings/{hostname} +**This endpoint allows you to retrieve your current setting for Google Analytics.** +For more information about using Google Analytics, please refer to [Googles URL Builder](https://support.google.com/analytics/answer/1033867?hl=en) and their article on ["Best Practices for Campaign Building"](https://support.google.com/analytics/answer/1037445). -```python -hostname = "test_url_param" -response = sg.client.user.webhooks.parse.settings._(hostname).get() -print response.status_code -print response.body -print response.headers -``` -## Delete a parse setting +We default the settings to Googles recommendations. For more information, see [Google Analytics Demystified](https://sendgrid.com/docs/Classroom/Track/Collecting_Data/google_analytics_demystified_ga_statistics_vs_sg_statistics.html). -**This endpoint allows you to delete a specific inbound parse setting.** +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). -### DELETE /user/webhooks/parse/settings/{hostname} +### GET /tracking_settings/google_analytics ```python -hostname = "test_url_param" -response = sg.client.user.webhooks.parse.settings._(hostname).delete() -print response.status_code -print response.body -print response.headers +response = sg.client.tracking_settings.google_analytics.get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieves Inbound Parse Webhook statistics. +## Update Open Tracking Settings -**This endpoint allows you to retrieve the statistics for your Parse Webhook usage.** +**This endpoint allows you to update your current settings for open tracking.** -SendGrid's Inbound Parse Webhook allows you to parse the contents and attachments of incoming emails. The Parse API can then POST the parsed emails to a URL that you specify. The Inbound Parse Webhook cannot parse messages greater than 20MB in size, including all attachments. +Open Tracking adds an invisible image at the end of the email which can track email opens. If the email recipient has images enabled on their email client, a request to SendGrids server for the invisible image is executed and an open event is logged. These events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook. -There are a number of pre-made integrations for the SendGrid Parse Webhook which make processing events easy. You can find these integrations in the [Library Index](https://sendgrid.com/docs/Integrate/libraries.html#-Webhook-Libraries). +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -### GET /user/webhooks/parse/stats +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). + +### PATCH /tracking_settings/open ```python -params = {'aggregated_by': 'day', 'limit': 'test_string', 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 'test_string'} -response = sg.client.user.webhooks.parse.stats.get(query_params=params) -print response.status_code -print response.body -print response.headers +data = { + "enabled": True +} +response = sg.client.tracking_settings.open.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` - -# WHITELABEL - -## Create a domain whitelabel. +## Get Open Tracking Settings -**This endpoint allows you to create a whitelabel for one of your domains.** +**This endpoint allows you to retrieve your current settings for open tracking.** -If you are creating a domain whitelabel that you would like a subuser to use, you have two options: -1. Use the "username" parameter. This allows you to create a whitelabel on behalf of your subuser. This means the subuser is able to see and modify the created whitelabel. -2. Use the Association workflow (see Associate Domain section). This allows you to assign a whitelabel created by the parent to a subuser. This means the subuser will default to the assigned whitelabel, but will not be able to see or modify that whitelabel. However, if the subuser creates their own whitelabel it will overwrite the assigned whitelabel. +Open Tracking adds an invisible image at the end of the email which can track email opens. If the email recipient has images enabled on their email client, a request to SendGrids server for the invisible image is executed and an open event is logged. These events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook. -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). -### POST /whitelabel/domains +### GET /tracking_settings/open ```python -data = { - "automatic_security": False, - "custom_spf": True, - "default": True, - "domain": "example.com", - "ips": [ - "192.168.1.1", - "192.168.1.2" - ], - "subdomain": "news", - "username": "john@example.com" -} -response = sg.client.whitelabel.domains.post(request_body=data) -print response.status_code -print response.body -print response.headers +response = sg.client.tracking_settings.open.get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## List all domain whitelabels. +## Update Subscription Tracking Settings -**This endpoint allows you to retrieve a list of all domain whitelabels you have created.** +**This endpoint allows you to update your current settings for subscription tracking.** -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Subscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). -### GET /whitelabel/domains +### PATCH /tracking_settings/subscription ```python -params = {'username': 'test_string', 'domain': 'test_string', 'exclude_subusers': 'true', 'limit': 1, 'offset': 1} -response = sg.client.whitelabel.domains.get(query_params=params) -print response.status_code -print response.body -print response.headers +data = { + "enabled": True, + "html_content": "html content", + "landing": "landing page html", + "plain_content": "text content", + "replace": "replacement tag", + "url": "url" +} +response = sg.client.tracking_settings.subscription.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Get the default domain whitelabel. +## Retrieve Subscription Tracking Settings -**This endpoint allows you to retrieve the default whitelabel for a domain.** +**This endpoint allows you to retrieve your current settings for subscription tracking.** -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Subscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| domain | string |The domain to find a default domain whitelabel for. | +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). -### GET /whitelabel/domains/default +### GET /tracking_settings/subscription ```python -response = sg.client.whitelabel.domains.default.get() -print response.status_code -print response.body -print response.headers +response = sg.client.tracking_settings.subscription.get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## List the domain whitelabel associated with the given user. + +# USER + +## Get a user's account information. -**This endpoint allows you to retrieve all of the whitelabels that have been assigned to a specific subuser.** +**This endpoint allows you to retrieve your user account details.** -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Your user's account information includes the user's account type and reputation. -Domain whitelabels can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's whitelabels. To associate a whitelabel with a subuser, the parent account must first create the whitelabel and validate it. The parent may then associate the whitelabel via the subuser management tools. +Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +For more information about your user profile: -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| username | string | Username of the subuser to find associated whitelabels for. | +* [Twilio SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) -### GET /whitelabel/domains/subuser +### GET /user/account ```python -response = sg.client.whitelabel.domains.subuser.get() -print response.status_code -print response.body -print response.headers +response = sg.client.user.account.get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Disassociate a domain whitelabel from a given user. - -**This endpoint allows you to disassociate a specific whitelabel from a subuser.** - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. - -Domain whitelabels can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's whitelabels. To associate a whitelabel with a subuser, the parent account must first create the whitelabel and validate it. The parent may then associate the whitelabel via the subuser management tools. +## Retrieve your credit balance -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +**This endpoint allows you to retrieve the current credit balance for your account.** -## URI Parameters -| URI Parameter | Type | Required? | Description | -|---|---|---|---| -| username | string | required | Username for the subuser to find associated whitelabels for. | +Your monthly credit allotment limits the number of emails you may send before incurring overage charges. For more information about credits and billing, please visit our [Classroom](https://sendgrid.com/docs/Classroom/Basics/Billing/billing_info_and_faqs.html). -### DELETE /whitelabel/domains/subuser +### GET /user/credits ```python -response = sg.client.whitelabel.domains.subuser.delete() -print response.status_code -print response.body -print response.headers +response = sg.client.user.credits.get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Update a domain whitelabel. +## Update your account email address -**This endpoint allows you to update the settings for a domain whitelabel.** +**This endpoint allows you to update the email address currently on file for your account.** -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +For more information about your user profile: -### PATCH /whitelabel/domains/{domain_id} +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) + +### PUT /user/email ```python data = { - "custom_spf": True, - "default": False + "email": "example@example.com" } -domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).patch(request_body=data) -print response.status_code -print response.body -print response.headers +response = sg.client.user.email.put(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve a domain whitelabel. +## Retrieve your account email address -**This endpoint allows you to retrieve a specific domain whitelabel.** +**This endpoint allows you to retrieve the email address currently on file for your account.** -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +For more information about your user profile: +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) -### GET /whitelabel/domains/{domain_id} +### GET /user/email ```python -domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).get() -print response.status_code -print response.body -print response.headers +response = sg.client.user.email.get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Delete a domain whitelabel. +## Update your password + +**This endpoint allows you to update your password.** -**This endpoint allows you to delete a domain whitelabel.** +Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +For more information about your user profile: -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) -### DELETE /whitelabel/domains/{domain_id} +### PUT /user/password ```python -domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).delete() -print response.status_code -print response.body -print response.headers +data = { + "new_password": "new_password", + "old_password": "old_password" +} +response = sg.client.user.password.put(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Associate a domain whitelabel with a given user. +## Update a user's profile -**This endpoint allows you to associate a specific domain whitelabel with a subuser.** +**This endpoint allows you to update your current profile details.** -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. -Domain whitelabels can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's whitelabels. To associate a whitelabel with a subuser, the parent account must first create the whitelabel and validate it. The parent may then associate the whitelabel via the subuser management tools. +For more information about your user profile: -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| domain_id | integer | ID of the domain whitelabel to associate with the subuser. | +It should be noted that any one or more of the parameters can be updated via the PATCH /user/profile endpoint. The only requirement is that you include at least one when you PATCH. -### POST /whitelabel/domains/{domain_id}/subuser +### PATCH /user/profile ```python data = { - "username": "jane@example.com" + "city": "Orange", + "first_name": "Example", + "last_name": "User" } -domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).subuser.post(request_body=data) -print response.status_code -print response.body -print response.headers +response = sg.client.user.profile.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Add an IP to a domain whitelabel. - -**This endpoint allows you to add an IP address to a domain whitelabel.** +## Get a user's profile -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +For more information about your user profile: -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| id | integer | ID of the domain to which you are adding an IP | +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) -### POST /whitelabel/domains/{id}/ips +### GET /user/profile ```python -data = { - "ip": "192.168.0.1" -} -id = "test_url_param" -response = sg.client.whitelabel.domains._(id).ips.post(request_body=data) -print response.status_code -print response.body -print response.headers +response = sg.client.user.profile.get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Remove an IP from a domain whitelabel. - -**This endpoint allows you to remove a domain's IP address from that domain's whitelabel.** +## Cancel or pause a scheduled send -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +**This endpoint allows you to cancel or pause an email that has been scheduled to be sent.** -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +If the maximum number of cancellations/pauses are added, HTTP 400 will +be returned. -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| id | integer | ID of the domain whitelabel to delete the IP from. | -| ip | string | IP to remove from the domain whitelabel. | +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### DELETE /whitelabel/domains/{id}/ips/{ip} +### POST /user/scheduled_sends ```python -id = "test_url_param" -ip = "test_url_param" -response = sg.client.whitelabel.domains._(id).ips._(ip).delete() -print response.status_code -print response.body -print response.headers +data = { + "batch_id": "YOUR_BATCH_ID", + "status": "pause" +} +response = sg.client.user.scheduled_sends.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Validate a domain whitelabel. - -**This endpoint allows you to validate a domain whitelabel. If it fails, it will return an error message describing why the whitelabel could not be validated.** - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +## Retrieve all scheduled sends -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +**This endpoint allows you to retrieve all cancel/paused scheduled send information.** -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| id | integer |ID of the domain whitelabel to validate. | +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### POST /whitelabel/domains/{id}/validate +### GET /user/scheduled_sends ```python -id = "test_url_param" -response = sg.client.whitelabel.domains._(id).validate.post() -print response.status_code -print response.body -print response.headers +response = sg.client.user.scheduled_sends.get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Create an IP whitelabel - -**This endpoint allows you to create an IP whitelabel.** - -When creating an IP whitelable, you should use the same subdomain that you used when you created a domain whitelabel. +## Update user scheduled send information -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. +**This endpoint allows you to update the status of a scheduled send for the given `batch_id`.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### POST /whitelabel/ips +### PATCH /user/scheduled_sends/{batch_id} ```python data = { - "domain": "example.com", - "ip": "192.168.1.1", - "subdomain": "email" + "status": "pause" } -response = sg.client.whitelabel.ips.post(request_body=data) -print response.status_code -print response.body -print response.headers +batch_id = "test_url_param" +response = sg.client.user.scheduled_sends._(batch_id).patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve all IP whitelabels - -**This endpoint allows you to retrieve all of the IP whitelabels that have been created by this account.** - -You may include a search key by using the "ip" parameter. This enables you to perform a prefix search for a given IP segment (e.g. "192."). +## Retrieve scheduled send -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. +**This endpoint allows you to retrieve the cancel/paused scheduled send information for a specific `batch_id`.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### GET /whitelabel/ips +### GET /user/scheduled_sends/{batch_id} ```python -params = {'ip': 'test_string', 'limit': 1, 'offset': 1} -response = sg.client.whitelabel.ips.get(query_params=params) -print response.status_code -print response.body -print response.headers +batch_id = "test_url_param" +response = sg.client.user.scheduled_sends._(batch_id).get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve an IP whitelabel - -**This endpoint allows you to retrieve an IP whitelabel.** +## Delete a cancellation or pause of a scheduled send -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. +**This endpoint allows you to delete the cancellation/pause of a scheduled send.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### GET /whitelabel/ips/{id} +### DELETE /user/scheduled_sends/{batch_id} ```python -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).get() -print response.status_code -print response.body -print response.headers +batch_id = "test_url_param" +response = sg.client.user.scheduled_sends._(batch_id).delete() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Delete an IP whitelabel +## Update Enforced TLS settings -**This endpoint allows you to delete an IP whitelabel.** +**This endpoint allows you to update your current Enforced TLS settings.** -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. +The Enforced TLS settings specify whether or not the recipient is required to support TLS or have a valid certificate. See the [SMTP Ports User Guide](https://sendgrid.com/docs/Classroom/Basics/Email_Infrastructure/smtp_ports.html) for more information on opportunistic TLS. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). +**Note:** If either setting is enabled and the recipient does not support TLS or have a valid certificate, we drop the message and send a block event with TLS required but not supported as the description. -### DELETE /whitelabel/ips/{id} +### PATCH /user/settings/enforced_tls ```python -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).delete() -print response.status_code -print response.body -print response.headers +data = { + "require_tls": True, + "require_valid_cert": False +} +response = sg.client.user.settings.enforced_tls.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Validate an IP whitelabel +## Retrieve current Enforced TLS settings. -**This endpoint allows you to validate an IP whitelabel.** +**This endpoint allows you to retrieve your current Enforced TLS settings.** -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. +The Enforced TLS settings specify whether or not the recipient is required to support TLS or have a valid certificate. See the [SMTP Ports User Guide](https://sendgrid.com/docs/Classroom/Basics/Email_Infrastructure/smtp_ports.html) for more information on opportunistic TLS. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). +**Note:** If either setting is enabled and the recipient does not support TLS or have a valid certificate, we drop the message and send a block event with TLS required but not supported as the description. -### POST /whitelabel/ips/{id}/validate +### GET /user/settings/enforced_tls ```python -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).validate.post() -print response.status_code -print response.body -print response.headers +response = sg.client.user.settings.enforced_tls.get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Create a Link Whitelabel +## Update your username -**This endpoint allows you to create a new link whitelabel.** +**This endpoint allows you to update the username for your account.** -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +For more information about your user profile: -### POST /whitelabel/links +* [Twilio SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) + +### PUT /user/username ```python data = { - "default": True, - "domain": "example.com", - "subdomain": "mail" + "username": "test_username" } -params = {'limit': 1, 'offset': 1} -response = sg.client.whitelabel.links.post(request_body=data, query_params=params) -print response.status_code -print response.body -print response.headers +response = sg.client.user.username.put(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve all link whitelabels +## Retrieve your username + +**This endpoint allows you to retrieve your current account username.** -**This endpoint allows you to retrieve all link whitelabels.** +Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +For more information about your user profile: -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +* [Twilio SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) -### GET /whitelabel/links +### GET /user/username ```python -params = {'limit': 1} -response = sg.client.whitelabel.links.get(query_params=params) -print response.status_code -print response.body -print response.headers +response = sg.client.user.username.get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve a Default Link Whitelabel +## Update Event Notification Settings -**This endpoint allows you to retrieve the default link whitelabel.** +**This endpoint allows you to update your current event webhook settings.** -Default link whitelabel is the actual link whitelabel to be used when sending messages. If there are multiple link whitelabels, the default is determined by the following order: -
    -
  • Validated link whitelabels marked as "default"
  • -
  • Legacy link whitelabels (migrated from the whitelabel wizard)
  • -
  • Default SendGrid link whitelabel (i.e. 100.ct.sendgrid.net)
  • -
+If an event type is marked as `true`, then the event webhook will include information about that event. -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +Twilio SendGrid's Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as Twilio SendGrid processes your email. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. -### GET /whitelabel/links/default +### PATCH /user/webhooks/event/settings ```python -params = {'domain': 'test_string'} -response = sg.client.whitelabel.links.default.get(query_params=params) -print response.status_code -print response.body -print response.headers +data = { + "bounce": True, + "click": True, + "deferred": True, + "delivered": True, + "dropped": True, + "enabled": True, + "group_resubscribe": True, + "group_unsubscribe": True, + "open": True, + "processed": True, + "spam_report": True, + "unsubscribe": True, + "url": "url" +} +response = sg.client.user.webhooks.event.settings.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve Associated Link Whitelabel +## Retrieve Event Webhook settings -**This endpoint allows you to retrieve the associated link whitelabel for a subuser.** +**This endpoint allows you to retrieve your current event webhook settings.** -Link whitelables can be associated with subusers from the parent account. This functionality allows -subusers to send mail using their parent's link whitelabels. To associate a link whitelabel, the parent account -must first create a whitelabel and validate it. The parent may then associate that whitelabel with a subuser via the API or the Subuser Management page in the user interface. +If an event type is marked as `true`, then the event webhook will include information about that event. -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +Twilio SendGrid's Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as Twilio SendGrid processes your email. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. -### GET /whitelabel/links/subuser +### GET /user/webhooks/event/settings ```python -params = {'username': 'test_string'} -response = sg.client.whitelabel.links.subuser.get(query_params=params) -print response.status_code -print response.body -print response.headers +response = sg.client.user.webhooks.event.settings.get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Disassociate a Link Whitelabel - -**This endpoint allows you to disassociate a link whitelabel from a subuser.** +## Test Event Notification Settings -Link whitelables can be associated with subusers from the parent account. This functionality allows -subusers to send mail using their parent's link whitelabels. To associate a link whitelabel, the parent account -must first create a whitelabel and validate it. The parent may then associate that whitelabel with a subuser via the API or the Subuser Management page in the user interface. +**This endpoint allows you to test your event webhook by sending a fake event notification post to the provided URL.** -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +Twilio SendGrid's Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as Twilio SendGrid processes your email. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. -### DELETE /whitelabel/links/subuser +### POST /user/webhooks/event/test ```python -params = {'username': 'test_string'} -response = sg.client.whitelabel.links.subuser.delete(query_params=params) -print response.status_code -print response.body -print response.headers +data = { + "url": "url" +} +response = sg.client.user.webhooks.event.test.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Update a Link Whitelabel - -**This endpoint allows you to update a specific link whitelabel. You can use this endpoint to change a link whitelabel's default status.** +## Create a parse setting -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +**This endpoint allows you to create a new inbound parse setting.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by Twilio SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). -### PATCH /whitelabel/links/{id} +### POST /user/webhooks/parse/settings ```python data = { - "default": True + "hostname": "myhostname.com", + "send_raw": False, + "spam_check": True, + "url": "http://email.myhosthame.com" } -id = "test_url_param" -response = sg.client.whitelabel.links._(id).patch(request_body=data) -print response.status_code -print response.body -print response.headers +response = sg.client.user.webhooks.parse.settings.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Retrieve a Link Whitelabel - -**This endpoint allows you to retrieve a specific link whitelabel.** +## Retrieve all parse settings -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +**This endpoint allows you to retrieve all of your current inbound parse settings.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by Twilio SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). -### GET /whitelabel/links/{id} +### GET /user/webhooks/parse/settings ```python -id = "test_url_param" -response = sg.client.whitelabel.links._(id).get() -print response.status_code -print response.body -print response.headers +response = sg.client.user.webhooks.parse.settings.get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Delete a Link Whitelabel - -**This endpoint allows you to delete a link whitelabel.** +## Update a parse setting -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +**This endpoint allows you to update a specific inbound parse setting.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by Twilio SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). -### DELETE /whitelabel/links/{id} +### PATCH /user/webhooks/parse/settings/{hostname} ```python -id = "test_url_param" -response = sg.client.whitelabel.links._(id).delete() -print response.status_code -print response.body -print response.headers +data = { + "send_raw": True, + "spam_check": False, + "url": "http://newdomain.com/parse" +} +hostname = "test_url_param" +response = sg.client.user.webhooks.parse.settings._(hostname).patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Validate a Link Whitelabel - -**This endpoint allows you to validate a link whitelabel.** +## Retrieve a specific parse setting -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +**This endpoint allows you to retrieve a specific inbound parse setting.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by Twilio SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). -### POST /whitelabel/links/{id}/validate +### GET /user/webhooks/parse/settings/{hostname} ```python -id = "test_url_param" -response = sg.client.whitelabel.links._(id).validate.post() -print response.status_code -print response.body -print response.headers +hostname = "test_url_param" +response = sg.client.user.webhooks.parse.settings._(hostname).get() +print(response.status_code) +print(response.body) +print(response.headers) ``` -## Associate a Link Whitelabel +## Delete a parse setting -**This endpoint allows you to associate a link whitelabel with a subuser account.** +**This endpoint allows you to delete a specific inbound parse setting.** -Link whitelables can be associated with subusers from the parent account. This functionality allows -subusers to send mail using their parent's link whitelabels. To associate a link whitelabel, the parent account -must first create a whitelabel and validate it. The parent may then associate that whitelabel with a subuser via the API or the Subuser Management page in the user interface. +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by Twilio SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +### DELETE /user/webhooks/parse/settings/{hostname} -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### POST /whitelabel/links/{link_id}/subuser +```python +hostname = "test_url_param" +response = sg.client.user.webhooks.parse.settings._(hostname).delete() +print(response.status_code) +print(response.body) +print(response.headers) +``` +## Retrieves Inbound Parse Webhook statistics. + +**This endpoint allows you to retrieve the statistics for your Parse Webhook usage.** + +Twilio SendGrid's Inbound Parse Webhook allows you to parse the contents and attachments of incoming emails. The Parse API can then POST the parsed emails to a URL that you specify. The Inbound Parse Webhook cannot parse messages greater than 20MB in size, including all attachments. + +There are a number of pre-made integrations for the Twilio SendGrid Parse Webhook which make processing events easy. You can find these integrations in the [Library Index](https://sendgrid.com/docs/Integrate/libraries.html#-Webhook-Libraries). + +### GET /user/webhooks/parse/stats ```python -data = { - "username": "jane@example.com" -} -link_id = "test_url_param" -response = sg.client.whitelabel.links._(link_id).subuser.post(request_body=data) -print response.status_code -print response.body -print response.headers +params = {'aggregated_by': 'day', 'limit': 'test_string', 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 'test_string'} +response = sg.client.user.webhooks.parse.stats.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) ``` diff --git a/USE_CASES.md b/USE_CASES.md deleted file mode 100644 index dd42994b0..000000000 --- a/USE_CASES.md +++ /dev/null @@ -1,683 +0,0 @@ -This documentation provides examples for specific use cases. Please [open an issue](https://github.com/sendgrid/sendgrid-python/issues) or make a pull request for any use cases you would like us to document here. Thank you! - -# Table of Contents - -* [Transactional Templates](#transactional-templates) -* [Attachment](#attachment) -* [Create a Django app to send email with SendGrid](#create-a-django-app-to-send-email-with-sendgrid) - * [Deploy to Heroku](#deploy-to-heroku) -* [How to Setup a Domain Whitelabel](#domain_whitelabel) -* [How to View Email Statistics](#email_stats) -* [Asynchronous Mail Send](#asynchronous-mail-send) -* [Error Handling](#error-handling) -* [Deploy A Simple Hello Email App on AWS](#hello_email_on_aws) - - -# Transactional Templates - -For this example, we assume you have created a [transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). Following is the template content we used for testing. - -Template ID (replace with your own): - -```text -13b8f94f-bcae-4ec6-b752-70d6cb59f932 -``` - -Email Subject: - -```text -<%subject%> -``` - -Template Body: - -```html - - - - - -Hello -name-, -

-I'm glad you are trying out the template feature! -

-<%body%> -

-I hope you are having a great day in -city- :) -

- - -``` - -## With Mail Helper Class - -```python -import sendgrid -import os -from sendgrid.helpers.mail import Email, Content, Substitution, Mail -try: - # Python 3 - import urllib.request as urllib -except ImportError: - # Python 2 - import urllib2 as urllib - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) -from_email = Email("test@example.com") -subject = "I'm replacing the subject tag" -to_email = Email("test@example.com") -content = Content("text/html", "I'm replacing the body tag") -mail = Mail(from_email, subject, to_email, content) -mail.personalizations[0].add_substitution(Substitution("-name-", "Example User")) -mail.personalizations[0].add_substitution(Substitution("-city-", "Denver")) -mail.template_id = "13b8f94f-bcae-4ec6-b752-70d6cb59f932" -try: - response = sg.client.mail.send.post(request_body=mail.get()) -except urllib.HTTPError as e: - print (e.read()) - exit() -print(response.status_code) -print(response.body) -print(response.headers) -``` - -## Without Mail Helper Class - -```python -import sendgrid -import os -try: - # Python 3 - import urllib.request as urllib -except ImportError: - # Python 2 - import urllib2 as urllib - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) -data = { - "personalizations": [ - { - "to": [ - { - "email": "test@example.com" - } - ], - "substitutions": { - "-name-": "Example User", - "-city-": "Denver" - }, - "subject": "I'm replacing the subject tag" - }, - ], - "from": { - "email": "test@example.com" - }, - "content": [ - { - "type": "text/html", - "value": "I'm replacing the body tag" - } - ], - "template_id": "13b8f94f-bcae-4ec6-b752-70d6cb59f932" -} -try: - response = sg.client.mail.send.post(request_body=data) -except urllib.HTTPError as e: - print (e.read()) - exit() -print(response.status_code) -print(response.body) -print(response.headers) -``` - - -# Attachment - -```python -import base64 -import sendgrid -import os -from sendgrid.helpers.mail import Email, Content, Mail, Attachment -try: - # Python 3 - import urllib.request as urllib -except ImportError: - # Python 2 - import urllib2 as urllib - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) -from_email = Email("test@example.com") -subject = "subject" -to_email = Email("to_email@example.com") -content = Content("text/html", "I'm a content example") - -file_path = "file_path.pdf" -with open(file_path,'rb') as f: - data = f.read() - f.close() -encoded = base64.b64encode(data).decode() - -attachment = Attachment() -attachment.content = encoded -attachment.type = "application/pdf" -attachment.filename = "test.pdf" -attachment.disposition = "attachment" -attachment.content_id = "Example Content ID" - -mail = Mail(from_email, subject, to_email, content) -mail.add_attachment(attachment) -try: - response = sg.client.mail.send.post(request_body=mail.get()) -except urllib.HTTPError as e: - print(e.read()) - exit() - -print(response.status_code) -print(response.body) -print(response.headers) -``` - - -# Create a Django app to send email with SendGrid - -This tutorial explains how we set up a simple Django app to send an email with the SendGrid Python SDK and how we deploy our app to Heroku. - -## Create a Django project - -We first create a project folder. - -```bash -$ mkdir hello-sendgrid -$ cd hello-sendgrid -``` - -We assume you have created and activated a [virtual environment](https://virtualenv.pypa.io/) (See [venv](https://docs.python.org/3/tutorial/venv.html) for Python 3+) for isolated Python environments. - -Run the command below to install Django, Gunicorn (a Python WSGI HTTP server), and SendGrid Python SDK. - -```bash -$ pip install django gunicorn sendgrid -``` - -It's a good practice for Python dependency management. We'll pin the requirements with a file `requirements.txt`. - -```bash -$ pip freeze > requirements.txt -``` - -Run the command below to initialize a Django project. - -```bash -$ django-admin startproject hello_sendgrid -``` - -The folder structure should look like this: - -``` -hello-sendgrid -├── hello_sendgrid -│   ├── hello_sendgrid -│   │   ├── __init__.py -│   │   ├── settings.py -│   │   ├── urls.py -│   │   └── wsgi.py -│   └── manage.py -└── requirements.txt -``` - -Let's create a page to generate and send an email to a user when you hit the page. - -We first create a file `views.py` and put it under the folder `hello_sendgrid/hello_sendgrid`. Add the minimum needed code below. - -```python -import os - -from django.http import HttpResponse - -import sendgrid -from sendgrid.helpers.mail import * - - -def index(request): - sg = sendgrid.SendGridAPIClient( - apikey=os.environ.get('SENDGRID_API_KEY') - ) - from_email = Email('test@example.com') - to_email = Email('test@example.com') - subject = 'Sending with SendGrid is Fun' - content = Content( - 'text/plain', - 'and easy to do anywhere, even with Python' - ) - mail = Mail(from_email, subject, to_email, content) - response = sg.client.mail.send.post(request_body=mail.get()) - - return HttpResponse('Email Sent!') -``` - -**Note:** It would be best to change your to email from `test@example.com` to your own email, so that you can see the email you receive. - -Now the folder structure should look like this: - -``` -hello-sendgrid -├── hello_sendgrid -│   ├── hello_sendgrid -│   │   ├── __init__.py -│   │   ├── settings.py -│   │   ├── urls.py -│   │   ├── views.py -│   │   └── wsgi.py -│   └── manage.py -└── requirements.txt -``` - -Next we open the file `urls.py` in order to add the view we have just created to the Django URL dispatcher. - -```python -from django.conf.urls import url -from django.contrib import admin - -from .views import index - - -urlpatterns = [ - url(r'^admin/', admin.site.urls), - url(r'^sendgrid/', index, name='sendgrid'), -] -``` - -These paths allow the URL `/sendgrid/` to send the email. - -We also assume that you have set up your development environment with your `SENDGRID_API_KEY`. If you have not done it yet, please do so. See the section [Setup Environment Variables](https://github.com/sendgrid/sendgrid-python#setup-environment-variables). - -Now we should be able to send an email. Let's run our Django development server to test it. - -``` -$ cd hello_sengrid -$ python manage.py migrate -$ python manage.py runserver -``` - -By default, it starts the development server at `http://127.0.0.1:8000/`. To test if we can send email or not, go to `http://127.0.0.1:8000/sendgrid/`. If it works, we should see the page says "Email Sent!". - -**Note:** If you use `test@example.com` as your from email, it's likely to go to your spam folder. To have the emails show up in your inbox, try using an email address at the domain you registered your SendGrid account. - - -## Deploy to Heroku - -There are different deployment methods we can choose. In this tutorial, we choose to deploy our app using the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli). Therefore, let's install it before we go further. - -Once you have the Heroku CLI installed, run the command below to log in to your Heroku account if you haven't already. - -``` -$ heroku login -``` - -Before we start the deployment, let's create a Heroku app by running the command below. This tutorial names the Heroku app `hello-sendgrid`. - -```bash -$ heroku create hello-sendgrid -``` - -**Note:** If you see Heroku reply with "Name is already taken", please add a random string to the end of the name. - -We also need to do a couple things: - -1. Add `'*'` or your Heroku app domain to `ALLOWED_HOSTS` in the file `settings.py`. It will look like this: -```python -ALLOWED_HOSTS = ['*'] -``` - -2. Add `Procfile` with the code below to declare what commands are run by your application's dynos on the Heroku platform. -``` -web: cd hello_sendgrid && gunicorn hello_sendgrid.wsgi --log-file - -``` - -The final folder structure looks like this: - -``` -hello-sendgrid -├── hello_sendgrid -│   ├── hello_sendgrid -│   │   ├── __init__.py -│   │   ├── settings.py -│   │   ├── urls.py -│   │   ├── views.py -│   │   └── wsgi.py -│   └── manage.py -├── Procfile -└── requirements.txt -``` - -Go to the root folder then initialize a Git repository. - -``` -$ git init -$ heroku git:remote -a hello-sendgrid -``` - -**Note:** Change `hello-sendgrid` to your new Heroku app name you created earlier. - -Add your `SENDGRID_API_KEY` as one of the Heroku environment variables. - -``` -$ heroku config:set SENDGRID_API_KEY= -``` - -Since we do not use any static files, we will disable `collectstatic` for this project. - -``` -$ heroku config:set DISABLE_COLLECTSTATIC=1 -``` - -Commit the code to the repository and deploy it to Heroku using Git. - -``` -$ git add . -$ git commit -am "Create simple Hello Email Django app using SendGrid" -$ git push heroku master -``` - -After that, let's verify if our app is working or not by accessing the root domain of your Heroku app. You should see the page says "Email Sent!" and on the Activity Feed page in the SendGrid dashboard, you should see a new feed with the email you set in the code. - - -# How to Setup a Domain Whitelabel - -You can find documentation for how to setup a domain whitelabel via the UI [here](https://sendgrid.com/docs/Classroom/Basics/Whitelabel/setup_domain_whitelabel.html) and via API [here](https://github.com/sendgrid/sendgrid-python/blob/master/USAGE.md#whitelabel). - -Find more information about all of SendGrid's whitelabeling related documentation [here](https://sendgrid.com/docs/Classroom/Basics/Whitelabel/index.html). - - -# How to View Email Statistics - -You can find documentation for how to view your email statistics via the UI [here](https://app.sendgrid.com/statistics) and via API [here](https://github.com/sendgrid/sendgrid-python/blob/master/USAGE.md#stats). - -Alternatively, we can post events to a URL of your choice via our [Event Webhook](https://sendgrid.com/docs/API_Reference/Webhooks/event.html) about events that occur as SendGrid processes your email. - - -# Asynchronous Mail Send - -## Using `asyncio` (3.5+) - -The built-in `asyncio` library can be used to send email in a non-blocking manner. `asyncio` helps us execute mail sending in a separate context, allowing us to continue execution of business logic without waiting for all our emails to send first. - -```python -import sendgrid -from sendgrid.helpers.mail import * -import os -import asyncio - - -sg = sendgrid.SendGridAPIClient( - apikey=os.getenv("SENDGRID_API_KEY") -) - -from_email = Email("test@example.com") -to_email = Email("test1@example.com") - -content = Content("text/plain", "This is asynchronous sending test.") - -# instantiate `sendgrid.helpers.mail.Mail` objects -em1 = Mail(from_email, "Message #1", to_email, content) -em2 = Mail(from_email, "Message #2", to_email, content) -em3 = Mail(from_email, "Message #3", to_email, content) -em4 = Mail(from_email, "Message #4", to_email, content) -em5 = Mail(from_email, "Message #5", to_email, content) -em6 = Mail(from_email, "Message #6", to_email, content) -em7 = Mail(from_email, "Message #7", to_email, content) -em8 = Mail(from_email, "Message #8", to_email, content) -em9 = Mail(from_email, "Message #9", to_email, content) -em10 = Mail(from_email, "Message #10", to_email, content) - - -ems = [em1, em2, em3, em4, em5, em6, em7, em8, em9, em10] - - -async def send_email(n, email): - ''' - send_mail wraps SendGrid's API client, and makes a POST request to - the api/v3/mail/send endpoint with `email`. - Args: - email: single mail object. - ''' - try: - response = sg.client.mail.send.post(request_body=email.get()) - if response.status_code < 300: - print("Email #{} processed".format(n), response.body, response.status_code) - except urllib.error.HTTPError as e: - e.read() - - -@asyncio.coroutine -def send_many(emails, cb): - ''' - send_many creates a number of non-blocking tasks (to send email) - that will run on the existing event loop. Due to non-blocking nature, - you can include a callback that will run after all tasks have been queued. - - Args: - emails: contains any # of `sendgrid.helpers.mail.Mail`. - cb: a function that will execute immediately. - ''' - print("START - sending emails ...") - for n, em in enumerate(emails): - asyncio.async(send_email(n, em)) - print("END - returning control...") - cb() - - -def sample_cb(): - print("Executing callback now...") - for i in range(0, 100): - print(i) - return - - -if __name__ == "__main__": - loop = asyncio.get_event_loop() - task = asyncio.async(send_many(ems, sample_cb)) - loop.run_until_complete(task) -``` - - -# Error Handling -[Custom exceptions](https://github.com/sendgrid/python-http-client/blob/master/python_http_client/exceptions.py) for `python_http_client` are now supported, which can be imported by consuming libraries. - -Please see [here](https://github.com/sendgrid/python-http-client/blob/master/python_http_client/exceptions.py) for a list of supported exceptions. - -```python - import sendgrid - import os - from sendgrid.helpers.mail import * - from python_http_client import exceptions - - sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - from_email = Email("dx@sendgrid.com") - to_email = Email("elmer.thomas@sendgrid.com") - subject = "Sending with SendGrid is Fun" - content = Content("text/plain", "and easy to do anywhere, even with Python") - mail = Mail(from_email, subject, to_email, content) - try: - response = sg.client.mail.send.post(request_body=mail.get()) - except exceptions.BadRequestsError as e: - print(e.body) - exit() - print(response.status_code) - print(response.body) - print(response.headers) -``` - - -# Deploy a simple Hello Email app on AWS - -This tutorial explains how to set up a simple "Hello Email" app on AWS, using the AWS CodeStar service. - -We'll be creating a basic web service to send email via SendGrid. The application will run on AWS Lambda, and the "endpoint" will be via AWS API Gateway. - -The neat thing is that CodeStar provides all of this in a pre-configured package. We just have to make some config changes, and push our code. - -Once this tutorial is complete, you'll have a basic web service for sending email that can be invoked via a link to your newly created API endpoint. - -### Prerequisites -Python 2.6, 2.7, 3.4, or 3.5 are supported by the sendgrid Python library, however I was able to utilize 3.6 with no issue. - -Before starting this tutorial, you will need to have access to an AWS account in which you are allowed to provision resources. This tutorial also assumes you've already created a SendGrid account with free-tier access. Finally, it is highly recommended you utilize [virtualenv](https://virtualenv.pypa.io/en/stable/). - -*DISCLAIMER*: Any resources provisioned here may result in charges being incurred to your account. Sendgrid is in no way responsible for any billing charges. - - -## Getting Started - -### Create AWS CodeStar Project -Log in to your AWS account and go to the AWS CodeStar service. Click "Start a project". For this tutorial we're going to choose a Python Web service, utilizing AWS Lambda. You can use the filters on the left hand side of the UI to narrow down the available choices. - -After you've selected the template, you're asked to provide a name for your project. Go ahead and name it "hello-email". Once you've entered a name, click "Create Project" in the lower right hand corner. You can then choose which tools you want to use to interact with the project. For this tutorial, we'll be choosing "Command Line". - -Once that is completed, you'll be given some basic steps to get Git installed and setup, and instructions for connecting to the AWS CodeCommit(git) repository. You can either use HTTPS, or SSH. Instructions for setting up either are provided. - -Go ahead and clone the Git repository link after it is created. You may need to click "Skip" in the lower right hand corner to proceed. - -Once that's done, you've successfully created a CodeStar project! You should be at the dashboard, with a view of the wiki, change log, build pipeline, and application endpoint. - -### Create SendGrid API Key -Log in to your SendGrid account. Click on your user name on the left hand side of the UI and choose "Setup Guide" from the drop-down menu. On the "Welcome" menu, choose "Send Your First Email", and then "Integrate using our Web API or SMTP relay." Choose "Web API" as the recommended option on the next screen, as we'll be using that for this tutorial. - -On the next menu, you have the option to choose what programming language you'll be using. The obvious choice for this tutorial will be Python. - -Follow the steps on the next screen. Choose a name for your API key, such as "hello-email". Follow the remaining steps to create an environment variable, install the sendgrid module, and copy the test code. Once that is complete, check the "I've integrated the code above" box, and click the "Next: Verify Integration" button. - -Assuming all the steps were completed correctly, you should be greeted with a success message. If not, go back and verify that everything is correct, including your API key environment varible, and Python code. - -## Deploy hello-world app using CodeStar - -For the rest of the tutorial, we'll be working out of the git repository we cloned from AWS earlier: -``` -$ cd hello-email -``` -note: this assumes you cloned the git repo inside your current directory. My directory is: - -``` -~/projects/hello-email -``` - -The directory contents should be as follows: - - ├──buildspec.yml - ├──index.py - ├──template.yml - ├──README.md - -The `buildspec.yml` file is a YAML definition for the AWS CodeBuild service, and will not need to be modified for this tutorial. The `index.py` is where the application logic will be placed, and the `template.yml` is a YAML definition file for the AWS Lambda function. - -We'll start by modifying the `template.yml` file. Copy and paste from the example below, or edit your existing copy to match: - -```yaml -AWSTemplateFormatVersion: 2010-09-09 -Transform: -- AWS::Serverless-2016-10-31 -- AWS::CodeStar - -Parameters: - ProjectId: - Type: String - Description: CodeStar projectId used to associate new resources to team members - -Resources: - HelloEmail: - Type: AWS::Serverless::Function - Properties: - Handler: index.handler - Runtime: python3.6 - Role: - Fn::ImportValue: - !Join ['-', [!Ref 'ProjectId', !Ref 'AWS::Region', 'LambdaTrustRole']] - Events: - GetEvent: - Type: Api - Properties: - Path: / - Method: get - PostEvent: - Type: Api - Properties: - Path: / - Method: post -``` - -In the root project directory, run the following commands: -``` -virtualenv venv -source ./venv/bin/activate -``` - -Prior to being able to deploy our Python code, we'll need to install the sendgrid Python module *locally*. One of the idiosyncracies of AWS Lambda is that all library and module dependencies that aren't part of the standard library have to be included with the code/build artifact. Virtual environments do not translate to the Lambda runtime environment. - -In the root project directory, run the following command: -``` -$ pip install sendgrid -t . -``` -This will install the module locally to the project dir, where it can be built into the Lambda deployment. - -Now go ahead and modify the `index.py` file to match below: - -```python -import json -import datetime -import sendgrid -import os -from sendgrid.helpers.mail import * - -def handler(event, context): - sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - from_email = Email("test@example.com") - to_email = Email("test@example.com") - subject = "Sending with SendGrid is Fun" - content = Content("text/plain", "and easy to do anywhere, even with Python") - mail = Mail(from_email, subject, to_email, content) - response = sg.client.mail.send.post(request_body=mail.get()) - status = b"{}".decode('utf-8').format(response.status_code) - body = b"{}".decode('utf-8').format(response.body) - headers = b"{}".decode('utf-8').format(response.headers) - data = { - 'status': status, - 'body': body, - 'headers': headers.splitlines(), - 'timestamp': datetime.datetime.utcnow().isoformat() - } - return {'statusCode': 200, - 'body': json.dumps(data), - 'headers': {'Content-Type': 'application/json'}} -``` - -Note that for the most part, we've simply copied the intial code from the API verification with SendGrid. Some slight modifications were needed to allow it to run as a lambda function, and for the output to be passed cleanly from the API endpoint. - -Change the `test@example.com` emails appropriately so that you may receive the test email. - -Go ahead and commit/push your code: - -``` -$ git add . -``` - -``` -$ git commit -m 'hello-email app' -``` - -``` -$ git push -``` - -Once the code is successfully pushed, head back to the AWS CodeStar dashboard for your project. After your commit successfully registers, an automated build and deployment process should kick off. - -One more step left before our application will work correctly. After your code has bee deployed, head to the AWS Lambda console. Click on your function name, which should start with `awscodestar-hello-email-lambda-`, or similar. - -Scroll down to the "Environment Variables" section. Here we need to populate our SendGrid API key. Copy the value from the `sendgrid.env` file you created earlier, ensuring to capture the entire value. Make sure the key is titled: - -``` -SENDGRID_API_KEY -``` - -Now, go back to your project dashboard in CodeStar. Click on the link under "Application endpoints". After a moment, you should be greeted with JSON output indicating an email was successfully sent. - -Congratulations, you've just used serverless technology to create an email sending app in AWS! diff --git a/app.json b/app.json index 3c86fae6c..e25064d12 100644 --- a/app.json +++ b/app.json @@ -1,11 +1,11 @@ { - "name": "SendGrid Inbound Parse", - "description": "Consume and parse POSTs from SendGrid's Inbound Parse Webhook", + "name": "Twilio SendGrid Inbound Parse", + "description": "Consume and parse POSTs from Twilio SendGrid's Inbound Parse Webhook", "keywords": [ "sendgrid", "inbound parse" ], "website": "http://www.sendgrid.com", - "repository": "https://github.com/sendgrid/sendgrid-python/tree/master", - "logo": "https://uiux.s3.amazonaws.com/2016-logos/email-logo%402x.png" + "repository": "https://github.com/sendgrid/sendgrid-python", + "logo": "https://sendgrid.com/brand/sg-twilio/SG_Twilio_Lockup_RGBx1.png" } \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index bc4ce8e79..000000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,42 +0,0 @@ -FROM ubuntu:xenial -ENV PYTHON_VERSIONS='python2.6 python2.7 python3.4 python3.5 python3.6' \ - OAI_SPEC_URL="https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/oai_stoplight.json" - -# install testing versions of python, including old versions, from deadsnakes -RUN set -x \ - && apt-get update \ - && apt-get install -y --no-install-recommends software-properties-common \ - && apt-add-repository -y ppa:fkrull/deadsnakes \ - && apt-get update \ - && apt-get install -y --no-install-recommends $PYTHON_VERSIONS \ - git \ - curl \ - && apt-get purge -y --auto-remove software-properties-common \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /root - -# install Prism -ADD https://raw.githubusercontent.com/stoplightio/prism/master/install.sh install.sh -RUN chmod +x ./install.sh && sync && \ - ./install.sh && \ - rm ./install.sh - -# install pip, tox -ADD https://bootstrap.pypa.io/get-pip.py get-pip.py -RUN python2.7 get-pip.py && \ - pip install tox && \ - rm get-pip.py - -# set up default sendgrid env -WORKDIR /root/sources -RUN git clone https://github.com/sendgrid/sendgrid-python.git && \ - git clone https://github.com/sendgrid/python-http-client.git -WORKDIR /root -RUN ln -s /root/sources/sendgrid-python/sendgrid && \ - ln -s /root/sources/python-http-client/python_http_client - -COPY entrypoint.sh entrypoint.sh -RUN chmod +x entrypoint.sh -ENTRYPOINT ["./entrypoint.sh"] -CMD ["--mock"] diff --git a/docker/README.md b/docker/README.md deleted file mode 100644 index 8d89da654..000000000 --- a/docker/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# Supported tags and respective `Dockerfile` links - - `v5.3.0`, `latest` [(Dockerfile)](https://github.com/sendgrid/sendgrid-python/blob/master/docker/Dockerfile) - - `v5.2.1` - - `v5.2.0` - - `v5.1.0` - - `v5.0.1` - - `v5.0.0` - - `v4.2.1` - - `v4.2.0` - - `v4.1.0` - - `v4.0.0` - - `v3.6.5` - - `v3.6.4` - - `v3.6.3` - - `v3.6.2` - - `v3.3.0` - - `v3.2.3` - - `v3.2.2` - - `v3.2.1` - - `v3.2.0` -# Quick reference - - **Where to get help:** - [Contact SendGrid Support](https://support.sendgrid.com/hc/en-us) - - - **Where to file issues:** - https://github.com/sendgrid/sendgrid-python/issues - - - **Where to get more info:** - [USAGE.md](https://github.com/sendgrid/sendgrid-python/blob/master/docker/USAGE.md) - - - **Maintained by:** - [SendGrid Inc.](https://sendgrid.com) - -# Usage examples - - Most recent version: `docker run -it sendgrid/sendgrid-python`. - - Old version: `docker run -it sendgrid/sendgrid-python:v4.2.0` - - Old version predating this Docker image: - ```sh-session - $ git clone https://github.com/sendgrid/sendgrid-python.git --branch v3.6.1 - $ realpath sendgrid-python - /path/to/sendgrid-python - $ docker run -it -v /path/to/sendgrid-python:/mnt/sendgrid-python sendgrid/sendgrid-python - ``` - - Your own fork: - ```sh-session - $ git clone https://github.com/you/cool-sendgrid-python.git - $ realpath cool-sendgrid-python - /path/to/cool-sendgrid-python - $ docker run -it -v /path/to/cool-sendgrid-python:/mnt/sendgrid-python sendgrid/sendgrid-python - ``` - -For more detailed information, see [USAGE.md](https://github.com/sendgrid/sendgrid-python/blob/master/docker/USAGE.md). - -# About - -sendgrid-python is guided and supported by the SendGrid [Developer Experience Team](mailto:dx@sendgrid.com). - -sendgrid-python is maintained and funded by SendGrid, Inc. The names and logos for sendgrid-python are trademarks of SendGrid, Inc. - -![SendGrid Logo](https://uiux.s3.amazonaws.com/2016-logos/email-logo%402x.png) diff --git a/docker/USAGE.md b/docker/USAGE.md deleted file mode 100644 index cd543c402..000000000 --- a/docker/USAGE.md +++ /dev/null @@ -1,84 +0,0 @@ -You can use Docker to easily try out or test sendgrid-python. - - -# Quickstart - -1. Install Docker on your machine. -2. Run `docker run -it sendgrid/sendgrid-python`. - - -# Info - -This Docker image contains - - `sendgrid-python` and `python-http-client` - - Stoplight's Prism, which lets you try out the API without actually sending email - - `tox` and all supported Python versions, set up to test `sendgrid-python` or your own fork - -Run it in interactive mode with `-it`. - -You can mount repositories in the `/mnt/sendgrid-python` and `/mnt/python-http-client` directories to use them instead of the default SendGrid libraries. Read on for more info. - - -# Options - -## Using an old version - -The easiest way to use an old version is to use an [old tag](https://github.com/sendgrid/sendgrid-python/releases). - -```sh-session -$ docker run -it sendgrid/sendgrid-python:v3.6.1 -``` - -Tags from before this Docker image was created might not exist yet. You may [manually download](#Versions) [old versions](https://github.com/sendgrid/sendgrid-python/releases) in order to use them. - - -## Specifying specific versions - -To use different versions of sendgrid-python or python-http-client - for instance, to replicate your production setup - mount them with the `-v :` option. When you put either repository under `/mnt`, the container will automatically detect it and make the proper symlinks. You can edit these files from the host machine while the container is running. - -For instance, to install sendgrid-python 3.6.1 and use the current python-http-client: - -```sh-session -$ git clone https://github.com/sendgrid/sendgrid-python.git --branch v3.6.1 -$ realpath sendgrid-python -/path/to/sendgrid-python -$ docker run -it -v /path/to/sendgrid-python:/mnt/sendgrid-python sendgrid/sendgrid-python -``` - -To install sendgrid-python v3.6.1 and use an older version of python-http-client: - -```sh-session -$ git clone https://github.com/sendgrid/sendgrid-python.git --branch v3.6.1 -$ realpath sendgrid-python -/path/to/sendgrid-python -$ git clone https://github.com/sendgrid/python-http-client.git --branch v1.2.4 -$ realpath python-http-client -/path/to/python-http-client -$ docker run -it -v /path/to/sendgrid-python:/mnt/sendgrid-python \ -> -v /path/to/python-http-client:/mnt/python-http-client \ -> sendgrid/sendgrid-python -``` - -## Specifying your own fork: - -```sh-session -$ git clone https://github.com/you/cool-sendgrid-python.git -$ realpath cool-sendgrid-python -/path/to/cool-sendgrid-python -$ docker run -it -v /path/to/cool-sendgrid-python:/mnt/sendgrid-python sendgrid/sendgrid-python -``` - -Note that the paths you specify in `-v` must be absolute. - - -# Testing -Testing is easy! Run the container, `cd sendgrid`, and run `tox`. - - -# About - -sendgrid-python is guided and supported by the SendGrid [Developer Experience Team](mailto:dx@sendgrid.com). - -sendgrid-python is maintained and funded by SendGrid, Inc. The names and logos for sendgrid-python are trademarks of SendGrid, Inc. - -![SendGrid Logo](https://uiux.s3.amazonaws.com/2016-logos/email-logo%402x.png) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh deleted file mode 100644 index 560d80a35..000000000 --- a/docker/entrypoint.sh +++ /dev/null @@ -1,48 +0,0 @@ -#! /bin/bash -clear - -# check for + link mounted libraries: -if [ -d /mnt/sendgrid-python ] -then - rm /root/sendgrid - ln -s /mnt/sendgrid-python/sendgrid - echo "Linked mounted sendgrid-python's code to /root/sendgrid" -fi -if [ -d /mnt/python_http_client ] -then - rm /root/python_http_client - ln -s /mnt/python-http-client/python_http_client - echo "Linked mounted python-http-client's code to /root/python_http_client" -fi - -SENDGRID_PYTHON_VERSION=$(python2.7 -c 'import sendgrid; print(sendgrid.__version__)') -echo "Welcome to sendgrid-python docker v${SENDGRID_PYTHON_VERSION}." -echo - -if [ "$1" != "--no-mock" ] -then - echo "Starting Prism in mock mode. Calls made to Prism will not actually send emails." - echo "Disable this by running this container with --no-mock." - prism run --mock --spec $OAI_SPEC_URL 2> /dev/null & -else - echo "Starting Prism in live (--no-mock) mode. Calls made to Prism will send emails." - prism run --spec $OAI_SPEC_URL 2> /dev/null & -fi -echo "To use Prism, make API calls to localhost:4010. For example," -echo " sg = sendgrid.SendGridAPIClient(" -echo " host='http://localhost:4010/'," -echo " api_key=os.environ.get('SENDGRID_API_KEY_CAMPAIGNS'))" -echo "To stop Prism, run \"kill $!\" from the shell." - -echo -echo "Starting Python. Type \"import sendgrid\" to get started; return to shell with exit()." -echo - -python2.7 - -echo -echo "To get back into Python, run one of the installed versions:" -echo " $PYTHON_VERSIONS" -echo "To test sendgrid-python, \"cd sendgrid\" and run \"tox\"." -echo -exec $SHELL \ No newline at end of file diff --git a/examples/accesssettings/accesssettings.py b/examples/accesssettings/accesssettings.py index aac0e4a54..17d115f9b 100644 --- a/examples/accesssettings/accesssettings.py +++ b/examples/accesssettings/accesssettings.py @@ -1,9 +1,8 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Retrieve all recent access attempts # @@ -20,17 +19,17 @@ # POST /access_settings/whitelist # data = { - "ips": [ - { - "ip": "192.168.1.1" - }, - { - "ip": "192.*.*.*" - }, - { - "ip": "192.168.1.3/32" - } - ] + "ips": [ + { + "ip": "192.168.1.1" + }, + { + "ip": "192.*.*.*" + }, + { + "ip": "192.168.1.3/32" + } + ] } response = sg.client.access_settings.whitelist.post(request_body=data) print(response.status_code) @@ -51,11 +50,11 @@ # DELETE /access_settings/whitelist # data = { - "ids": [ - 1, - 2, - 3 - ] + "ids": [ + 1, + 2, + 3 + ] } response = sg.client.access_settings.whitelist.delete(request_body=data) print(response.status_code) @@ -81,4 +80,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/alerts/alerts.py b/examples/alerts/alerts.py index e30d48748..cdfe008fd 100644 --- a/examples/alerts/alerts.py +++ b/examples/alerts/alerts.py @@ -1,18 +1,17 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Create a new Alert # # POST /alerts # data = { - "email_to": "example@example.com", - "frequency": "daily", - "type": "stats_notification" + "email_to": "example@example.com", + "frequency": "daily", + "type": "stats_notification" } response = sg.client.alerts.post(request_body=data) print(response.status_code) @@ -33,7 +32,7 @@ # PATCH /alerts/{alert_id} # data = { - "email_to": "example@example.com" + "email_to": "example@example.com" } alert_id = "test_url_param" response = sg.client.alerts._(alert_id).patch(request_body=data) @@ -60,4 +59,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/apikeys/apikeys.py b/examples/apikeys/apikeys.py index 42c3afa10..3e612cb15 100644 --- a/examples/apikeys/apikeys.py +++ b/examples/apikeys/apikeys.py @@ -1,22 +1,21 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Create API keys # # POST /api_keys # data = { - "name": "My API Key", - "sample": "data", - "scopes": [ - "mail.send", - "alerts.create", - "alerts.read" - ] + "name": "My API Key", + "sample": "data", + "scopes": [ + "mail.send", + "alerts.create", + "alerts.read" + ] } response = sg.client.api_keys.post(request_body=data) print(response.status_code) @@ -38,11 +37,11 @@ # PUT /api_keys/{api_key_id} # data = { - "name": "A New Hope", - "scopes": [ - "user.profile.read", - "user.profile.update" - ] + "name": "A New Hope", + "scopes": [ + "user.profile.read", + "user.profile.update" + ] } api_key_id = "test_url_param" response = sg.client.api_keys._(api_key_id).put(request_body=data) @@ -55,7 +54,7 @@ # PATCH /api_keys/{api_key_id} # data = { - "name": "A New Hope" + "name": "A New Hope" } api_key_id = "test_url_param" response = sg.client.api_keys._(api_key_id).patch(request_body=data) @@ -82,4 +81,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/asm/asm.py b/examples/asm/asm.py index 43130cf06..1b081b851 100644 --- a/examples/asm/asm.py +++ b/examples/asm/asm.py @@ -1,18 +1,17 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Create a new suppression group # # POST /asm/groups # data = { - "description": "Suggestions for products our users might like.", - "is_default": True, - "name": "Product Suggestions" + "description": "Suggestions for products our users might like.", + "is_default": True, + "name": "Product Suggestions" } response = sg.client.asm.groups.post(request_body=data) print(response.status_code) @@ -34,9 +33,9 @@ # PATCH /asm/groups/{group_id} # data = { - "description": "Suggestions for items our users might like.", - "id": 103, - "name": "Item Suggestions" + "description": "Suggestions for items our users might like.", + "id": 103, + "name": "Item Suggestions" } group_id = "test_url_param" response = sg.client.asm.groups._(group_id).patch(request_body=data) @@ -69,13 +68,14 @@ # POST /asm/groups/{group_id}/suppressions # data = { - "recipient_emails": [ - "test1@example.com", - "test2@example.com" - ] + "recipient_emails": [ + "test1@example.com", + "test2@example.com" + ] } group_id = "test_url_param" -response = sg.client.asm.groups._(group_id).suppressions.post(request_body=data) +response = sg.client.asm.groups._( + group_id).suppressions.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) @@ -95,14 +95,15 @@ # POST /asm/groups/{group_id}/suppressions/search # data = { - "recipient_emails": [ - "exists1@example.com", - "exists2@example.com", - "doesnotexists@example.com" - ] + "recipient_emails": [ + "exists1@example.com", + "exists2@example.com", + "doesnotexists@example.com" + ] } group_id = "test_url_param" -response = sg.client.asm.groups._(group_id).suppressions.search.post(request_body=data) +response = sg.client.asm.groups._( + group_id).suppressions.search.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) @@ -132,10 +133,10 @@ # POST /asm/suppressions/global # data = { - "recipient_emails": [ - "test1@example.com", - "test2@example.com" - ] + "recipient_emails": [ + "test1@example.com", + "test2@example.com" + ] } response = sg.client.asm.suppressions._("global").post(request_body=data) print(response.status_code) @@ -171,4 +172,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/browsers/browsers.py b/examples/browsers/browsers.py index c123c12e5..43152260a 100644 --- a/examples/browsers/browsers.py +++ b/examples/browsers/browsers.py @@ -1,17 +1,20 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Retrieve email statistics by browser. # # GET /browsers/stats # -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'browsers': 'test_string', 'limit': 'test_string', 'offset': 'test_string', 'start_date': '2016-01-01'} +params = {'end_date': '2016-04-01', + 'aggregated_by': 'day', + 'browsers': 'test_string', + 'limit': 'test_string', + 'offset': 'test_string', + 'start_date': '2016-01-01'} response = sg.client.browsers.stats.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/campaigns/campaigns.py b/examples/campaigns/campaigns.py index c77fc878b..dbbb6c0e1 100644 --- a/examples/campaigns/campaigns.py +++ b/examples/campaigns/campaigns.py @@ -1,33 +1,32 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Create a Campaign # # POST /campaigns # data = { - "categories": [ - "spring line" - ], - "custom_unsubscribe_url": "", - "html_content": "

Check out our spring line!

", - "ip_pool": "marketing", - "list_ids": [ - 110, - 124 - ], - "plain_content": "Check out our spring line!", - "segment_ids": [ - 110 - ], - "sender_id": 124451, - "subject": "New Products for Spring!", - "suppression_group_id": 42, - "title": "March Newsletter" + "categories": [ + "spring line" + ], + "custom_unsubscribe_url": "", + "html_content": "

Check out our spring line!

", + "ip_pool": "marketing", + "list_ids": [ + 110, + 124 + ], + "plain_content": "Check out our spring line!", + "segment_ids": [ + 110 + ], + "sender_id": 124451, + "subject": "New Products for Spring!", + "suppression_group_id": 42, + "title": "March Newsletter" } response = sg.client.campaigns.post(request_body=data) print(response.status_code) @@ -49,13 +48,13 @@ # PATCH /campaigns/{campaign_id} # data = { - "categories": [ - "summer line" - ], - "html_content": "

Check out our summer line!

", - "plain_content": "Check out our summer line!", - "subject": "New Products for Summer!", - "title": "May Newsletter" + "categories": [ + "summer line" + ], + "html_content": "

Check out our summer line!

", + "plain_content": "Check out our summer line!", + "subject": "New Products for Summer!", + "title": "May Newsletter" } campaign_id = "test_url_param" response = sg.client.campaigns._(campaign_id).patch(request_body=data) @@ -88,10 +87,11 @@ # PATCH /campaigns/{campaign_id}/schedules # data = { - "send_at": 1489451436 + "send_at": 1489451436 } campaign_id = "test_url_param" -response = sg.client.campaigns._(campaign_id).schedules.patch(request_body=data) +response = sg.client.campaigns._( + campaign_id).schedules.patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) @@ -101,7 +101,7 @@ # POST /campaigns/{campaign_id}/schedules # data = { - "send_at": 1489771528 + "send_at": 1489771528 } campaign_id = "test_url_param" response = sg.client.campaigns._(campaign_id).schedules.post(request_body=data) @@ -144,11 +144,11 @@ # POST /campaigns/{campaign_id}/schedules/test # data = { - "to": "your.email@example.com" + "to": "your.email@example.com" } campaign_id = "test_url_param" -response = sg.client.campaigns._(campaign_id).schedules.test.post(request_body=data) +response = sg.client.campaigns._( + campaign_id).schedules.test.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/categories/categories.py b/examples/categories/categories.py index 7984f0fe0..b8275713f 100644 --- a/examples/categories/categories.py +++ b/examples/categories/categories.py @@ -1,9 +1,8 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Retrieve all categories # @@ -19,7 +18,8 @@ # Retrieve Email Statistics for Categories # # GET /categories/stats # -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01', 'categories': 'test_string'} +params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, + 'offset': 1, 'start_date': '2016-01-01', 'categories': 'test_string'} response = sg.client.categories.stats.get(query_params=params) print(response.status_code) print(response.body) @@ -29,9 +29,14 @@ # Retrieve sums of email stats for each category [Needs: Stats object defined, has category ID?] # # GET /categories/stats/sums # -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} +params = {'end_date': '2016-04-01', + 'aggregated_by': 'day', + 'limit': 1, + 'sort_by_metric': 'test_string', + 'offset': 1, + 'start_date': '2016-01-01', + 'sort_by_direction': 'asc'} response = sg.client.categories.stats.sums.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/clients/clients.py b/examples/clients/clients.py index 7831ef78f..023a440db 100644 --- a/examples/clients/clients.py +++ b/examples/clients/clients.py @@ -1,15 +1,16 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Retrieve email statistics by client type. # # GET /clients/stats # -params = {'aggregated_by': 'day', 'start_date': '2016-01-01', 'end_date': '2016-04-01'} +params = {'aggregated_by': 'day', + 'start_date': '2016-01-01', + 'end_date': '2016-04-01'} response = sg.client.clients.stats.get(query_params=params) print(response.status_code) print(response.body) @@ -19,10 +20,11 @@ # Retrieve stats by a specific client type. # # GET /clients/{client_type}/stats # -params = {'aggregated_by': 'day', 'start_date': '2016-01-01', 'end_date': '2016-04-01'} +params = {'aggregated_by': 'day', + 'start_date': '2016-01-01', + 'end_date': '2016-04-01'} client_type = "test_url_param" response = sg.client.clients._(client_type).stats.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/contactdb/contactdb.py b/examples/contactdb/contactdb.py index c234d7724..b702974df 100644 --- a/examples/contactdb/contactdb.py +++ b/examples/contactdb/contactdb.py @@ -1,17 +1,16 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Create a Custom Field # # POST /contactdb/custom_fields # data = { - "name": "pet", - "type": "text" + "name": "pet", + "type": "text" } response = sg.client.contactdb.custom_fields.post(request_body=data) print(response.status_code) @@ -52,7 +51,7 @@ # POST /contactdb/lists # data = { - "name": "your list name" + "name": "your list name" } response = sg.client.contactdb.lists.post(request_body=data) print(response.status_code) @@ -73,10 +72,10 @@ # DELETE /contactdb/lists # data = [ - 1, - 2, - 3, - 4 + 1, + 2, + 3, + 4 ] response = sg.client.contactdb.lists.delete(request_body=data) print(response.status_code) @@ -88,11 +87,13 @@ # PATCH /contactdb/lists/{list_id} # data = { - "name": "newlistname" + "name": "newlistname" } params = {'list_id': 1} list_id = "test_url_param" -response = sg.client.contactdb.lists._(list_id).patch(request_body=data, query_params=params) +response = sg.client.contactdb.lists._(list_id).patch( + request_body=data, + query_params=params) print(response.status_code) print(response.body) print(response.headers) @@ -124,11 +125,12 @@ # POST /contactdb/lists/{list_id}/recipients # data = [ - "recipient_id1", - "recipient_id2" + "recipient_id1", + "recipient_id2" ] list_id = "test_url_param" -response = sg.client.contactdb.lists._(list_id).recipients.post(request_body=data) +response = sg.client.contactdb.lists._( + list_id).recipients.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) @@ -139,7 +141,8 @@ params = {'page': 1, 'page_size': 1} list_id = "test_url_param" -response = sg.client.contactdb.lists._(list_id).recipients.get(query_params=params) +response = sg.client.contactdb.lists._( + list_id).recipients.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) @@ -150,7 +153,8 @@ list_id = "test_url_param" recipient_id = "test_url_param" -response = sg.client.contactdb.lists._(list_id).recipients._(recipient_id).post() +response = sg.client.contactdb.lists._( + list_id).recipients._(recipient_id).post() print(response.status_code) print(response.body) print(response.headers) @@ -162,7 +166,8 @@ params = {'recipient_id': 1, 'list_id': 1} list_id = "test_url_param" recipient_id = "test_url_param" -response = sg.client.contactdb.lists._(list_id).recipients._(recipient_id).delete(query_params=params) +response = sg.client.contactdb.lists._(list_id).recipients._( + recipient_id).delete(query_params=params) print(response.status_code) print(response.body) print(response.headers) @@ -172,11 +177,11 @@ # PATCH /contactdb/recipients # data = [ - { - "email": "jones@example.com", - "first_name": "Guy", - "last_name": "Jones" - } + { + "email": "jones@example.com", + "first_name": "Guy", + "last_name": "Jones" + } ] response = sg.client.contactdb.recipients.patch(request_body=data) print(response.status_code) @@ -188,18 +193,18 @@ # POST /contactdb/recipients # data = [ - { - "age": 25, - "email": "example@example.com", - "first_name": "", - "last_name": "User" - }, - { - "age": 25, - "email": "example2@example.com", - "first_name": "Example", - "last_name": "User" - } + { + "age": 25, + "email": "example@example.com", + "first_name": "", + "last_name": "User" + }, + { + "age": 25, + "email": "example2@example.com", + "first_name": "Example", + "last_name": "User" + } ] response = sg.client.contactdb.recipients.post(request_body=data) print(response.status_code) @@ -221,8 +226,8 @@ # DELETE /contactdb/recipients # data = [ - "recipient_id1", - "recipient_id2" + "recipient_id1", + "recipient_id2" ] response = sg.client.contactdb.recipients.delete(request_body=data) print(response.status_code) @@ -301,28 +306,28 @@ # POST /contactdb/segments # data = { - "conditions": [ - { - "and_or": "", - "field": "last_name", - "operator": "eq", - "value": "Miller" - }, - { - "and_or": "and", - "field": "last_clicked", - "operator": "gt", - "value": "01/02/2015" - }, - { - "and_or": "or", - "field": "clicks.campaign_identifier", - "operator": "eq", - "value": "513" - } - ], - "list_id": 4, - "name": "Last Name Miller" + "conditions": [ + { + "and_or": "", + "field": "last_name", + "operator": "eq", + "value": "Miller" + }, + { + "and_or": "and", + "field": "last_clicked", + "operator": "gt", + "value": "01/02/2015" + }, + { + "and_or": "or", + "field": "clicks.campaign_identifier", + "operator": "eq", + "value": "513" + } + ], + "list_id": 4, + "name": "Last Name Miller" } response = sg.client.contactdb.segments.post(request_body=data) print(response.status_code) @@ -343,20 +348,22 @@ # PATCH /contactdb/segments/{segment_id} # data = { - "conditions": [ - { - "and_or": "", - "field": "last_name", - "operator": "eq", - "value": "Miller" - } - ], - "list_id": 5, - "name": "The Millers" + "conditions": [ + { + "and_or": "", + "field": "last_name", + "operator": "eq", + "value": "Miller" + } + ], + "list_id": 5, + "name": "The Millers" } params = {'segment_id': 'test_string'} segment_id = "test_url_param" -response = sg.client.contactdb.segments._(segment_id).patch(request_body=data, query_params=params) +response = sg.client.contactdb.segments._(segment_id).patch( + request_body=data, + query_params=params) print(response.status_code) print(response.body) print(response.headers) @@ -378,7 +385,8 @@ params = {'delete_contacts': 'true'} segment_id = "test_url_param" -response = sg.client.contactdb.segments._(segment_id).delete(query_params=params) +response = sg.client.contactdb.segments._( + segment_id).delete(query_params=params) print(response.status_code) print(response.body) print(response.headers) @@ -389,8 +397,8 @@ params = {'page': 1, 'page_size': 1} segment_id = "test_url_param" -response = sg.client.contactdb.segments._(segment_id).recipients.get(query_params=params) +response = sg.client.contactdb.segments._( + segment_id).recipients.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/dataresidency/set_region.py b/examples/dataresidency/set_region.py new file mode 100644 index 000000000..9aae2611f --- /dev/null +++ b/examples/dataresidency/set_region.py @@ -0,0 +1,37 @@ +import sendgrid +import os + +from sendgrid import Email, To, Content, Mail + +# Example 1 +# setting region to be "global" + +sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) +from_email = Email("example@abc.com") +to_email = To("example@abc.com") +subject = "Sending with SendGrid is Fun" +content = Content("text/plain", "and easy to do anywhere, even with Python") +mail = Mail(from_email, to_email, subject, content) +sg.set_sendgrid_data_residency("global") +print(sg.client.host) +response = sg.client.mail.send.post(request_body=mail.get()) +print(response) +print(response.status_code) +print(response.body) +print(response.headers) + +# Example 2 +# setting region to "eu" +sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY_EU')) +sg.set_sendgrid_data_residency("eu") +from_email = Email("example@abc.com") +to_email = To("example@abc.com") +subject = "Sending with SendGrid is Fun" +content = Content("text/plain", "and easy to do anywhere, even with Python") +print(sg.client.host) +mail = Mail(from_email, to_email, subject, content) +response = sg.client.mail.send.post(request_body=mail.get()) +print(response) +print(response.status_code) +print(response.body) +print(response.headers) \ No newline at end of file diff --git a/examples/devices/devices.py b/examples/devices/devices.py index 108e98452..50c96243d 100644 --- a/examples/devices/devices.py +++ b/examples/devices/devices.py @@ -1,17 +1,18 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Retrieve email statistics by device type. # # GET /devices/stats # -params = {'aggregated_by': 'day', 'limit': 1, 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} +params = {'aggregated_by': 'day', 'limit': 1, + 'start_date': '2016-01-01', + 'end_date': '2016-04-01', + 'offset': 1} response = sg.client.devices.stats.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/geo/geo.py b/examples/geo/geo.py index 7d58ec085..64265b201 100644 --- a/examples/geo/geo.py +++ b/examples/geo/geo.py @@ -1,17 +1,20 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Retrieve email statistics by country and state/province. # # GET /geo/stats # -params = {'end_date': '2016-04-01', 'country': 'US', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01'} +params = {'end_date': '2016-04-01', + 'country': 'US', + 'aggregated_by': 'day', + 'limit': 1, + 'offset': 1, + 'start_date': '2016-01-01'} response = sg.client.geo.stats.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/helpers/README.md b/examples/helpers/README.md new file mode 100644 index 000000000..8d7594d44 --- /dev/null +++ b/examples/helpers/README.md @@ -0,0 +1,73 @@ +## Using helper class to send emails +You can use helper classes to customize the process of sending emails using SendGrid. Each process (such as sending a mock email, +building attachments, configuring settings, building personalizations, etc.) are made easy using helpers. All you need is a file with +all the classes imported and you can start sending emails! + +> Note: You will need move this file to the root directory of this project to execute properly. + +### Creating a simple email object and sending it +The example [here](https://github.com/sendgrid/sendgrid-python/blob/0b683169b08d3a7c204107cd333be33053297e74/examples/helpers/mail_example.py#L9) +defines minimum requirement to send an email. +``` + from_email = Email("test@example.com") + subject = "Hello World from the SendGrid Python Library" + to_email = Email("test@example.com") +``` +You can use `Email` class to define a mail id. + +``` +content = Content("text/plain", "some text here") +``` +The `Content` class takes mainly two parameters: MIME type and the actual content of the email, it then returns the JSON-ready representation of this content. + +``` + mail = Mail(from_email, to_email, subject, content) +``` +After adding the above we create a mail object using `Mail` class, it takes the following parameters: email address to send from, subject line of emails, email address to send to, content of the message. +For more information on parameters and usage, see [here](../mail/mail.py) + +### Creating Personalizations + +The personalization helper can be used to create personalizations and customize various aspects of an email. See example [here](mail_example.py) in `build_multiple_emails_personalized()`, and refer [here](https://docs.sendgrid.com/for-developers/sending-email/personalizations) for more documentation. +``` + mock_personalization = Personalization() + + for to_addr in personalization['to_list']: + mock_personalization.add_to(to_addr) + + mock_personalization.set_from(from_addr) + mock_personalization.add_cc(cc_addr) + # etc... +``` + +### Creating Attachments + +To create attachments, we use the `Attachment` class and make sure the content is base64 encoded before passing it into attachment.content. +``` + attachment = Attachment() + attachment.content = ("TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNl" + "Y3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12") +``` +Another example: [Link](../../use_cases/attachment.md) + +### Managing Settings + +To configure settings in mail, you can use the `MailSettings` class. The class takes some [parameters](../mailsettings/mailsettings.py#L1)(such as bcc_settings, bypass_list_management, footer_settings, sandbox_mode) + +To add tracking settings, you can add `TrackingSettings` class. See example [here](mail_example.py#L118) and parameters and usage [here](../trackingsettings/trackingsettings.py). + +### Sending email + +After you have configured every component and added your own functions, you can send emails. +``` + sg = SendGridAPIClient() + data = build_kitchen_sink() + response = sg.send(data) +``` +Make sure you have [environment variable](../../TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key) set up! +Full example [here](https://github.com/sendgrid/sendgrid-python/blob/0b683169b08d3a7c204107cd333be33053297e74/examples/helpers/mail_example.py#L203). + +### Using Dynamic Templates +You can use dynamic (handlebars) transactional templates to make things easy and less time taking. To make this work, you should have dynamic template created within your SendGrid account. + +See Full example [here](https://github.com/sendgrid/sendgrid-python/blob/0b683169b08d3a7c204107cd333be33053297e74/examples/helpers/mail_example.py#L221). diff --git a/examples/helpers/eventwebhook/eventwebhook_example.py b/examples/helpers/eventwebhook/eventwebhook_example.py new file mode 100644 index 000000000..91ad8d64b --- /dev/null +++ b/examples/helpers/eventwebhook/eventwebhook_example.py @@ -0,0 +1,14 @@ +from sendgrid.helpers.eventwebhook import EventWebhook, EventWebhookHeader + +def is_valid_signature(request): + public_key = 'base64-encoded public key' + + event_webhook = EventWebhook() + ec_public_key = event_webhook.convert_public_key_to_ecdsa(public_key) + + return event_webhook.verify_signature( + request.text, + request.headers[EventWebhookHeader.SIGNATURE], + request.headers[EventWebhookHeader.TIMESTAMP], + ec_public_key + ) diff --git a/examples/helpers/mail/mail_example.py b/examples/helpers/mail/mail_example.py deleted file mode 100644 index bfd8ea718..000000000 --- a/examples/helpers/mail/mail_example.py +++ /dev/null @@ -1,219 +0,0 @@ -import json -import os -import urllib2 -from sendgrid.helpers.mail import * -from sendgrid import * - -# NOTE: you will need move this file to the root -# directory of this project to execute properly. - - -def build_hello_email(): - """Minimum required to send an email""" - from_email = Email("test@example.com") - subject = "Hello World from the SendGrid Python Library" - to_email = Email("test@example.com") - content = Content("text/plain", "some text here") - mail = Mail(from_email, subject, to_email, content) - mail.personalizations[0].add_to(Email("test2@example.com")) - - return mail.get() - - -def build_personalization(personalization): - """Build personalization mock instance from a mock dict""" - mock_personalization = Personalization() - for to_addr in personalization['to_list']: - personalization.add_to(to_addr) - - for cc_addr in personalization['cc_list']: - personalization.add_to(cc_addr) - - for bcc_addr in personalization['bcc_list']: - personalization.add_bc(bcc_addr) - - for header in personalization['headers']: - personalization.add_header(header) - - for substitution in personalization['substitutions']: - personalization.add_substitution(substitution) - - for arg in personalization['custom_args']: - personalization.add_custom_arg(arg) - - personalization.subject = personalization['subject'] - personalization.send_at = personalization['send_at'] - return mock_personalization - - -def get_mock_personalization_dict(): - """Get a dict of personalization mock.""" - mock_pers = dict() - - mock_pers['to_list'] = [Email("test1@example.com", - "Example User"), - Email("test2@example.com", - "Example User")] - - mock_pers['cc_list'] = [Email("test3@example.com", - "Example User"), - Email("test4@example.com", - "Example User")] - - mock_pers['bcc_list'] = [Email("test5@example.com"), - Email("test6@example.com")] - - mock_pers['subject'] = ("Hello World from the Personalized " - "SendGrid Python Library") - - mock_pers['headers'] = [Header("X-Test", "test"), - Header("X-Mock", "true")] - - mock_pers['substitutions'] = [Substitution("%name%", "Example User"), - Substitution("%city%", "Denver")] - - mock_pers['custom_args'] = [CustomArg("user_id", "343"), - CustomArg("type", "marketing")] - - mock_pers['send_at'] = 1443636843 - return mock_pers - - -def build_attachment1(): - """Build attachment mock.""" - attachment = Attachment() - attachment.content = ("TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNl" - "Y3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12") - attachment.type = "application/pdf" - attachment.filename = "balance_001.pdf" - attachment.disposition = "attachment" - attachment.content_id = "Balance Sheet" - return attachment - - -def build_attachment2(): - """Build attachment mock.""" - attachment = Attachment() - attachment.content = "BwdW" - attachment.type = "image/png" - attachment.filename = "banner.png" - attachment.disposition = "inline" - attachment.content_id = "Banner" - return attachment - - -def build_mail_settings(): - """Build mail settings mock.""" - mail_settings = MailSettings() - mail_settings.bcc_settings = BCCSettings(True, Email("test@example.com")) - mail_settings.bypass_list_management = BypassListManagement(True) - mail_settings.footer_settings = FooterSettings(True, "Footer Text", - ("Footer " - "Text")) - mail_settings.sandbox_mode = SandBoxMode(True) - mail_settings.spam_check = SpamCheck(True, 1, - "https://spamcatcher.sendgrid.com") - return mail_settings - - -def build_tracking_settings(): - """Build tracking settings mock.""" - tracking_settings = TrackingSettings() - tracking_settings.click_tracking = ClickTracking(True, True) - tracking_settings.open_tracking = OpenTracking(True, - ("Optional tag to " - "replace with the" - "open image in the " - "body of the message")) - - subs_track = SubscriptionTracking(True, - ("text to insert into the " - "text/plain portion of the" - " message"), - ("html to insert " - "into the text/html portion of " - "the message"), - ("Optional tag to replace with " - "the open image in the body of " - "the message")) - - tracking_settings.subscription_tracking = subs_track - tracking_settings.ganalytics = Ganalytics(True, "some source", - "some medium", "some term", - "some_content", "some_campaign") - return tracking_settings - - -def build_kitchen_sink(): - """All settings set""" - mail = Mail() - - mail.from_email = Email("test@example.com", "Example User") - mail.subject = "Hello World from the SendGrid Python Library" - - personalization = get_mock_personalization_dict() - mail.add_personalization(build_personalization(personalization)) - mail.add_personalization(build_personalization(personalization)) - - mail.add_content(Content("text/plain", "some text here")) - mail.add_content(Content("text/html", ("some text " - "here"))) - - mail.add_attachment(build_attachment1()) - mail.add_attachment(build_attachment2()) - - mail.template_id = "13b8f94f-bcae-4ec6-b752-70d6cb59f932" - - mail.add_section(Section("%section1%", "Substitution Text for Section 1")) - mail.add_section(Section("%section2%", "Substitution Text for Section 2")) - - mail.add_header(Header("X-Test1", "test1")) - mail.add_header(Header("X-Test3", "test2")) - - mail.add_category(Category("May")) - mail.add_category(Category("2016")) - - mail.add_custom_arg(CustomArg("campaign", "welcome")) - mail.add_custom_arg(CustomArg("weekday", "morning")) - - mail.send_at = 1443636842 - - # This must be a valid [batch ID] - # (https://sendgrid.com/docs/API_Reference/SMTP_API/scheduling_parameters.html) to work - # mail.set_batch_id("N2VkYjBjYWItMGU4OC0xMWU2LWJhMzYtZjQ1Yzg5OTBkNzkxLWM5ZTUyZjNhOA") - mail.asm = ASM(99, [4, 5, 6, 7, 8]) - mail.ip_pool_name = "24" - mail.mail_settings = build_mail_settings() - mail.tracking_settings = build_tracking_settings() - mail.reply_to = Email("test@example.com") - - return mail.get() - - -def send_hello_email(): - # Assumes you set your environment variable: - # https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key - sg = SendGridAPIClient() - data = build_hello_email() - response = sg.client.mail.send.post(request_body=data) - print(response.status_code) - print(response.headers) - print(response.body) - - -def send_kitchen_sink(): - # Assumes you set your environment variable: - # https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key - sg = SendGridAPIClient() - data = build_kitchen_sink() - response = sg.client.mail.send.post(request_body=data) - print(response.status_code) - print(response.headers) - print(response.body) - - -# this will actually send an email -send_hello_email() - -# this will only send an email if you set SandBox Mode to False -send_kitchen_sink() diff --git a/examples/helpers/mail_example.py b/examples/helpers/mail_example.py new file mode 100644 index 000000000..f6905787b --- /dev/null +++ b/examples/helpers/mail_example.py @@ -0,0 +1,382 @@ +import os +import json + +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import * + + +# NOTE: you will need move this file to the root +# directory of this project to execute properly. + + +def build_hello_email(): + ## Send a Single Email to a Single Recipient + + message = Mail(from_email=From('from@example.com', 'Example From Name'), + to_emails=To('to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), + html_content=HtmlContent('and easy to do anywhere, even with Python')) + + try: + print(json.dumps(message.get(), sort_keys=True, indent=4)) + return message.get() + + except SendGridException as e: + print(e.message) + + mock_personalization = Personalization() + personalization_dict = get_mock_personalization_dict() + + for cc_addr in personalization_dict['cc_list']: + mock_personalization.add_to(cc_addr) + + for bcc_addr in personalization_dict['bcc_list']: + mock_personalization.add_bcc(bcc_addr) + + for header in personalization_dict['headers']: + mock_personalization.add_header(header) + + for substitution in personalization_dict['substitutions']: + mock_personalization.add_substitution(substitution) + + for arg in personalization_dict['custom_args']: + mock_personalization.add_custom_arg(arg) + + mock_personalization.subject = personalization_dict['subject'] + mock_personalization.send_at = personalization_dict['send_at'] + + message.add_personalization(mock_personalization) + + return message + +def get_mock_personalization_dict(): + """Get a dict of personalization mock.""" + mock_pers = dict() + + mock_pers['to_list'] = [To("test1@example.com", + "Example User"), + To("test2@example.com", + "Example User")] + + mock_pers['cc_list'] = [To("test3@example.com", + "Example User"), + To("test4@example.com", + "Example User")] + + mock_pers['bcc_list'] = [To("test5@example.com"), + To("test6@example.com")] + + mock_pers['subject'] = ("Hello World from the Personalized " + "SendGrid Python Library") + + mock_pers['headers'] = [Header("X-Test", "test"), + Header("X-Mock", "true")] + + mock_pers['substitutions'] = [Substitution("%name%", "Example User"), + Substitution("%city%", "Denver")] + + mock_pers['custom_args'] = [CustomArg("user_id", "343"), + CustomArg("type", "marketing")] + + mock_pers['send_at'] = 1443636843 + return mock_pers + +def build_multiple_emails_personalized(): + # Note that the domain for all From email addresses must match + + message = Mail(from_email=From('from@example.com', 'Example From Name'), + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), + html_content=HtmlContent('and easy to do anywhere, even with Python')) + + mock_personalization = Personalization() + mock_personalization.add_to(To('test@example.com', 'Example User 1')) + mock_personalization.add_cc(Cc('test1@example.com', 'Example User 2')) + message.add_personalization(mock_personalization) + + mock_personalization_2 = Personalization() + mock_personalization_2.add_to(To('test2@example.com', 'Example User 3')) + mock_personalization_2.set_from(From('from@example.com', 'Example From Name 2')) + mock_personalization_2.add_bcc(Bcc('test3@example.com', 'Example User 4')) + message.add_personalization(mock_personalization_2) + + try: + print(json.dumps(message.get(), sort_keys=True, indent=4)) + return message.get() + + except SendGridException as e: + print(e.message) + + return message + +def build_attachment1(): + """Build attachment mock. Make sure your content is base64 encoded before passing into attachment.content. + Another example: https://github.com/sendgrid/sendgrid-python/blob/HEAD/use_cases/attachment.md""" + + attachment = Attachment() + attachment.file_content = ("TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNl" + "Y3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12") + attachment.file_type = "application/pdf" + attachment.file_name = "balance_001.pdf" + attachment.disposition = "attachment" + attachment.content_id = "Balance Sheet" + return attachment + + +def build_attachment2(): + """Build attachment mock.""" + attachment = Attachment() + attachment.file_content = "BwdW" + attachment.file_type = "image/png" + attachment.file_name = "banner.png" + attachment.disposition = "inline" + attachment.content_id = "Banner" + return attachment + +def build_kitchen_sink(): + """All settings set""" + from sendgrid.helpers.mail import ( + Mail, From, To, Cc, Bcc, Subject, PlainTextContent, + HtmlContent, SendGridException, Substitution, + Header, CustomArg, SendAt, Content, MimeType, Attachment, + FileName, FileContent, FileType, Disposition, ContentId, + TemplateId, Section, ReplyTo, Category, BatchId, Asm, + GroupId, GroupsToDisplay, IpPoolName, MailSettings, + BccSettings, BccSettingsEmail, BypassListManagement, + FooterSettings, FooterText, FooterHtml, SandBoxMode, + SpamCheck, SpamThreshold, SpamUrl, TrackingSettings, + ClickTracking, SubscriptionTracking, SubscriptionText, + SubscriptionHtml, SubscriptionSubstitutionTag, + OpenTracking, OpenTrackingSubstitutionTag, Ganalytics, + UtmSource, UtmMedium, UtmTerm, UtmContent, UtmCampaign) + import time + import datetime + + message = Mail() + + # Define Personalizations + + message.to = To('test1@sendgrid.com', 'Example User1', p=0) + message.to = [ + To('test2@sendgrid.com', 'Example User2', p=0), + To('test3@sendgrid.com', 'Example User3', p=0) + ] + + message.cc = Cc('test4@example.com', 'Example User4', p=0) + message.cc = [ + Cc('test5@example.com', 'Example User5', p=0), + Cc('test6@example.com', 'Example User6', p=0) + ] + + message.bcc = Bcc('test7@example.com', 'Example User7', p=0) + message.bcc = [ + Bcc('test8@example.com', 'Example User8', p=0), + Bcc('test9@example.com', 'Example User9', p=0) + ] + + message.subject = Subject('Sending with SendGrid is Fun 0', p=0) + + message.header = Header('X-Test1', 'Test1', p=0) + message.header = Header('X-Test2', 'Test2', p=0) + message.header = [ + Header('X-Test3', 'Test3', p=0), + Header('X-Test4', 'Test4', p=0) + ] + + message.substitution = Substitution('%name1%', 'Example Name 1', p=0) + message.substitution = Substitution('%city1%', 'Example City 1', p=0) + message.substitution = [ + Substitution('%name2%', 'Example Name 2', p=0), + Substitution('%city2%', 'Example City 2', p=0) + ] + + message.custom_arg = CustomArg('marketing1', 'true', p=0) + message.custom_arg = CustomArg('transactional1', 'false', p=0) + message.custom_arg = [ + CustomArg('marketing2', 'false', p=0), + CustomArg('transactional2', 'true', p=0) + ] + + message.send_at = SendAt(1461775051, p=0) + + message.to = To('test10@example.com', 'Example User10', p=1) + message.to = [ + To('test11@example.com', 'Example User11', p=1), + To('test12@example.com', 'Example User12', p=1) + ] + + message.cc = Cc('test13@example.com', 'Example User13', p=1) + message.cc = [ + Cc('test14@example.com', 'Example User14', p=1), + Cc('test15@example.com', 'Example User15', p=1) + ] + + message.bcc = Bcc('test16@example.com', 'Example User16', p=1) + message.bcc = [ + Bcc('test17@example.com', 'Example User17', p=1), + Bcc('test18@example.com', 'Example User18', p=1) + ] + + message.header = Header('X-Test5', 'Test5', p=1) + message.header = Header('X-Test6', 'Test6', p=1) + message.header = [ + Header('X-Test7', 'Test7', p=1), + Header('X-Test8', 'Test8', p=1) + ] + + message.substitution = Substitution('%name3%', 'Example Name 3', p=1) + message.substitution = Substitution('%city3%', 'Example City 3', p=1) + message.substitution = [ + Substitution('%name4%', 'Example Name 4', p=1), + Substitution('%city4%', 'Example City 4', p=1) + ] + + message.custom_arg = CustomArg('marketing3', 'true', p=1) + message.custom_arg = CustomArg('transactional3', 'false', p=1) + message.custom_arg = [ + CustomArg('marketing4', 'false', p=1), + CustomArg('transactional4', 'true', p=1) + ] + + message.send_at = SendAt(1461775052, p=1) + + message.subject = Subject('Sending with SendGrid is Fun 1', p=1) + + # The values below this comment are global to entire message + + message.from_email = From('help@twilio.com', 'Twilio SendGrid') + + message.reply_to = ReplyTo('help_reply@twilio.com', 'Twilio SendGrid Reply') + + message.subject = Subject('Sending with SendGrid is Fun 2') + + message.content = Content(MimeType.text, 'and easy to do anywhere, even with Python') + message.content = Content(MimeType.html, 'and easy to do anywhere, even with Python') + message.content = [ + Content('text/calendar', 'Party Time!!'), + Content('text/custom', 'Party Time 2!!') + ] + + message.attachment = Attachment(FileContent('base64 encoded content 1'), + FileName('balance_001.pdf'), + FileType('application/pdf'), + Disposition('attachment'), + ContentId('Content ID 1')) + message.attachment = [ + Attachment(FileContent('base64 encoded content 2'), + FileName('banner.png'), + FileType('image/png'), + Disposition('inline'), + ContentId('Content ID 2')), + Attachment(FileContent('base64 encoded content 3'), + FileName('banner2.png'), + FileType('image/png'), + Disposition('inline'), + ContentId('Content ID 3')) + ] + + message.template_id = TemplateId('13b8f94f-bcae-4ec6-b752-70d6cb59f932') + + message.section = Section('%section1%', 'Substitution for Section 1 Tag') + message.section = [ + Section('%section2%', 'Substitution for Section 2 Tag'), + Section('%section3%', 'Substitution for Section 3 Tag') + ] + + message.header = Header('X-Test9', 'Test9') + message.header = Header('X-Test10', 'Test10') + message.header = [ + Header('X-Test11', 'Test11'), + Header('X-Test12', 'Test12') + ] + + message.category = Category('Category 1') + message.category = Category('Category 2') + message.category = [ + Category('Category 1'), + Category('Category 2') + ] + + message.custom_arg = CustomArg('marketing5', 'false') + message.custom_arg = CustomArg('transactional5', 'true') + message.custom_arg = [ + CustomArg('marketing6', 'true'), + CustomArg('transactional6', 'false') + ] + + message.send_at = SendAt(1461775053) + + message.batch_id = BatchId("HkJ5yLYULb7Rj8GKSx7u025ouWVlMgAi") + + message.asm = Asm(GroupId(1), GroupsToDisplay([1,2,3,4])) + + message.ip_pool_name = IpPoolName("IP Pool Name") + + mail_settings = MailSettings() + mail_settings.bcc_settings = BccSettings(False, BccSettingsTo("bcc@twilio.com")) + mail_settings.bypass_list_management = BypassListManagement(False) + mail_settings.footer_settings = FooterSettings(True, FooterText("w00t"), FooterHtml("w00t!")) + mail_settings.sandbox_mode = SandBoxMode(True) + mail_settings.spam_check = SpamCheck(True, SpamThreshold(5), SpamUrl("https://example.com")) + message.mail_settings = mail_settings + + tracking_settings = TrackingSettings() + tracking_settings.click_tracking = ClickTracking(True, False) + tracking_settings.open_tracking = OpenTracking(True, OpenTrackingSubstitutionTag("open_tracking")) + tracking_settings.subscription_tracking = SubscriptionTracking( + True, + SubscriptionText("Goodbye"), + SubscriptionHtml("Goodbye!"), + SubscriptionSubstitutionTag("unsubscribe")) + tracking_settings.ganalytics = Ganalytics( + True, + UtmSource("utm_source"), + UtmMedium("utm_medium"), + UtmTerm("utm_term"), + UtmContent("utm_content"), + UtmCampaign("utm_campaign")) + message.tracking_settings = tracking_settings + + return message + +def send_multiple_emails_personalized(): + # Assumes you set your environment variable: + # https://github.com/sendgrid/sendgrid-python/blob/HEAD/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key + message = build_multiple_emails_personalized() + sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message=message) + print(response.status_code) + print(response.body) + print(response.headers) + +def send_hello_email(): + # Assumes you set your environment variable: + # https://github.com/sendgrid/sendgrid-python/blob/HEAD/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key + message = build_hello_email() + sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message=message) + print(response.status_code) + print(response.body) + print(response.headers) + + +def send_kitchen_sink(): + # Assumes you set your environment variable: + # https://github.com/sendgrid/sendgrid-python/blob/HEAD/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key + message = build_kitchen_sink() + sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message=message) + print(response.status_code) + print(response.body) + print(response.headers) + + +## this will actually send an email +# send_hello_email() + +## this will send multiple emails +# send_multiple_emails_personalized() + +## this will only send an email if you set SandBox Mode to False +# send_kitchen_sink() diff --git a/examples/helpers/stats/stats_example.py b/examples/helpers/stats/stats_example.py new file mode 100644 index 000000000..f22baa5c4 --- /dev/null +++ b/examples/helpers/stats/stats_example.py @@ -0,0 +1,101 @@ +import json +import os +from sendgrid.helpers.stats import * +from sendgrid import * + +# NOTE: you will need to move this file to the root directory of this project to execute properly. + +# Assumes you set your environment variable: +# See: https://github.com/sendgrid/sendgrid-python/blob/HEAD/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key +sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + + +def pprint_json(json_raw): + print(json.dumps(json.loads(json_raw), indent=2, sort_keys=True)) + + +def build_global_stats(): + global_stats = Stats() + global_stats.start_date = '2017-10-14' + global_stats.end_date = '2017-10-20' + global_stats.aggregated_by = 'day' + return global_stats.get() + + +def build_category_stats(): + category_stats = CategoryStats('2017-10-15', ['foo', 'bar']) + # category_stats.start_date = '2017-10-15' + # category_stats.add_category(Category("foo")) + # category_stats.add_category(Category("bar")) + return category_stats.get() + + +def build_category_stats_sums(): + category_stats = CategoryStats() + category_stats.start_date = '2017-10-15' + category_stats.limit = 5 + category_stats.offset = 1 + return category_stats.get() + + +def build_subuser_stats(): + subuser_stats = SubuserStats('2017-10-20', ['aaronmakks','foo']) + # subuser_stats.start_date = '2017-10-15' + # subuser_stats.add_subuser(Subuser("foo")) + # subuser_stats.add_subuser(Subuser("bar")) + return subuser_stats.get() + + +def build_subuser_stats_sums(): + subuser_stats = SubuserStats() + subuser_stats.start_date = '2017-10-15' + subuser_stats.limit = 5 + subuser_stats.offset = 1 + return subuser_stats.get() + + +def get_global_stats(): + stats_params = build_global_stats() + response = sg.client.stats.get(query_params=stats_params) + print(response.status_code) + print(response.headers) + pprint_json(response.body) + + +def get_category_stats(): + stats_params = build_category_stats() + response = sg.client.categories.stats.get(query_params=stats_params) + print(response.status_code) + print(response.headers) + pprint_json(response.body) + + +def get_category_stats_sums(): + stats_params = build_category_stats_sums() + response = sg.client.categories.stats.sums.get(query_params=stats_params) + print(response.status_code) + print(response.headers) + pprint_json(response.body) + + +def get_subuser_stats(): + stats_params = build_subuser_stats() + response = sg.client.subusers.stats.get(query_params=stats_params) + print(response.status_code) + print(response.headers) + pprint_json(response.body) + + +def get_subuser_stats_sums(): + stats_params = build_subuser_stats_sums() + response = sg.client.subusers.stats.sums.get(query_params=stats_params) + print(response.status_code) + print(response.headers) + pprint_json(response.body) + + +get_global_stats() +get_category_stats() +get_category_stats_sums() +get_subuser_stats() +get_subuser_stats_sums() diff --git a/examples/ips/ips.py b/examples/ips/ips.py index 6c48ae306..316d0c858 100644 --- a/examples/ips/ips.py +++ b/examples/ips/ips.py @@ -1,15 +1,15 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Retrieve all IP addresses # # GET /ips # -params = {'subuser': 'test_string', 'ip': 'test_string', 'limit': 1, 'exclude_whitelabels': 'true', 'offset': 1} +params = {'subuser': 'test_string', 'ip': 'test_string', + 'limit': 1, 'exclude_whitelabels': 'true', 'offset': 1} response = sg.client.ips.get(query_params=params) print(response.status_code) print(response.body) @@ -29,7 +29,7 @@ # POST /ips/pools # data = { - "name": "marketing" + "name": "marketing" } response = sg.client.ips.pools.post(request_body=data) print(response.status_code) @@ -50,7 +50,7 @@ # PUT /ips/pools/{pool_name} # data = { - "name": "new_pool_name" + "name": "new_pool_name" } pool_name = "test_url_param" response = sg.client.ips.pools._(pool_name).put(request_body=data) @@ -83,7 +83,7 @@ # POST /ips/pools/{pool_name}/ips # data = { - "ip": "0.0.0.0" + "ip": "0.0.0.0" } pool_name = "test_url_param" response = sg.client.ips.pools._(pool_name).ips.post(request_body=data) @@ -107,7 +107,7 @@ # POST /ips/warmup # data = { - "ip": "0.0.0.0" + "ip": "0.0.0.0" } response = sg.client.ips.warmup.post(request_body=data) print(response.status_code) @@ -152,4 +152,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/mail/mail.py b/examples/mail/mail.py index fef420e87..d2ccc80f0 100644 --- a/examples/mail/mail.py +++ b/examples/mail/mail.py @@ -1,9 +1,8 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Create a batch ID # @@ -27,148 +26,148 @@ ################################################## # v3 Mail Send # # POST /mail/send # -# This endpoint has a helper, check it out [here](https://github.com/sendgrid/sendgrid-python/blob/master/sendgrid/helpers/mail/README.md). +# This endpoint has a helper, check it out +# [here](https://github.com/sendgrid/sendgrid-python/blob/HEAD/use_cases/README.md). data = { - "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 SendGrid Team

", - "text": "Thanks,/n The SendGrid Team" - }, - "sandbox_mode": { - "enable": False - }, - "spam_check": { - "enable": True, - "post_to_url": "http://example.com/compliance", - "threshold": 3 - } - }, - "personalizations": [ - { - "bcc": [ + "asm": { + "group_id": 1, + "groups_to_display": [ + 1, + 2, + 3 + ] + }, + "attachments": [ { - "email": "sam.doe@example.com", - "name": "Sam Doe" + "content": "[BASE64 encoded content block here]", + "content_id": "ii_139db99fdb5c3704", + "disposition": "inline", + "filename": "file1.jpg", + "name": "file1", + "type": "jpg" } - ], - "cc": [ + ], + "batch_id": "[YOUR BATCH ID GOES HERE]", + "categories": [ + "category1", + "category2" + ], + "content": [ { - "email": "jane.doe@example.com", - "name": "Jane Doe" + "type": "text/html", + "value": "

Hello, world!

" } - ], - "custom_args": { - "New Argument 1": "New Value 1", - "activationAttempt": "1", + ], + "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": [ + }, + "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 SendGrid Team

", + "text": "Thanks,/n The SendGrid Team" + }, + "sandbox_mode": { + "enable": False + }, + "spam_check": { + "enable": True, + "post_to_url": "http://example.com/compliance", + "threshold": 3 + } + }, + "personalizations": [ { - "email": "john.doe@example.com", - "name": "John Doe" + "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" + }, + "sections": { + "section": { + ":sectionName1": "section 1 text", + ":sectionName2": "section 2 text" + } + }, + "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 %>." } - ] - } - ], - "reply_to": { - "email": "sam.smith@example.com", - "name": "Sam Smith" - }, - "sections": { - "section": { - ":sectionName1": "section 1 text", - ":sectionName2": "section 2 text" - } - }, - "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 receiveing these emails <% click here %>." } - } } response = sg.client.mail.send.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/mailboxproviders/mailboxproviders.py b/examples/mailboxproviders/mailboxproviders.py index 1b75ecac5..4fbf470e2 100644 --- a/examples/mailboxproviders/mailboxproviders.py +++ b/examples/mailboxproviders/mailboxproviders.py @@ -1,17 +1,20 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Retrieve email statistics by mailbox provider. # # GET /mailbox_providers/stats # -params = {'end_date': '2016-04-01', 'mailbox_providers': 'test_string', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01'} +params = {'end_date': '2016-04-01', + 'mailbox_providers': 'test_string', + 'aggregated_by': 'day', + 'limit': 1, + 'offset': 1, + 'start_date': '2016-01-01'} response = sg.client.mailbox_providers.stats.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/mailsettings/mailsettings.py b/examples/mailsettings/mailsettings.py index 18c57b960..a4d46e399 100644 --- a/examples/mailsettings/mailsettings.py +++ b/examples/mailsettings/mailsettings.py @@ -1,9 +1,8 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Retrieve all mail settings # @@ -20,11 +19,11 @@ # PATCH /mail_settings/address_whitelist # data = { - "enabled": True, - "list": [ - "email1@example.com", - "example.com" - ] + "enabled": True, + "list": [ + "email1@example.com", + "example.com" + ] } response = sg.client.mail_settings.address_whitelist.patch(request_body=data) print(response.status_code) @@ -45,8 +44,8 @@ # PATCH /mail_settings/bcc # data = { - "email": "email@example.com", - "enabled": False + "email": "email@example.com", + "enabled": False } response = sg.client.mail_settings.bcc.patch(request_body=data) print(response.status_code) @@ -67,9 +66,9 @@ # PATCH /mail_settings/bounce_purge # data = { - "enabled": True, - "hard_bounces": 5, - "soft_bounces": 5 + "enabled": True, + "hard_bounces": 5, + "soft_bounces": 5 } response = sg.client.mail_settings.bounce_purge.patch(request_body=data) print(response.status_code) @@ -90,9 +89,9 @@ # PATCH /mail_settings/footer # data = { - "enabled": True, - "html_content": "...", - "plain_content": "..." + "enabled": True, + "html_content": "...", + "plain_content": "..." } response = sg.client.mail_settings.footer.patch(request_body=data) print(response.status_code) @@ -113,8 +112,8 @@ # PATCH /mail_settings/forward_bounce # data = { - "email": "example@example.com", - "enabled": True + "email": "example@example.com", + "enabled": True } response = sg.client.mail_settings.forward_bounce.patch(request_body=data) print(response.status_code) @@ -135,8 +134,8 @@ # PATCH /mail_settings/forward_spam # data = { - "email": "", - "enabled": False + "email": "", + "enabled": False } response = sg.client.mail_settings.forward_spam.patch(request_body=data) print(response.status_code) @@ -157,7 +156,7 @@ # PATCH /mail_settings/plain_content # data = { - "enabled": False + "enabled": False } response = sg.client.mail_settings.plain_content.patch(request_body=data) print(response.status_code) @@ -178,9 +177,9 @@ # PATCH /mail_settings/spam_check # data = { - "enabled": True, - "max_score": 5, - "url": "url" + "enabled": True, + "max_score": 5, + "url": "url" } response = sg.client.mail_settings.spam_check.patch(request_body=data) print(response.status_code) @@ -201,8 +200,8 @@ # PATCH /mail_settings/template # data = { - "enabled": True, - "html_content": "<% body %>" + "enabled": True, + "html_content": "<% body %>" } response = sg.client.mail_settings.template.patch(request_body=data) print(response.status_code) @@ -217,4 +216,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/partnersettings/partnersettings.py b/examples/partnersettings/partnersettings.py index 37f77f4e6..d3675a6ba 100644 --- a/examples/partnersettings/partnersettings.py +++ b/examples/partnersettings/partnersettings.py @@ -1,9 +1,8 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Returns a list of all partner settings. # @@ -20,9 +19,9 @@ # PATCH /partner_settings/new_relic # data = { - "enable_subuser_statistics": True, - "enabled": True, - "license_key": "" + "enable_subuser_statistics": True, + "enabled": True, + "license_key": "" } response = sg.client.partner_settings.new_relic.patch(request_body=data) print(response.status_code) @@ -37,4 +36,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/scopes/scopes.py b/examples/scopes/scopes.py index 124f77d39..99519dc3e 100644 --- a/examples/scopes/scopes.py +++ b/examples/scopes/scopes.py @@ -1,9 +1,8 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Retrieve a list of scopes for which this user has access. # @@ -13,4 +12,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/whitelabel/whitelabel.py b/examples/senderauthentication/senderauthentication.py similarity index 67% rename from examples/whitelabel/whitelabel.py rename to examples/senderauthentication/senderauthentication.py index f529d3ed2..f842d9302 100644 --- a/examples/whitelabel/whitelabel.py +++ b/examples/senderauthentication/senderauthentication.py @@ -3,23 +3,23 @@ import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## -# Create a domain whitelabel. # +# Create a domain authentication. # # POST /whitelabel/domains # data = { - "automatic_security": False, - "custom_spf": True, - "default": True, - "domain": "example.com", - "ips": [ - "192.168.1.1", - "192.168.1.2" - ], - "subdomain": "news", - "username": "john@example.com" + "automatic_security": False, + "custom_spf": True, + "default": True, + "domain": "example.com", + "ips": [ + "192.168.1.1", + "192.168.1.2" + ], + "subdomain": "news", + "username": "john@example.com" } response = sg.client.whitelabel.domains.post(request_body=data) print(response.status_code) @@ -27,17 +27,18 @@ print(response.headers) ################################################## -# List all domain whitelabels. # +# List all domain authentications. # # GET /whitelabel/domains # -params = {'username': 'test_string', 'domain': 'test_string', 'exclude_subusers': 'true', 'limit': 1, 'offset': 1} +params = {'username': 'test_string', 'domain': 'test_string', + 'exclude_subusers': 'true', 'limit': 1, 'offset': 1} response = sg.client.whitelabel.domains.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) ################################################## -# Get the default domain whitelabel. # +# Get the default domain authentication. # # GET /whitelabel/domains/default # response = sg.client.whitelabel.domains.default.get() @@ -46,7 +47,7 @@ print(response.headers) ################################################## -# List the domain whitelabel associated with the given user. # +# List the domain authentication associated with the given user. # # GET /whitelabel/domains/subuser # response = sg.client.whitelabel.domains.subuser.get() @@ -55,7 +56,7 @@ print(response.headers) ################################################## -# Disassociate a domain whitelabel from a given user. # +# Disassociate a domain authentication from a given user. # # DELETE /whitelabel/domains/subuser # response = sg.client.whitelabel.domains.subuser.delete() @@ -64,12 +65,12 @@ print(response.headers) ################################################## -# Update a domain whitelabel. # +# Update a domain authentication. # # PATCH /whitelabel/domains/{domain_id} # data = { - "custom_spf": True, - "default": False + "custom_spf": True, + "default": False } domain_id = "test_url_param" response = sg.client.whitelabel.domains._(domain_id).patch(request_body=data) @@ -78,7 +79,7 @@ print(response.headers) ################################################## -# Retrieve a domain whitelabel. # +# Retrieve a domain authentication. # # GET /whitelabel/domains/{domain_id} # domain_id = "test_url_param" @@ -88,7 +89,7 @@ print(response.headers) ################################################## -# Delete a domain whitelabel. # +# Delete a domain authentication. # # DELETE /whitelabel/domains/{domain_id} # domain_id = "test_url_param" @@ -98,60 +99,61 @@ print(response.headers) ################################################## -# Associate a domain whitelabel with a given user. # +# Associate a domain authentication with a given user. # # POST /whitelabel/domains/{domain_id}/subuser # data = { - "username": "jane@example.com" + "username": "jane@example.com" } domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).subuser.post(request_body=data) +response = sg.client.whitelabel.domains._( + domain_id).subuser.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) ################################################## -# Add an IP to a domain whitelabel. # +# Add an IP to a domain authentication. # # POST /whitelabel/domains/{id}/ips # data = { - "ip": "192.168.0.1" + "ip": "192.168.0.1" } -id = "test_url_param" -response = sg.client.whitelabel.domains._(id).ips.post(request_body=data) +id_ = "test_url_param" +response = sg.client.whitelabel.domains._(id_).ips.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) ################################################## -# Remove an IP from a domain whitelabel. # +# Remove an IP from a domain authentication. # # DELETE /whitelabel/domains/{id}/ips/{ip} # -id = "test_url_param" +id_ = "test_url_param" ip = "test_url_param" -response = sg.client.whitelabel.domains._(id).ips._(ip).delete() +response = sg.client.whitelabel.domains._(id_).ips._(ip).delete() print(response.status_code) print(response.body) print(response.headers) ################################################## -# Validate a domain whitelabel. # +# Validate a domain authentication. # # POST /whitelabel/domains/{id}/validate # -id = "test_url_param" -response = sg.client.whitelabel.domains._(id).validate.post() +id_ = "test_url_param" +response = sg.client.whitelabel.domains._(id_).validate.post() print(response.status_code) print(response.body) print(response.headers) ################################################## -# Create an IP whitelabel # +# Create a reverse DNS record # # POST /whitelabel/ips # data = { - "domain": "example.com", - "ip": "192.168.1.1", - "subdomain": "email" + "domain": "example.com", + "ip": "192.168.1.1", + "subdomain": "email" } response = sg.client.whitelabel.ips.post(request_body=data) print(response.status_code) @@ -159,7 +161,7 @@ print(response.headers) ################################################## -# Retrieve all IP whitelabels # +# Create a reverse DNS record # # GET /whitelabel/ips # params = {'ip': 'test_string', 'limit': 1, 'offset': 1} @@ -169,52 +171,53 @@ print(response.headers) ################################################## -# Retrieve an IP whitelabel # +# Retrieve a reverse DNS record # # GET /whitelabel/ips/{id} # -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).get() +id_ = "test_url_param" +response = sg.client.whitelabel.ips._(id_).get() print(response.status_code) print(response.body) print(response.headers) ################################################## -# Delete an IP whitelabel # +# Delete a reverse DNS record # # DELETE /whitelabel/ips/{id} # -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).delete() +id_ = "test_url_param" +response = sg.client.whitelabel.ips._(id_).delete() print(response.status_code) print(response.body) print(response.headers) ################################################## -# Validate an IP whitelabel # +# Validate a reverse DNS record # # POST /whitelabel/ips/{id}/validate # -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).validate.post() +id_ = "test_url_param" +response = sg.client.whitelabel.ips._(id_).validate.post() print(response.status_code) print(response.body) print(response.headers) ################################################## -# Create a Link Whitelabel # +# Create a Link Branding # # POST /whitelabel/links # data = { - "default": True, - "domain": "example.com", - "subdomain": "mail" + "default": True, + "domain": "example.com", + "subdomain": "mail" } params = {'limit': 1, 'offset': 1} -response = sg.client.whitelabel.links.post(request_body=data, query_params=params) +response = sg.client.whitelabel.links.post( + request_body=data, query_params=params) print(response.status_code) print(response.body) print(response.headers) ################################################## -# Retrieve all link whitelabels # +# Retrieve all link brandings # # GET /whitelabel/links # params = {'limit': 1} @@ -224,7 +227,7 @@ print(response.headers) ################################################## -# Retrieve a Default Link Whitelabel # +# Retrieve a Default Link Branding # # GET /whitelabel/links/default # params = {'domain': 'test_string'} @@ -234,7 +237,7 @@ print(response.headers) ################################################## -# Retrieve Associated Link Whitelabel # +# Retrieve Associated Link Branding # # GET /whitelabel/links/subuser # params = {'username': 'test_string'} @@ -244,7 +247,7 @@ print(response.headers) ################################################## -# Disassociate a Link Whitelabel # +# Disassociate a Link Branding # # DELETE /whitelabel/links/subuser # params = {'username': 'test_string'} @@ -254,58 +257,58 @@ print(response.headers) ################################################## -# Update a Link Whitelabel # +# Update a Link Branding # # PATCH /whitelabel/links/{id} # data = { - "default": True + "default": True } -id = "test_url_param" -response = sg.client.whitelabel.links._(id).patch(request_body=data) +id_ = "test_url_param" +response = sg.client.whitelabel.links._(id_).patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) ################################################## -# Retrieve a Link Whitelabel # +# Retrieve a Link Branding # # GET /whitelabel/links/{id} # -id = "test_url_param" -response = sg.client.whitelabel.links._(id).get() +id_ = "test_url_param" +response = sg.client.whitelabel.links._(id_).get() print(response.status_code) print(response.body) print(response.headers) ################################################## -# Delete a Link Whitelabel # +# Delete a Link Branding # # DELETE /whitelabel/links/{id} # -id = "test_url_param" -response = sg.client.whitelabel.links._(id).delete() +id_ = "test_url_param" +response = sg.client.whitelabel.links._(id_).delete() print(response.status_code) print(response.body) print(response.headers) ################################################## -# Validate a Link Whitelabel # +# Validate a Link Branding # # POST /whitelabel/links/{id}/validate # -id = "test_url_param" -response = sg.client.whitelabel.links._(id).validate.post() +id_ = "test_url_param" +response = sg.client.whitelabel.links._(id_).validate.post() print(response.status_code) print(response.body) print(response.headers) ################################################## -# Associate a Link Whitelabel # +# Associate a Link Branding # # POST /whitelabel/links/{link_id}/subuser # data = { - "username": "jane@example.com" + "username": "jane@example.com" } link_id = "test_url_param" -response = sg.client.whitelabel.links._(link_id).subuser.post(request_body=data) +response = sg.client.whitelabel.links._( + link_id).subuser.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/senders/senders.py b/examples/senders/senders.py index f21459b71..55eb44631 100644 --- a/examples/senders/senders.py +++ b/examples/senders/senders.py @@ -1,30 +1,29 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Create a Sender Identity # # POST /senders # data = { - "address": "123 Elm St.", - "address_2": "Apt. 456", - "city": "Denver", - "country": "United States", - "from": { - "email": "from@example.com", - "name": "Example INC" - }, - "nickname": "My Sender ID", - "reply_to": { - "email": "replyto@example.com", - "name": "Example INC" - }, - "state": "Colorado", - "zip": "80202" + "address": "123 Elm St.", + "address_2": "Apt. 456", + "city": "Denver", + "country": "United States", + "from": { + "email": "from@example.com", + "name": "Example INC" + }, + "nickname": "My Sender ID", + "reply_to": { + "email": "replyto@example.com", + "name": "Example INC" + }, + "state": "Colorado", + "zip": "80202" } response = sg.client.senders.post(request_body=data) print(response.status_code) @@ -45,21 +44,21 @@ # PATCH /senders/{sender_id} # data = { - "address": "123 Elm St.", - "address_2": "Apt. 456", - "city": "Denver", - "country": "United States", - "from": { - "email": "from@example.com", - "name": "Example INC" - }, - "nickname": "My Sender ID", - "reply_to": { - "email": "replyto@example.com", - "name": "Example INC" - }, - "state": "Colorado", - "zip": "80202" + "address": "123 Elm St.", + "address_2": "Apt. 456", + "city": "Denver", + "country": "United States", + "from": { + "email": "from@example.com", + "name": "Example INC" + }, + "nickname": "My Sender ID", + "reply_to": { + "email": "replyto@example.com", + "name": "Example INC" + }, + "state": "Colorado", + "zip": "80202" } sender_id = "test_url_param" response = sg.client.senders._(sender_id).patch(request_body=data) @@ -96,4 +95,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/stats/stats.py b/examples/stats/stats.py index a7bf3362e..cde422447 100644 --- a/examples/stats/stats.py +++ b/examples/stats/stats.py @@ -1,17 +1,16 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Retrieve global email statistics # # GET /stats # -params = {'aggregated_by': 'day', 'limit': 1, 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} +params = {'aggregated_by': 'day', 'limit': 1, + 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} response = sg.client.stats.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/subusers/subusers.py b/examples/subusers/subusers.py index 6aa91e535..0f5ba6fe0 100644 --- a/examples/subusers/subusers.py +++ b/examples/subusers/subusers.py @@ -1,22 +1,21 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Create Subuser # # POST /subusers # data = { - "email": "John@example.com", - "ips": [ - "1.1.1.1", - "2.2.2.2" - ], - "password": "johns_password", - "username": "John@example.com" + "email": "John@example.com", + "ips": [ + "1.1.1.1", + "2.2.2.2" + ], + "password": "johns_password", + "username": "John@example.com" } response = sg.client.subusers.post(request_body=data) print(response.status_code) @@ -47,7 +46,12 @@ # Retrieve email statistics for your subusers. # # GET /subusers/stats # -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01', 'subusers': 'test_string'} +params = {'end_date': '2016-04-01', + 'aggregated_by': 'day', + 'limit': 1, + 'offset': 1, + 'start_date': '2016-01-01', + 'subusers': 'test_string'} response = sg.client.subusers.stats.get(query_params=params) print(response.status_code) print(response.body) @@ -57,7 +61,12 @@ # Retrieve monthly stats for all subusers # # GET /subusers/stats/monthly # -params = {'subuser': 'test_string', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'date': 'test_string', 'sort_by_direction': 'asc'} +params = {'subuser': 'test_string', + 'limit': 1, + 'sort_by_metric': 'test_string', + 'offset': 1, + 'date': 'test_string', + 'sort_by_direction': 'asc'} response = sg.client.subusers.stats.monthly.get(query_params=params) print(response.status_code) print(response.body) @@ -67,7 +76,13 @@ # Retrieve the totals for each email statistic metric for all subusers. # # GET /subusers/stats/sums # -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} +params = {'end_date': '2016-04-01', + 'aggregated_by': 'day', + 'limit': 1, + 'sort_by_metric': 'test_string', + 'offset': 1, + 'start_date': '2016-01-01', + 'sort_by_direction': 'asc'} response = sg.client.subusers.stats.sums.get(query_params=params) print(response.status_code) print(response.body) @@ -78,7 +93,7 @@ # PATCH /subusers/{subuser_name} # data = { - "disabled": False + "disabled": False } subuser_name = "test_url_param" response = sg.client.subusers._(subuser_name).patch(request_body=data) @@ -101,7 +116,7 @@ # PUT /subusers/{subuser_name}/ips # data = [ - "127.0.0.1" + "127.0.0.1" ] subuser_name = "test_url_param" response = sg.client.subusers._(subuser_name).ips.put(request_body=data) @@ -114,8 +129,8 @@ # PUT /subusers/{subuser_name}/monitor # data = { - "email": "example@example.com", - "frequency": 500 + "email": "example@example.com", + "frequency": 500 } subuser_name = "test_url_param" response = sg.client.subusers._(subuser_name).monitor.put(request_body=data) @@ -128,8 +143,8 @@ # POST /subusers/{subuser_name}/monitor # data = { - "email": "example@example.com", - "frequency": 50000 + "email": "example@example.com", + "frequency": 50000 } subuser_name = "test_url_param" response = sg.client.subusers._(subuser_name).monitor.post(request_body=data) @@ -161,10 +176,14 @@ # Retrieve the monthly email statistics for a single subuser # # GET /subusers/{subuser_name}/stats/monthly # -params = {'date': 'test_string', 'sort_by_direction': 'asc', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1} +params = {'date': 'test_string', + 'sort_by_direction': 'asc', + 'limit': 1, + 'sort_by_metric': 'test_string', + 'offset': 1} subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).stats.monthly.get(query_params=params) +response = sg.client.subusers._( + subuser_name).stats.monthly.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/suppression/suppression.py b/examples/suppression/suppression.py index abdaef76d..430f76f35 100644 --- a/examples/suppression/suppression.py +++ b/examples/suppression/suppression.py @@ -1,9 +1,8 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Retrieve all blocks # @@ -20,11 +19,11 @@ # DELETE /suppression/blocks # data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] } response = sg.client.suppression.blocks.delete(request_body=data) print(response.status_code) @@ -66,11 +65,11 @@ # DELETE /suppression/bounces # data = { - "delete_all": True, - "emails": [ - "example@example.com", - "example2@example.com" - ] + "delete_all": True, + "emails": [ + "example@example.com", + "example2@example.com" + ] } response = sg.client.suppression.bounces.delete(request_body=data) print(response.status_code) @@ -113,11 +112,11 @@ # DELETE /suppression/invalid_emails # data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] } response = sg.client.suppression.invalid_emails.delete(request_body=data) print(response.status_code) @@ -179,11 +178,11 @@ # DELETE /suppression/spam_reports # data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] } response = sg.client.suppression.spam_reports.delete(request_body=data) print(response.status_code) @@ -199,4 +198,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/templates/templates.py b/examples/templates/templates.py index 9d3d5dd4b..9b5210191 100644 --- a/examples/templates/templates.py +++ b/examples/templates/templates.py @@ -1,16 +1,15 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Create a transactional template. # # POST /templates # data = { - "name": "example_name" + "name": "example_name" } response = sg.client.templates.post(request_body=data) print(response.status_code) @@ -31,7 +30,7 @@ # PATCH /templates/{template_id} # data = { - "name": "new_example_name" + "name": "new_example_name" } template_id = "test_url_param" response = sg.client.templates._(template_id).patch(request_body=data) @@ -64,12 +63,12 @@ # POST /templates/{template_id}/versions # data = { - "active": 1, - "html_content": "<%body%>", - "name": "example_version_name", - "plain_content": "<%body%>", - "subject": "<%subject%>", - "template_id": "ddb96bbc-9b92-425e-8979-99464621b543" + "active": 1, + "html_content": "<%body%>", + "name": "example_version_name", + "plain_content": "<%body%>", + "subject": "<%subject%>", + "template_id": "ddb96bbc-9b92-425e-8979-99464621b543" } template_id = "test_url_param" response = sg.client.templates._(template_id).versions.post(request_body=data) @@ -82,15 +81,16 @@ # PATCH /templates/{template_id}/versions/{version_id} # data = { - "active": 1, - "html_content": "<%body%>", - "name": "updated_example_name", - "plain_content": "<%body%>", - "subject": "<%subject%>" + "active": 1, + "html_content": "<%body%>", + "name": "updated_example_name", + "plain_content": "<%body%>", + "subject": "<%subject%>" } template_id = "test_url_param" version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).patch(request_body=data) +response = sg.client.templates._(template_id).versions._( + version_id).patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) @@ -123,8 +123,8 @@ template_id = "test_url_param" version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).activate.post() +response = sg.client.templates._( + template_id).versions._(version_id).activate.post() print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/trackingsettings/trackingsettings.py b/examples/trackingsettings/trackingsettings.py index 80dbe243a..b3c49f8b2 100644 --- a/examples/trackingsettings/trackingsettings.py +++ b/examples/trackingsettings/trackingsettings.py @@ -1,9 +1,8 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Retrieve Tracking Settings # @@ -20,7 +19,7 @@ # PATCH /tracking_settings/click # data = { - "enabled": True + "enabled": True } response = sg.client.tracking_settings.click.patch(request_body=data) print(response.status_code) @@ -41,14 +40,15 @@ # PATCH /tracking_settings/google_analytics # data = { - "enabled": True, - "utm_campaign": "website", - "utm_content": "", - "utm_medium": "email", - "utm_source": "sendgrid.com", - "utm_term": "" + "enabled": True, + "utm_campaign": "website", + "utm_content": "", + "utm_medium": "email", + "utm_source": "sendgrid.com", + "utm_term": "" } -response = sg.client.tracking_settings.google_analytics.patch(request_body=data) +response = sg.client.tracking_settings.google_analytics.patch( + request_body=data) print(response.status_code) print(response.body) print(response.headers) @@ -67,7 +67,7 @@ # PATCH /tracking_settings/open # data = { - "enabled": True + "enabled": True } response = sg.client.tracking_settings.open.patch(request_body=data) print(response.status_code) @@ -88,12 +88,12 @@ # PATCH /tracking_settings/subscription # data = { - "enabled": True, - "html_content": "html content", - "landing": "landing page html", - "plain_content": "text content", - "replace": "replacement tag", - "url": "url" + "enabled": True, + "html_content": "html content", + "landing": "landing page html", + "plain_content": "text content", + "replace": "replacement tag", + "url": "url" } response = sg.client.tracking_settings.subscription.patch(request_body=data) print(response.status_code) @@ -108,4 +108,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/user/user.py b/examples/user/user.py index e71a8d11e..5160f9ff8 100644 --- a/examples/user/user.py +++ b/examples/user/user.py @@ -1,9 +1,8 @@ import sendgrid -import json import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## # Get a user's account information. # @@ -28,7 +27,7 @@ # PUT /user/email # data = { - "email": "example@example.com" + "email": "example@example.com" } response = sg.client.user.email.put(request_body=data) print(response.status_code) @@ -49,8 +48,8 @@ # PUT /user/password # data = { - "new_password": "new_password", - "old_password": "old_password" + "new_password": "new_password", + "old_password": "old_password" } response = sg.client.user.password.put(request_body=data) print(response.status_code) @@ -62,9 +61,9 @@ # PATCH /user/profile # data = { - "city": "Orange", - "first_name": "Example", - "last_name": "User" + "city": "Orange", + "first_name": "Example", + "last_name": "User" } response = sg.client.user.profile.patch(request_body=data) print(response.status_code) @@ -85,8 +84,8 @@ # POST /user/scheduled_sends # data = { - "batch_id": "YOUR_BATCH_ID", - "status": "pause" + "batch_id": "YOUR_BATCH_ID", + "status": "pause" } response = sg.client.user.scheduled_sends.post(request_body=data) print(response.status_code) @@ -107,7 +106,7 @@ # PATCH /user/scheduled_sends/{batch_id} # data = { - "status": "pause" + "status": "pause" } batch_id = "test_url_param" response = sg.client.user.scheduled_sends._(batch_id).patch(request_body=data) @@ -140,8 +139,8 @@ # PATCH /user/settings/enforced_tls # data = { - "require_tls": True, - "require_valid_cert": False + "require_tls": True, + "require_valid_cert": False } response = sg.client.user.settings.enforced_tls.patch(request_body=data) print(response.status_code) @@ -162,7 +161,7 @@ # PUT /user/username # data = { - "username": "test_username" + "username": "test_username" } response = sg.client.user.username.put(request_body=data) print(response.status_code) @@ -183,19 +182,19 @@ # PATCH /user/webhooks/event/settings # data = { - "bounce": True, - "click": True, - "deferred": True, - "delivered": True, - "dropped": True, - "enabled": True, - "group_resubscribe": True, - "group_unsubscribe": True, - "open": True, - "processed": True, - "spam_report": True, - "unsubscribe": True, - "url": "url" + "bounce": True, + "click": True, + "deferred": True, + "delivered": True, + "dropped": True, + "enabled": True, + "group_resubscribe": True, + "group_unsubscribe": True, + "open": True, + "processed": True, + "spam_report": True, + "unsubscribe": True, + "url": "url" } response = sg.client.user.webhooks.event.settings.patch(request_body=data) print(response.status_code) @@ -216,7 +215,7 @@ # POST /user/webhooks/event/test # data = { - "url": "url" + "url": "url" } response = sg.client.user.webhooks.event.test.post(request_body=data) print(response.status_code) @@ -228,10 +227,10 @@ # POST /user/webhooks/parse/settings # data = { - "hostname": "myhostname.com", - "send_raw": False, - "spam_check": True, - "url": "http://email.myhosthame.com" + "hostname": "myhostname.com", + "send_raw": False, + "spam_check": True, + "url": "http://email.myhosthame.com" } response = sg.client.user.webhooks.parse.settings.post(request_body=data) print(response.status_code) @@ -252,12 +251,13 @@ # PATCH /user/webhooks/parse/settings/{hostname} # data = { - "send_raw": True, - "spam_check": False, - "url": "http://newdomain.com/parse" + "send_raw": True, + "spam_check": False, + "url": "http://newdomain.com/parse" } hostname = "test_url_param" -response = sg.client.user.webhooks.parse.settings._(hostname).patch(request_body=data) +response = sg.client.user.webhooks.parse.settings._( + hostname).patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) @@ -286,9 +286,12 @@ # Retrieves Inbound Parse Webhook statistics. # # GET /user/webhooks/parse/stats # -params = {'aggregated_by': 'day', 'limit': 'test_string', 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 'test_string'} +params = {'aggregated_by': 'day', + 'limit': 'test_string', + 'start_date': '2016-01-01', + 'end_date': '2016-04-01', + 'offset': 'test_string'} response = sg.client.user.webhooks.parse.stats.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/live_test.py b/live_test.py new file mode 100644 index 000000000..d666140fb --- /dev/null +++ b/live_test.py @@ -0,0 +1,357 @@ +## Send a Single Email to a Single Recipient +import os +import json +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException + +message = Mail(from_email=From('help@twilio.com', 'Twilio SendGrid'), + to_emails=To('ethomas@twilio.com', 'Elmer Thomas'), + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), + html_content=HtmlContent('and easy to do anywhere, even with Python')) + +try: + print(json.dumps(message.get(), sort_keys=True, indent=4)) + sendgrid_client = SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message=message) + print(response.status_code) + print(response.body) + print(response.headers) +except SendGridException as e: + print(e.message) + +# Send a Single Email to Multiple Recipients +import os +import json +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException + +to_emails = [ + To('ethomas@twilio.com', 'Elmer SendGrid'), + To('elmer.thomas@gmail.com', 'Elmer Thomas') +] +message = Mail(from_email=From('help@twilio.com', 'Twilio SendGrid'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), + html_content=HtmlContent('and easy to do anywhere, even with Python')) + +try: + print(json.dumps(message.get(), sort_keys=True, indent=4)) + sendgrid_client = SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message=message) + print(response.status_code) + print(response.body) + print(response.headers) +except SendGridException as e: + print(e.message) + +# Send Multiple Emails to Multiple Recipients + +import os +import json +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException, Substitution +import time +import datetime + +to_emails = [ + To(email='ethomas@twilio.com', + name='Elmer Twilio', + substitutions={ + Substitution('-name-', 'Elmer Twilio'), + Substitution('-github-', 'http://github.com/ethomas'), + }, + subject=Subject('Override Global Subject')), + To(email='elmer.thomas@gmail.com', + name='Elmer Thomas', + substitutions={ + Substitution('-name-', 'Elmer Thomas'), + Substitution('-github-', 'http://github.com/thinkingserious'), + }) +] +ts = time.time() +global_substitutions = Substitution('-time-', datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')) +message = Mail(from_email=From('help@twilio.com', 'Twilio SendGrid'), + to_emails=to_emails, + subject=Subject('Hi -name-'), + plain_text_content=PlainTextContent('Hello -name-, your github is -github-, email sent at -time-'), + html_content=HtmlContent('Hello -name-, your github is here email sent at -time-'), + global_substitutions=global_substitutions, + is_multiple=True) + +try: + print(json.dumps(message.get(), sort_keys=True, indent=4)) + sendgrid_client = SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message=message) + print(response.status_code) + print(response.body) + print(response.headers) +except SendGridException as e: + print(e.message) + +# Kitchen Sink - an example with all settings used + +import os +import json +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import ( + Mail, From, To, Cc, Bcc, Subject, PlainTextContent, + HtmlContent, SendGridException, Substitution, + Header, CustomArg, SendAt, Content, MimeType, Attachment, + FileName, FileContent, FileType, Disposition, ContentId, + TemplateId, Section, ReplyTo, Category, BatchId, Asm, + GroupId, GroupsToDisplay, IpPoolName, MailSettings, + BccSettings, BccSettingsEmail, BypassListManagement, + FooterSettings, FooterText, FooterHtml, SandBoxMode, + SpamCheck, SpamThreshold, SpamUrl, TrackingSettings, + ClickTracking, SubscriptionTracking, SubscriptionText, + SubscriptionHtml, SubscriptionSubstitutionTag, + OpenTracking, OpenTrackingSubstitutionTag, Ganalytics, + UtmSource, UtmMedium, UtmTerm, UtmContent, UtmCampaign) +import time +import datetime + +message = Mail() + +# Define Personalizations + +message.to = To('elmer+test1@sendgrid.com', 'Example User1', p=0) +message.to = [ + To('elmer+test2@sendgrid.com', 'Example User2', p=0), + To('elmer+test3@sendgrid.com', 'Example User3', p=0) +] + +message.cc = Cc('test4@example.com', 'Example User4', p=0) +message.cc = [ + Cc('test5@example.com', 'Example User5', p=0), + Cc('test6@example.com', 'Example User6', p=0) +] + +message.bcc = Bcc('test7@example.com', 'Example User7', p=0) +message.bcc = [ + Bcc('test8@example.com', 'Example User8', p=0), + Bcc('test9@example.com', 'Example User9', p=0) +] + +message.subject = Subject('Sending with SendGrid is Fun 0', p=0) + +message.header = Header('X-Test1', 'Test1', p=0) +message.header = Header('X-Test2', 'Test2', p=0) +message.header = [ + Header('X-Test3', 'Test3', p=0), + Header('X-Test4', 'Test4', p=0) +] + +message.substitution = Substitution('%name1%', 'Example Name 1', p=0) +message.substitution = Substitution('%city1%', 'Example City 1', p=0) +message.substitution = [ + Substitution('%name2%', 'Example Name 2', p=0), + Substitution('%city2%', 'Example City 2', p=0) +] + +message.custom_arg = CustomArg('marketing1', 'true', p=0) +message.custom_arg = CustomArg('transactional1', 'false', p=0) +message.custom_arg = [ + CustomArg('marketing2', 'false', p=0), + CustomArg('transactional2', 'true', p=0) +] + +message.send_at = SendAt(1461775051, p=0) + +message.to = To('test10@example.com', 'Example User10', p=1) +message.to = [ + To('test11@example.com', 'Example User11', p=1), + To('test12@example.com', 'Example User12', p=1) +] + +message.cc = Cc('test13@example.com', 'Example User13', p=1) +message.cc = [ + Cc('test14@example.com', 'Example User14', p=1), + Cc('test15@example.com', 'Example User15', p=1) +] + +message.bcc = Bcc('test16@example.com', 'Example User16', p=1) +message.bcc = [ + Bcc('test17@example.com', 'Example User17', p=1), + Bcc('test18@example.com', 'Example User18', p=1) +] + +message.header = Header('X-Test5', 'Test5', p=1) +message.header = Header('X-Test6', 'Test6', p=1) +message.header = [ + Header('X-Test7', 'Test7', p=1), + Header('X-Test8', 'Test8', p=1) +] + +message.substitution = Substitution('%name3%', 'Example Name 3', p=1) +message.substitution = Substitution('%city3%', 'Example City 3', p=1) +message.substitution = [ + Substitution('%name4%', 'Example Name 4', p=1), + Substitution('%city4%', 'Example City 4', p=1) +] + +message.custom_arg = CustomArg('marketing3', 'true', p=1) +message.custom_arg = CustomArg('transactional3', 'false', p=1) +message.custom_arg = [ + CustomArg('marketing4', 'false', p=1), + CustomArg('transactional4', 'true', p=1) +] + +message.send_at = SendAt(1461775052, p=1) + +message.subject = Subject('Sending with SendGrid is Fun 1', p=1) + +# The values below this comment are global to entire message + +message.from_email = From('help@twilio.com', 'Twilio SendGrid') + +message.reply_to = ReplyTo('help_reply@twilio.com', 'Twilio SendGrid Reply') + +message.subject = Subject('Sending with SendGrid is Fun 2') + +message.content = Content(MimeType.text, 'and easy to do anywhere, even with Python') +message.content = Content(MimeType.html, 'and easy to do anywhere, even with Python') +message.content = [ + Content('text/calendar', 'Party Time!!'), + Content('text/custom', 'Party Time 2!!') +] + +message.attachment = Attachment(FileContent('base64 encoded content 1'), + FileType('application/pdf'), + FileName('balance_001.pdf'), + Disposition('attachment'), + ContentId('Content ID 1')) +message.attachment = [ + Attachment(FileContent('base64 encoded content 2'), + FileType('image/png'), + FileName('banner.png'), + Disposition('inline'), + ContentId('Content ID 2')), + Attachment(FileContent('base64 encoded content 3'), + FileType('image/png'), + FileName('banner2.png'), + Disposition('inline'), + ContentId('Content ID 3')) +] + +message.template_id = TemplateId('13b8f94f-bcae-4ec6-b752-70d6cb59f932') + +message.section = Section('%section1%', 'Substitution for Section 1 Tag') +message.section = [ + Section('%section2%', 'Substitution for Section 2 Tag'), + Section('%section3%', 'Substitution for Section 3 Tag') +] + +message.header = Header('X-Test9', 'Test9') +message.header = Header('X-Test10', 'Test10') +message.header = [ + Header('X-Test11', 'Test11'), + Header('X-Test12', 'Test12') +] + +message.category = Category('Category 1') +message.category = Category('Category 2') +message.category = [ + Category('Category 1'), + Category('Category 2') +] + +message.custom_arg = CustomArg('marketing5', 'false') +message.custom_arg = CustomArg('transactional5', 'true') +message.custom_arg = [ + CustomArg('marketing6', 'true'), + CustomArg('transactional6', 'false') +] + +message.send_at = SendAt(1461775053) + +message.batch_id = BatchId("HkJ5yLYULb7Rj8GKSx7u025ouWVlMgAi") + +message.asm = Asm(GroupId(1), GroupsToDisplay([1,2,3,4])) + +message.ip_pool_name = IpPoolName("IP Pool Name") + +mail_settings = MailSettings() +mail_settings.bcc_settings = BccSettings(False, BccSettingsEmail("bcc@twilio.com")) +mail_settings.bypass_list_management = BypassListManagement(False) +mail_settings.footer_settings = FooterSettings(True, FooterText("w00t"), FooterHtml("w00t!")) +mail_settings.sandbox_mode = SandBoxMode(True) +mail_settings.spam_check = SpamCheck(True, SpamThreshold(5), SpamUrl("https://example.com")) +message.mail_settings = mail_settings + +tracking_settings = TrackingSettings() +tracking_settings.click_tracking = ClickTracking(True, False) +tracking_settings.open_tracking = OpenTracking(True, OpenTrackingSubstitutionTag("open_tracking")) +tracking_settings.subscription_tracking = SubscriptionTracking( + True, + SubscriptionText("Goodbye"), + SubscriptionHtml("Goodbye!"), + SubscriptionSubstitutionTag("unsubscribe")) +tracking_settings.ganalytics = Ganalytics( + True, + UtmSource("utm_source"), + UtmMedium("utm_medium"), + UtmTerm("utm_term"), + UtmContent("utm_content"), + UtmCampaign("utm_campaign")) +message.tracking_settings = tracking_settings + +try: + sendgrid_client = SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) + print(json.dumps(message.get(), sort_keys=True, indent=4)) + response = sendgrid_client.send(message=message) + print(response.status_code) + print(response.body) + print(response.headers) +except SendGridException as e: + print(e.message) + +## Send a Single Email to a Single Recipient with a Dynamic Template +import os +import json +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException, DynamicTemplateData + +message = Mail(from_email=From('help@twilio.com', 'Twilio SendGrid'), + to_emails=To('ethomas@twilio.com', 'Elmer Thomas'), + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), + html_content=HtmlContent('and easy to do anywhere, even with Python')) +message.dynamic_template_data = DynamicTemplateData({ + "total":"$ 239.85", + "items":[ + { + "text":"New Line Sneakers", + "image":"https://marketing-image-production.s3.amazonaws.com/uploads/8dda1131320a6d978b515cc04ed479df259a458d5d45d58b6b381cae0bf9588113e80ef912f69e8c4cc1ef1a0297e8eefdb7b270064cc046b79a44e21b811802.png", + "price":"$ 79.95" + }, + { + "text":"Old Line Sneakers", + "image":"https://marketing-image-production.s3.amazonaws.com/uploads/3629f54390ead663d4eb7c53702e492de63299d7c5f7239efdc693b09b9b28c82c924225dcd8dcb65732d5ca7b7b753c5f17e056405bbd4596e4e63a96ae5018.png", + "price":"$ 79.95" + }, + { + "text":"Blue Line Sneakers", + "image":"https://marketing-image-production.s3.amazonaws.com/uploads/00731ed18eff0ad5da890d876c456c3124a4e44cb48196533e9b95fb2b959b7194c2dc7637b788341d1ff4f88d1dc88e23f7e3704726d313c57f350911dd2bd0.png", + "price":"$ 79.95" + } + ], + "receipt":True, + "name":"Sample Name", + "address01":"1234 Fake St.", + "address02":"Apt. 123", + "city":"Place", + "state":"CO", + "zip":"80202" +}) + +try: + print(json.dumps(message.get(), sort_keys=True, indent=4)) + sendgrid_client = SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message=message) + print(response.status_code) + print(response.body) + print(response.headers) +except SendGridException as e: + print(e.message) diff --git a/proposals/mail-helper-refactor.md b/proposals/mail-helper-refactor.md index 5a0127fd6..70798c262 100644 --- a/proposals/mail-helper-refactor.md +++ b/proposals/mail-helper-refactor.md @@ -1,32 +1,35 @@ +# This is the original proposal for v6.0.0 + # Send a Single Email to a Single Recipient -The following code assumes you are storing the API key in an [environment variable (recommended)](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. +The following code assumes you are storing the API key in an [environment variable (recommended)](../TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. This is the minimum code needed to send an email. ```python import os -import sendgrid -from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException -msg = Mail(from_email=From('from@example.com', 'From Name'), - to_emails=To('to@example.com', 'To Name'), - subject=Subject('Sending with SendGrid is Fun'), - plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), - html_content=HtmlContent('and easy to do anywhere, even with Python')) +message = Mail(from_email=From('from@example.com', 'From Name'), + to_emails=To('to@example.com', 'To Name'), + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), + html_content=HtmlContent('and easy to do anywhere, even with Python')) try: - response = sendgrid.send(msg, apikey=os.environ.get('SENDGRID_apikey')) + sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_apikey')) + response = sendgrid_client.send(message=message) print(response.status_code) print(response.body) print(response.headers) -except Exception as e: +except SendGridException as e: print(e.read()) ``` # Send a Single Email to Multiple Recipients -The following code assumes you are storing the API key in an [environment variable (recommended)](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. +The following code assumes you are storing the API key in an [environment variable (recommended)](../TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. ```python import os @@ -39,12 +42,12 @@ to_emails = [ ] msg = Mail(from_email=From('from@example.com', 'From Name'), to_emails=to_emails, - subject=Subject('Sending with SendGrid is Fun'), + subject=Subject('Sending with Twilio SendGrid is Fun'), plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), html_content=HtmlContent('and easy to do anywhere, even with Python')) try: - response = sendgrid.send(msg, apikey=os.environ.get('SENDGRID_apikey')) + response = sendgrid.send(msg, os.environ.get('SENDGRID_apikey')) print(response.status_code) print(response.body) print(response.headers) @@ -54,7 +57,7 @@ except Exception as e: # Send Multiple Emails to Multiple Recipients -The following code assumes you are storing the API key in an [environment variable (recommended)](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. +The following code assumes you are storing the API key in an [environment variable (recommended)](../TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. ```python import os @@ -87,7 +90,7 @@ msg = Mail(from_email=From('from@example.com', 'From Name'), global_substitutions=global_substitutions) try: - response = sendgrid.send(msg, apikey=os.environ.get('SENDGRID_apikey')) + response = sendgrid.send(msg, os.environ.get('SENDGRID_apikey')) print(response.status_code) print(response.body) print(response.headers) @@ -97,7 +100,7 @@ except Exception as e: # Kitchen Sink - an example with all settings used -The following code assumes you are storing the API key in an [environment variable (recommended)](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. +The following code assumes you are storing the API key in an [environment variable (recommended)](../TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. ```python import os @@ -106,26 +109,26 @@ from sendgrid.helpers.mail import * msg = Mail(from_email=From('from@example.com', 'From Name'), to_email=To('to@example.com', 'To Name'), - subject=Subject('Sending with SendGrid is Fun'), + subject=Subject('Sending with Twilio SendGrid is Fun'), plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), html_content=HtmlContent('and easy to do anywhere, even with Python')) # For a detailed description of each of these settings, please see the [documentation](https://sendgrid.com/docs/API_Reference/api_v3.html). msg.to = To('test1@example.com', 'Example User1') -msg.to = [ +msg.to = [ To('test2@example.com', 'Example User2'), To('test3@example.com', 'Example User3') ] msg.cc = Cc('test4@example.com', 'Example User4') -msg.cc = [ +msg.cc = [ Cc('test5@example.com', 'Example User5'), Cc('test6@example.com', 'Example User6') ] msg.bcc = Bcc('test7@example.com', 'Example User7') -msg.bcc = [ +msg.bcc = [ Bcc('test8@example.com', 'Example User8'), Bcc('test9@example.com', 'Example User9') ] @@ -137,13 +140,6 @@ msg.header = [ Header('X-Test4', 'Test4') ] -msg.substitution = Substitution('%name1%', 'Example Name 1') -msg.substitution = Substitution('%city1%', 'Denver') -msg.substitution = [ - Substitution('%name2%', 'Example Name 2'), - Substitution('%city2%', 'Orange') -] - msg.custom_arg = CustomArg('marketing1', 'false') msg.custom_arg = CustomArg('transactional1', 'true') msg.custom_arg = [ @@ -191,14 +187,14 @@ msg.custom_arg = CustomArg('marketing3', 'true', p=1) msg.custom_arg = CustomArg('transactional3', 'false', p=1) msg.custom_arg = [ CustomArg('marketing4', 'false', p=1), - CustomArg('transactional4': 'true', p=1) + CustomArg('transactional4', 'true', p=1) ] msg.send_at = SendAt(1461775052, p=1) -# The values below this comment are global to entire message +# The values below this comment are global to the entire message -msg.global_subject = Subject('Sending with SendGrid is Fun') +msg.global_subject = Subject('Sending with Twilio SendGrid is Fun') msg.content = Content(MimeType.Text, 'and easy to do anywhere, even with Python') msg.content = Content(MimeType.Html, 'and easy to do anywhere, even with Python') @@ -222,7 +218,7 @@ msg.attachment = [ File('base64 encoded content'), Type('image/png'), Disposition('inline'), - Name('Banner 2')) + Name('Banner 2')) ] msg.template_id = TemplateId('13b8f94f-bcae-4ec6-b752-70d6cb59f932') @@ -230,17 +226,17 @@ msg.template_id = TemplateId('13b8f94f-bcae-4ec6-b752-70d6cb59f932') msg.global_header = Header('X-Day', 'Monday') msg.global_headers = [ Header('X-Month', 'January'), - Header('X-Year': '2017') + Header('X-Year', '2017') ] msg.section = Section('%section1%', 'Substitution for Section 1 Tag') msg.section = [ Section('%section2%', 'Substitution for Section 2 Tag'), - Section('%section3%': 'Substitution for Section 3 Tag') + Section('%section3%', 'Substitution for Section 3 Tag') ] try: - response = sendgrid.send(msg, apikey=os.environ.get('SENDGRID_apikey')) + response = sendgrid.send(msg, os.environ.get('SENDGRID_apikey')) print(response.status_code) print(response.body) print(response.headers) @@ -250,7 +246,7 @@ except Exception as e: # Attachments -The following code assumes you are storing the API key in an [environment variable (recommended)](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. +The following code assumes you are storing the API key in an [environment variable (recommended)](../TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. ```python import os @@ -259,7 +255,7 @@ from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, Htm msg = Mail(from_email=From('from@example.com', 'From Name'), to_emails=To('to@example.com', 'To Name'), - subject=Subject('Sending with SendGrid is Fun'), + subject=Subject('Sending with Twilio SendGrid is Fun'), plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), html_content=HtmlContent('and easy to do anywhere, even with Python')) msg.attachment = Attachment(FileName('balance_001.pdf'), @@ -269,7 +265,7 @@ msg.attachment = Attachment(FileName('balance_001.pdf'), Name('Balance Sheet')) try: - response = sendgrid.send(msg, apikey=os.environ.get('SENDGRID_apikey')) + response = sendgrid.send(msg, os.environ.get('SENDGRID_apikey')) print(response.status_code) print(response.body) print(response.headers) @@ -279,7 +275,7 @@ except Exception as e: # Transactional Templates -The following code assumes you are storing the API key in an [environment variable (recommended)](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. +The following code assumes you are storing the API key in an [environment variable (recommended)](../TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. For this example, we assume you have created a [transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). Following is the template content we used for testing. @@ -322,7 +318,7 @@ from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, Htm msg = Mail(from_email=From('from@example.com', 'From Name'), to_emails=To('to@example.com', 'To Name'), - subject=Subject('Sending with SendGrid is Fun'), + subject=Subject('Sending with Twilio SendGrid is Fun'), plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), html_content=HtmlContent('and easy to do anywhere, even with Python')) msg.substitution = [ @@ -332,7 +328,7 @@ msg.substitution = [ msg.template_id = TemplateId('13b8f94f-bcae-4ec6-b752-70d6cb59f932') try: - response = sendgrid.send(msg, apikey=os.environ.get('SENDGRID_apikey')) + response = sendgrid.send(msg, os.environ.get('SENDGRID_apikey')) print(response.status_code) print(response.body) print(response.headers) diff --git a/register.py b/register.py deleted file mode 100644 index f7c0b9be0..000000000 --- a/register.py +++ /dev/null @@ -1,19 +0,0 @@ -import pypandoc - -output = pypandoc.convert('README.md', 'rst') -with open('README.txt' 'w+') as f: - f.write(str(output.encode('utf-8'))) - -readme_rst = open('./README.txt').read() -replace = ''' - .. figure:: https://uiux.s3.amazonaws.com/2016-logos/email-logo - %402x.png\n :alt: SendGrid Logo\n\n SendGrid Logo\n - ''' -replacement = ''' - |SendGrid Logo|\n\n.. |SendGrid Logo| image:: - https://uiux.s3.amazonaws.com/2016-logos/email-logo%402x.png - \n :target: https://www.sendgrid.com - ''' -final_text = readme_rst.replace(replace, replacement) -with open('./README.txt', 'w') as f: - f.write(final_text) diff --git a/requirements.txt b/requirements.txt index 34d770b5b..ed2594a90 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,6 @@ -Flask==0.10.1 -PyYAML==3.11 -python-http-client==2.2.1 -six==1.10.0 +Flask==3.1.0 +PyYAML>=4.2b1 +python-http-client>=3.2.1 +six==1.17.0 +cryptography>=45.0.6 +more-itertools==5.0.0 diff --git a/sendgrid.env b/sendgrid.env deleted file mode 100644 index 954b741c7..000000000 --- a/sendgrid.env +++ /dev/null @@ -1 +0,0 @@ -export SENDGRID_API_KEY='echo export SENDGRID_API_KEY=YOUR_API_KEY > sendgrid.env' diff --git a/sendgrid/__init__.py b/sendgrid/__init__.py index 2bbd38b59..cd994dd2f 100644 --- a/sendgrid/__init__.py +++ b/sendgrid/__init__.py @@ -1,10 +1,10 @@ """ -This library allows you to quickly and easily use the SendGrid Web API v3 via +This library allows you to quickly and easily use the Twilio SendGrid Web API v3 via Python. -For more information on this library, see the README on Github. +For more information on this library, see the README on GitHub. http://github.com/sendgrid/sendgrid-python -For more information on the SendGrid v3 API, see the v3 docs: +For more information on the Twilio SendGrid v3 API, see the v3 docs: http://sendgrid.com/docs/API_Reference/api_v3.html For the user guide, code examples, and more, visit the main docs page: http://sendgrid.com/docs/index.html @@ -15,7 +15,10 @@ Modules to help with common tasks. """ -from .version import __version__ # noqa -# v3 API +from .helpers.endpoints import * # noqa +from .helpers.mail import * # noqa +from .helpers.stats import * # noqa +from .helpers.eventwebhook import * # noqa from .sendgrid import SendGridAPIClient # noqa -from .helpers.mail import Email # noqa +from .twilio_email import TwilioEmailAPIClient # noqa +from .version import __version__ diff --git a/sendgrid/base_interface.py b/sendgrid/base_interface.py new file mode 100644 index 000000000..f94f09479 --- /dev/null +++ b/sendgrid/base_interface.py @@ -0,0 +1,83 @@ +import python_http_client + +region_host_dict = {'eu':'https://api.eu.sendgrid.com','global':'https://api.sendgrid.com'} + +class BaseInterface(object): + def __init__(self, auth, host, impersonate_subuser): + """ + Construct the Twilio SendGrid v3 API object. + Note that the underlying client is being set up during initialization, + therefore changing attributes in runtime will not affect HTTP client + behaviour. + + :param auth: the authorization header + :type auth: string + :param impersonate_subuser: the subuser to impersonate. Will be passed + by "On-Behalf-Of" header by underlying + client. See + https://sendgrid.com/docs/User_Guide/Settings/subusers.html + for more details + :type impersonate_subuser: string + :param host: base URL for API calls + :type host: string + """ + from . import __version__ + self.auth = auth + self.impersonate_subuser = impersonate_subuser + self.version = __version__ + self.useragent = 'sendgrid/{};python'.format(self.version) + self.host = host + + self.client = python_http_client.Client( + host=self.host, + request_headers=self._default_headers, + version=3) + + @property + def _default_headers(self): + """Set the default header for a Twilio SendGrid v3 API call""" + headers = { + "Authorization": self.auth, + "User-Agent": self.useragent, + "Accept": 'application/json' + } + if self.impersonate_subuser: + headers['On-Behalf-Of'] = self.impersonate_subuser + + return headers + + def reset_request_headers(self): + self.client.request_headers = self._default_headers + + def send(self, message): + """Make a Twilio SendGrid v3 API request with the request body generated by + the Mail object + + :param message: The Twilio SendGrid v3 API request body generated by the Mail + object + :type message: Mail + """ + if not isinstance(message, dict): + message = message.get() + + return self.client.mail.send.post(request_body=message) + + def set_sendgrid_data_residency(self, region): + """ + Client libraries contain setters for specifying region/edge. + This supports global and eu regions only. This set will likely expand in the future. + Global is the default residency (or region) + Global region means the message will be sent through https://api.sendgrid.com + EU region means the message will be sent through https://api.eu.sendgrid.com + :param region: string + :return: + """ + if region in region_host_dict.keys(): + self.host = region_host_dict[region] + if self._default_headers is not None: + self.client = python_http_client.Client( + host=self.host, + request_headers=self._default_headers, + version=3) + else: + raise ValueError("region can only be \"eu\" or \"global\"") diff --git a/sendgrid/helpers/__init__.py b/sendgrid/helpers/__init__.py index 8b6581daf..fb29c5e2e 100644 --- a/sendgrid/helpers/__init__.py +++ b/sendgrid/helpers/__init__.py @@ -1,19 +1 @@ -"""v3/mail/send response body builder - -Builder for assembling emails to be sent with the v3 SendGrid API. - -Usage example: - def build_hello_email(): - to_email = from_email = Email("test@example.com") - subject = "Hello World from the SendGrid Python Library" - content = Content("text/plain", "some text here") - mail = Mail(from_email, subject, to_email, content) - mail.personalizations[0].add_to(Email("test2@example.com")) - return mail.get() # assembled request body - -For more usage examples, see -https://github.com/sendgrid/sendgrid-python/tree/master/examples/helpers/mail - -For more information on the v3 API, see -https://sendgrid.com/docs/API_Reference/api_v3.html -""" +"""Modules to help with SendGrid v3 API common tasks.""" diff --git a/sendgrid/helpers/endpoints/__init__.py b/sendgrid/helpers/endpoints/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sendgrid/helpers/endpoints/ip/__init__.py b/sendgrid/helpers/endpoints/ip/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sendgrid/helpers/endpoints/ip/unassigned.py b/sendgrid/helpers/endpoints/ip/unassigned.py new file mode 100644 index 000000000..816050d3d --- /dev/null +++ b/sendgrid/helpers/endpoints/ip/unassigned.py @@ -0,0 +1,59 @@ +import json + + +def format_ret(return_set, as_json=False): + """ decouple, allow for modifications to return type + returns a list of ip addresses in object or json form """ + ret_list = list() + for item in return_set: + d = {"ip": item} + ret_list.append(d) + + if as_json: + return json.dumps(ret_list) + + return ret_list + + +def unassigned(data, as_json=False): + """ https://sendgrid.com/docs/API_Reference/api_v3.html#ip-addresses + The /ips rest endpoint returns information about the IP addresses + and the usernames assigned to an IP + + unassigned returns a listing of the IP addresses that are allocated + but have 0 users assigned + + + data (response.body from sg.client.ips.get()) + as_json False -> get list of dicts + True -> get json object + + example: + sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + + params = { + 'subuser': 'test_string', + 'ip': 'test_string', + 'limit': 1, + 'exclude_whitelabels': + 'true', 'offset': 1 + } + response = sg.client.ips.get(query_params=params) + if response.status_code == 201: + data = response.body + unused = unassigned(data) + """ + + no_subusers = set() + + if not isinstance(data, list): + return format_ret(no_subusers, as_json=as_json) + + for current in data: + num_subusers = len(current["subusers"]) + if num_subusers == 0: + current_ip = current["ip"] + no_subusers.add(current_ip) + + ret_val = format_ret(no_subusers, as_json=as_json) + return ret_val diff --git a/sendgrid/helpers/eventwebhook/__init__.py b/sendgrid/helpers/eventwebhook/__init__.py new file mode 100644 index 000000000..9d618bf3a --- /dev/null +++ b/sendgrid/helpers/eventwebhook/__init__.py @@ -0,0 +1,56 @@ +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.serialization import load_pem_public_key +import base64 +from .eventwebhook_header import EventWebhookHeader + +class EventWebhook: + """ + This class allows you to use the Event Webhook feature. Read the docs for + more details: https://sendgrid.com/docs/for-developers/tracking-events/event + """ + + def __init__(self, public_key=None): + """ + Construct the Event Webhook verifier object + :param public_key: verification key under Mail Settings + :type public_key: string + """ + self.public_key = self.convert_public_key_to_ecdsa(public_key) if public_key else public_key + + def convert_public_key_to_ecdsa(self, public_key): + """ + Convert the public key string to an EllipticCurvePublicKey object. + + :param public_key: verification key under Mail Settings + :type public_key string + :return: An EllipticCurvePublicKey object using the ECDSA algorithm + :rtype cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey + """ + pem_key = "-----BEGIN PUBLIC KEY-----\n" + public_key + "\n-----END PUBLIC KEY-----" + return load_pem_public_key(pem_key.encode("utf-8")) + + def verify_signature(self, payload, signature, timestamp, public_key=None): + """ + Verify signed event webhook requests. + + :param payload: event payload in the request body + :type payload: string + :param signature: value obtained from the 'X-Twilio-Email-Event-Webhook-Signature' header + :type signature: string + :param timestamp: value obtained from the 'X-Twilio-Email-Event-Webhook-Timestamp' header + :type timestamp: string + :param public_key: elliptic curve public key + :type public_key: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey + :return: true or false if signature is valid + """ + timestamped_payload = (timestamp + payload).encode('utf-8') + decoded_signature = base64.b64decode(signature) + + key = public_key or self.public_key + try: + key.verify(decoded_signature, timestamped_payload, ec.ECDSA(hashes.SHA256())) + return True + except InvalidSignature: + return False diff --git a/sendgrid/helpers/eventwebhook/eventwebhook_header.py b/sendgrid/helpers/eventwebhook/eventwebhook_header.py new file mode 100644 index 000000000..a41a48524 --- /dev/null +++ b/sendgrid/helpers/eventwebhook/eventwebhook_header.py @@ -0,0 +1,10 @@ +class EventWebhookHeader: + """ + This class lists headers that get posted to the webhook. Read the docs for + more details: https://sendgrid.com/docs/for-developers/tracking-events/event + """ + SIGNATURE = 'X-Twilio-Email-Event-Webhook-Signature' + TIMESTAMP = 'X-Twilio-Email-Event-Webhook-Timestamp' + + def __init__(self): + pass diff --git a/sendgrid/helpers/inbound/README.md b/sendgrid/helpers/inbound/README.md index bc69d1189..79e5b4544 100644 --- a/sendgrid/helpers/inbound/README.md +++ b/sendgrid/helpers/inbound/README.md @@ -1,13 +1,17 @@ -**This helper is a stand alone module to help get you started consuming and processing Inbound Parse data.** +**This helper is a stand-alone module to help get you started consuming and processing Inbound Parse data.** ## Table of Contents -* [Quick Start for Local Testing with Sample Data](#quick_start_local_sample) -* [Quick Start for Local Testing with Real Data](#quick_start_local_real) -* [Deploy to Heroku](#heroku) -* [Code Walkthrough](#code_walkthrough) -* [Testing the Source Code](#testing) -* [Contributing](#contributing) +- [Quick Start for Local Testing with Sample Data](#quick-start-for-local-testing-with-sample-data) +- [Quick Start for Local Testing with Real Data](#quick-start-for-local-testing-with-real-data) +- [Deploy to Heroku](#deploy-to-heroku) +- [Code Walkthrough](#code-walkthrough) + - [app.py](#apppy) + - [config.py & config.yml](#configpy--configyml) + - [parse.py](#parsepy) + - [send.py & /sample_data](#sendpy--sampledata) +- [Testing the Source Code](#testing-the-source-code) +- [Contributing](#contributing) # Quick Start for Local Testing with Sample Data @@ -32,7 +36,7 @@ pip install -r requirements.txt python sendgrid/helpers/inbound/send.py ./sendgrid/helpers/inbound/sample_data/default_data.txt ``` -More sample data can be found [here](https://github.com/sendgrid/sendgrid-python/tree/master/sendgrid/helpers/inbound/sample_data). +More sample data can be found [here](sample_data). View the results in the first terminal. @@ -67,7 +71,7 @@ Next, send an email to [anything]@inbound.yourdomain.com, then look at the termi Get a [Heroku](https://www.heroku.com) account. -[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/sendgrid/sendgrid-python/tree/master) +[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/sendgrid/sendgrid-python/tree/main) [Setup your MX records.](https://sendgrid.com/docs/Classroom/Basics/Inbound_Parse_Webhook/setting_up_the_inbound_parse_webhook.html#-Setup) Depending on your domain name host, you may need to wait up to 48 hours for the settings to propagate. @@ -96,7 +100,7 @@ heroku git:remote -a [name-of-your-app] ---make changes--- git add . git commit -m "update configuration" -git push heroku master +git push heroku main ``` @@ -123,12 +127,12 @@ This module is used to send sample test data. It is useful for testing and devel Tests are located in the root of this project in the /test folder: -- [test_config.py](https://github.com/sendgrid/sendgrid-python/blob/master/test/test_config.py) -- [test_parse.py](https://github.com/sendgrid/sendgrid-python/blob/master/test/test_parse.py) +- [test_config.py](../../../test/test_config.py) +- [test_parse.py](../../../test/test_parse.py) -Learn about testing this code [here](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md#testing). +Learn about testing this code [here](../../../CONTRIBUTING.md#testing). # Contributing -If you would like to contribute to this project, please see our [contributing guide](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md). Thanks! +If you would like to contribute to this project, please see our [contributing guide](../../../CONTRIBUTING.md). Thanks! diff --git a/sendgrid/helpers/inbound/app.py b/sendgrid/helpers/inbound/app.py index 605c4497c..0d4435907 100755 --- a/sendgrid/helpers/inbound/app.py +++ b/sendgrid/helpers/inbound/app.py @@ -30,7 +30,7 @@ def index(): def inbound_parse(): """Process POST from Inbound Parse and print received data.""" parse = Parse(config, request) - # Sample proccessing action + # Sample processing action print(parse.key_values()) # Tell SendGrid's Inbound Parse to stop sending POSTs # Everything is 200 OK :) diff --git a/sendgrid/helpers/inbound/config.py b/sendgrid/helpers/inbound/config.py index 32c389e38..06ca683cb 100644 --- a/sendgrid/helpers/inbound/config.py +++ b/sendgrid/helpers/inbound/config.py @@ -5,6 +5,7 @@ class Config(object): """All configuration for this app is loaded here""" + def __init__(self, **opts): if os.environ.get('ENV') != 'prod': # We are not in Heroku self.init_environment() @@ -14,8 +15,8 @@ def __init__(self, **opts): self.path = opts.get( 'path', os.path.abspath(os.path.dirname(__file__)) ) - with open(self.path + '/config.yml') as stream: - config = yaml.load(stream) + with open('{0}/config.yml'.format(self.path)) as stream: + config = yaml.load(stream, Loader=yaml.FullLoader) self._debug_mode = config['debug_mode'] self._endpoint = config['endpoint'] self._host = config['host'] @@ -27,16 +28,15 @@ def init_environment(): """Allow variables assigned in .env available using os.environ.get('VAR_NAME')""" base_path = os.path.abspath(os.path.dirname(__file__)) - if os.path.exists(base_path + '/.env'): - with open(base_path + '/.env') as f: + env_path = '{0}/.env'.format(base_path) + if os.path.exists(env_path): + with open(env_path) as f: lines = f.readlines() for line in lines: var = line.strip().split('=') if len(var) == 2: os.environ[var[0]] = var[1] - - @property def debug_mode(self): """Flask debug mode - set to False in production.""" diff --git a/sendgrid/helpers/inbound/parse.py b/sendgrid/helpers/inbound/parse.py index 77b6683ad..49627a121 100644 --- a/sendgrid/helpers/inbound/parse.py +++ b/sendgrid/helpers/inbound/parse.py @@ -7,6 +7,7 @@ class Parse(object): + def __init__(self, config, request): self._keys = config.keys self._request = request diff --git a/sendgrid/helpers/inbound/sample_data/default_data.txt b/sendgrid/helpers/inbound/sample_data/default_data.txt index cdf52d68b..7c3ce6be2 100644 --- a/sendgrid/helpers/inbound/sample_data/default_data.txt +++ b/sendgrid/helpers/inbound/sample_data/default_data.txt @@ -20,7 +20,7 @@ inbound@inbound.example.com --xYzZY Content-Disposition: form-data; name="html" -Hello SendGrid! +Hello Twilio SendGrid! --xYzZY Content-Disposition: form-data; name="from" @@ -29,7 +29,7 @@ Example User --xYzZY Content-Disposition: form-data; name="text" -Hello SendGrid! +Hello Twilio SendGrid! --xYzZY Content-Disposition: form-data; name="sender_ip" diff --git a/sendgrid/helpers/inbound/sample_data/raw_data.txt b/sendgrid/helpers/inbound/sample_data/raw_data.txt index eff28c385..12f64cb4a 100644 --- a/sendgrid/helpers/inbound/sample_data/raw_data.txt +++ b/sendgrid/helpers/inbound/sample_data/raw_data.txt @@ -16,13 +16,13 @@ Content-Type: multipart/alternative; boundary=001a113ee97c89842f0539be8e7a --001a113ee97c89842f0539be8e7a Content-Type: text/plain; charset=UTF-8 -Hello SendGrid! +Hello Twilio SendGrid! --001a113ee97c89842f0539be8e7a Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable -Hello SendGrid! +Hello Twilio SendGrid! --001a113ee97c89842f0539be8e7a-- diff --git a/sendgrid/helpers/inbound/sample_data/raw_data_with_attachments.txt b/sendgrid/helpers/inbound/sample_data/raw_data_with_attachments.txt index f8cd93fc2..058fd8a64 100644 --- a/sendgrid/helpers/inbound/sample_data/raw_data_with_attachments.txt +++ b/sendgrid/helpers/inbound/sample_data/raw_data_with_attachments.txt @@ -19,13 +19,13 @@ Content-Type: multipart/alternative; boundary=001a1140ffb6f4fc5f053a2257e0 --001a1140ffb6f4fc5f053a2257e0 Content-Type: text/plain; charset=UTF-8 -Hello SendGrid! +Hello Twilio SendGrid! --001a1140ffb6f4fc5f053a2257e0 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable -Hello SendGrid! +Hello Twilio SendGrid! --001a1140ffb6f4fc5f053a2257e0-- diff --git a/sendgrid/helpers/inbound/send.py b/sendgrid/helpers/inbound/send.py index 9cbb5aa6c..8dbfa68d2 100644 --- a/sendgrid/helpers/inbound/send.py +++ b/sendgrid/helpers/inbound/send.py @@ -2,6 +2,7 @@ Usage: ./send.py [path to file containing test data]""" import argparse import sys +from io import open try: from config import Config except ImportError: @@ -11,6 +12,7 @@ class Send(object): + def __init__(self, url): """Create a Send object with target `url`.""" self._url = url @@ -26,18 +28,20 @@ def test_payload(self, payload_filepath): "Content-Type": "multipart/form-data; boundary=xYzZY" } client = Client(host=self.url, request_headers=headers) - with open(payload_filepath, 'r') as f: - data = f.read() - return client.post(request_body=data) + f = open(payload_filepath, 'r', encoding='utf-8') + data = f.read() + return client.post(request_body=data) @property def url(self): """URL to send to.""" return self._url + def main(): config = Config() - parser = argparse.ArgumentParser(description='Test data and optional host.') + parser = argparse.ArgumentParser( + description='Test data and optional host.') parser.add_argument('data', type=str, help='path to the sample data') @@ -52,5 +56,6 @@ def main(): print(response.headers) print(response.body) + if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/sendgrid/helpers/inbound/templates/index.html b/sendgrid/helpers/inbound/templates/index.html index b0f0954db..7cbede381 100644 --- a/sendgrid/helpers/inbound/templates/index.html +++ b/sendgrid/helpers/inbound/templates/index.html @@ -3,8 +3,8 @@ SendGrid Incoming Parse -

You have successfuly launched the server!

+

You have successfully launched the server!

- Check out the documentation on how to use this software to utilize the SendGrid Inbound Parse webhook. + Check out the documentation on how to use this software to utilize the SendGrid Inbound Parse webhook. \ No newline at end of file diff --git a/sendgrid/helpers/mail/README.md b/sendgrid/helpers/mail/README.md index 09e3e2035..bbf0a2ece 100644 --- a/sendgrid/helpers/mail/README.md +++ b/sendgrid/helpers/mail/README.md @@ -1,15 +1,10 @@ -**This helper allows you to quickly and easily build a Mail object for sending email through SendGrid.** +**This helper allows you to quickly and easily build a Mail object for sending email through Twilio SendGrid.** # Quick Start -Run the [example](https://github.com/sendgrid/sendgrid-python/tree/master/examples/helpers/mail) (make sure you have set your environment variable to include your SENDGRID_API_KEY). - -```bash -cp examples/helpers/mail_settings.py . -python mail_settings.py -``` +Please complete the [installation steps](https://github.com/sendgrid/sendgrid-python#installation) and then execute the [quick start example](https://github.com/sendgrid/sendgrid-python#quick-start). ## Usage -- See the [examples](https://github.com/sendgrid/sendgrid-python/tree/master/examples/helpers/mail) for complete working examples. -- [Documentation](https://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/overview.html) \ No newline at end of file +- For the most common use cases, please see [these examples](../../../use_cases) +- The complete v3 API Documentation can be found [here](https://sendgrid.com/docs/API_Reference/api_v3.html) diff --git a/sendgrid/helpers/mail/__init__.py b/sendgrid/helpers/mail/__init__.py index b9e908560..358f2d912 100644 --- a/sendgrid/helpers/mail/__init__.py +++ b/sendgrid/helpers/mail/__init__.py @@ -1,22 +1,63 @@ -from .asm import ASM +from .asm import Asm from .attachment import Attachment -from .bcc_settings import BCCSettings +from .batch_id import BatchId +from .bcc_email import Bcc +from .bcc_settings import BccSettings +from .bcc_settings_email import BccSettingsEmail +from .bypass_bounce_management import BypassBounceManagement from .bypass_list_management import BypassListManagement +from .bypass_spam_management import BypassSpamManagement +from .bypass_unsubscribe_management import BypassUnsubscribeManagement from .category import Category +from .cc_email import Cc from .click_tracking import ClickTracking from .content import Content +from .content_id import ContentId from .custom_arg import CustomArg +from .disposition import Disposition +from .dynamic_template_data import DynamicTemplateData from .email import Email +from .exceptions import SendGridException, ApiKeyIncludedException +from .file_content import FileContent +from .file_name import FileName +from .file_type import FileType from .footer_settings import FooterSettings +from .footer_text import FooterText +from .footer_html import FooterHtml +from .from_email import From from .ganalytics import Ganalytics +from .group_id import GroupId +from .groups_to_display import GroupsToDisplay from .header import Header +from .html_content import HtmlContent +from .amp_html_content import AmpHtmlContent +from .ip_pool_name import IpPoolName from .mail_settings import MailSettings from .mail import Mail +from .mime_type import MimeType from .open_tracking import OpenTracking +from .open_tracking_substitution_tag import OpenTrackingSubstitutionTag from .personalization import Personalization +from .plain_text_content import PlainTextContent +from .reply_to import ReplyTo from .sandbox_mode import SandBoxMode from .section import Section +from .send_at import SendAt from .spam_check import SpamCheck +from .spam_threshold import SpamThreshold +from .spam_url import SpamUrl +from .subject import Subject from .subscription_tracking import SubscriptionTracking +from .subscription_text import SubscriptionText +from .subscription_html import SubscriptionHtml +from .subscription_substitution_tag import SubscriptionSubstitutionTag from .substitution import Substitution +from .template_id import TemplateId from .tracking_settings import TrackingSettings +from .to_email import To +from .utm_source import UtmSource +from .utm_medium import UtmMedium +from .utm_term import UtmTerm +from .utm_content import UtmContent +from .utm_campaign import UtmCampaign +from .validators import ValidateApiKey diff --git a/sendgrid/helpers/mail/amp_html_content.py b/sendgrid/helpers/mail/amp_html_content.py new file mode 100644 index 000000000..1a282053f --- /dev/null +++ b/sendgrid/helpers/mail/amp_html_content.py @@ -0,0 +1,59 @@ +from .content import Content +from .validators import ValidateApiKey + + +class AmpHtmlContent(Content): + """AMP HTML content to be included in your email.""" + + def __init__(self, content): + """Create an AMP HTML Content with the specified MIME type and content. + + :param content: The AMP HTML content. + :type content: string + """ + self._content = None + self._validator = ValidateApiKey() + + if content is not None: + self.content = content + + @property + def mime_type(self): + """The MIME type for AMP HTML content. + + :rtype: string + """ + return "text/x-amp-html" + + @property + def content(self): + """The actual AMP HTML content. + + :rtype: string + """ + return self._content + + @content.setter + def content(self, value): + """The actual AMP HTML content. + + :param value: The actual AMP HTML content. + :type value: string + """ + self._validator.validate_message_dict(value) + self._content = value + + def get(self): + """ + Get a JSON-ready representation of this AmpContent. + + :returns: This AmpContent, ready for use in a request body. + :rtype: dict + """ + content = {} + if self.mime_type is not None: + content["type"] = self.mime_type + + if self.content is not None: + content["value"] = self.content + return content diff --git a/sendgrid/helpers/mail/asm.py b/sendgrid/helpers/mail/asm.py index 8187aaba7..62db8372a 100644 --- a/sendgrid/helpers/mail/asm.py +++ b/sendgrid/helpers/mail/asm.py @@ -1,55 +1,80 @@ -class ASM(object): +from .group_id import GroupId +from .groups_to_display import GroupsToDisplay + + +class Asm(object): """An object specifying unsubscribe behavior.""" - def __init__(self, group_id=None, groups_to_display=None): + def __init__(self, group_id, groups_to_display=None): """Create an ASM with the given group_id and groups_to_display. :param group_id: ID of an unsubscribe group - :type group_id: int, optional + :type group_id: GroupId, int, required :param groups_to_display: Unsubscribe groups to display - :type groups_to_display: list(int), optional + :type groups_to_display: GroupsToDisplay, list(int), optional """ - self.group_id = group_id - self.groups_to_display = groups_to_display + self._group_id = None + self._groups_to_display = None + + if group_id is not None: + self.group_id = group_id + + if groups_to_display is not None: + self.groups_to_display = groups_to_display @property def group_id(self): """The unsubscribe group to associate with this email. - :rtype: integer + :rtype: GroupId """ return self._group_id @group_id.setter def group_id(self, value): - self._group_id = value + """The unsubscribe group to associate with this email. + + :param value: ID of an unsubscribe group + :type value: GroupId, int, required + """ + if isinstance(value, GroupId): + self._group_id = value + else: + self._group_id = GroupId(value) @property def groups_to_display(self): """The unsubscribe groups that you would like to be displayed on the unsubscribe preferences page. Max of 25 groups. - :rtype: list(int) + :rtype: GroupsToDisplay """ return self._groups_to_display @groups_to_display.setter def groups_to_display(self, value): - if value is not None and len(value) > 25: - raise ValueError("New groups_to_display exceeds max length of 25.") - self._groups_to_display = value + """An array containing the unsubscribe groups that you would like to + be displayed on the unsubscribe preferences page. Max of 25 groups. + + :param groups_to_display: Unsubscribe groups to display + :type groups_to_display: GroupsToDisplay, list(int), optional + """ + if isinstance(value, GroupsToDisplay): + self._groups_to_display = value + else: + self._groups_to_display = GroupsToDisplay(value) def get(self): """ - Get a JSON-ready representation of this ASM. + Get a JSON-ready representation of this ASM object. - :returns: This ASM, ready for use in a request body. + :returns: This ASM object, ready for use in a request body. :rtype: dict """ asm = {} if self.group_id is not None: - asm["group_id"] = self.group_id + asm["group_id"] = self.group_id.get() if self.groups_to_display is not None: - asm["groups_to_display"] = self.groups_to_display + asm["groups_to_display"] = self.groups_to_display.get() return asm diff --git a/sendgrid/helpers/mail/attachment.py b/sendgrid/helpers/mail/attachment.py index 09215f97f..f8b53a688 100644 --- a/sendgrid/helpers/mail/attachment.py +++ b/sendgrid/helpers/mail/attachment.py @@ -1,49 +1,125 @@ +from .file_content import FileContent +from .file_type import FileType +from .file_name import FileName +from .disposition import Disposition +from .content_id import ContentId + + class Attachment(object): """An attachment to be included with an email.""" - def __init__(self): - """Create an empty Attachment.""" - self._content = None - self._type = None - self._filename = None + def __init__( + self, + file_content=None, + file_name=None, + file_type=None, + disposition=None, + content_id=None): + """Create an Attachment + + :param file_content: The Base64 encoded content of the attachment + :type file_content: FileContent, string + :param file_name: The filename of the attachment + :type file_name: FileName, string + :param file_type: The MIME type of the content you are attaching + :type file_type FileType, string, optional + :param disposition: The content-disposition of the attachment, + specifying display style. Specifies how you + would like the attachment to be displayed. + - "inline" results in the attached file being + displayed automatically within the message. + - "attachment" results in the attached file + requiring some action to display (e.g. opening + or downloading the file). + If unspecified, "attachment" is used. Must be one + of the two choices. + :type disposition: Disposition, string, optional + :param content_id: The content id for the attachment. + This is used when the Disposition is set to + "inline" and the attachment is an image, allowing + the file to be displayed within the email body. + :type content_id: ContentId, string, optional + """ + self._file_content = None + self._file_type = None + self._file_name = None self._disposition = None self._content_id = None + if file_content is not None: + self.file_content = file_content + + if file_type is not None: + self.file_type = file_type + + if file_name is not None: + self.file_name = file_name + + if disposition is not None: + self.disposition = disposition + + if content_id is not None: + self.content_id = content_id + @property - def content(self): + def file_content(self): """The Base64 encoded content of the attachment. - :rtype: string + :rtype: FileContent """ - return self._content + return self._file_content - @content.setter - def content(self, value): - self._content = value + @file_content.setter + def file_content(self, value): + """The Base64 encoded content of the attachment + + :param value: The Base64 encoded content of the attachment + :type value: FileContent, string + """ + if isinstance(value, FileContent): + self._file_content = value + else: + self._file_content = FileContent(value) @property - def type(self): - """The MIME type of the content you are attaching. + def file_name(self): + """The file name of the attachment. - :rtype: string + :rtype: FileName """ - return self._type + return self._file_name - @type.setter - def type(self, value): - self._type = value + @file_name.setter + def file_name(self, value): + """The filename of the attachment + + :param file_name: The filename of the attachment + :type file_name: FileName, string + """ + if isinstance(value, FileName): + self._file_name = value + else: + self._file_name = FileName(value) @property - def filename(self): - """The filename of the attachment. + def file_type(self): + """The MIME type of the content you are attaching. - :rtype: string + :rtype: FileType """ - return self._filename + return self._file_type + + @file_type.setter + def file_type(self, value): + """The MIME type of the content you are attaching - @filename.setter - def filename(self, value): - self._filename = value + :param file_type: The MIME type of the content you are attaching + :type file_type FileType, string, optional + """ + if isinstance(value, FileType): + self._file_type = value + else: + self._file_type = FileType(value) @property def disposition(self): @@ -56,13 +132,37 @@ def disposition(self): display (e.g. opening or downloading the file). If unspecified, "attachment" is used. Must be one of the two choices. - :rtype: string + :rtype: Disposition """ return self._disposition @disposition.setter def disposition(self, value): - self._disposition = value + """The content-disposition of the attachment, specifying display style. + + Specifies how you would like the attachment to be displayed. + - "inline" results in the attached file being displayed automatically + within the message. + - "attachment" results in the attached file requiring some action to + display (e.g. opening or downloading the file). + If unspecified, "attachment" is used. Must be one of the two choices. + + :param disposition: The content-disposition of the attachment, + specifying display style. Specifies how you would + like the attachment to be displayed. + - "inline" results in the attached file being + displayed automatically within the message. + - "attachment" results in the attached file + requiring some action to display (e.g. opening + or downloading the file). + If unspecified, "attachment" is used. Must be one + of the two choices. + :type disposition: Disposition, string, optional + """ + if isinstance(value, Disposition): + self._disposition = value + else: + self._disposition = Disposition(value) @property def content_id(self): @@ -77,7 +177,21 @@ def content_id(self): @content_id.setter def content_id(self, value): - self._content_id = value + """The content id for the attachment. + + This is used when the disposition is set to "inline" and the attachment + is an image, allowing the file to be displayed within the email body. + + :param content_id: The content id for the attachment. + This is used when the Disposition is set to "inline" + and the attachment is an image, allowing the file to + be displayed within the email body. + :type content_id: ContentId, string, optional + """ + if isinstance(value, ContentId): + self._content_id = value + else: + self._content_id = ContentId(value) def get(self): """ @@ -87,18 +201,18 @@ def get(self): :rtype: dict """ attachment = {} - if self.content is not None: - attachment["content"] = self.content + if self.file_content is not None: + attachment["content"] = self.file_content.get() - if self.type is not None: - attachment["type"] = self.type + if self.file_type is not None: + attachment["type"] = self.file_type.get() - if self.filename is not None: - attachment["filename"] = self.filename + if self.file_name is not None: + attachment["filename"] = self.file_name.get() if self.disposition is not None: - attachment["disposition"] = self.disposition + attachment["disposition"] = self.disposition.get() if self.content_id is not None: - attachment["content_id"] = self.content_id + attachment["content_id"] = self.content_id.get() return attachment diff --git a/sendgrid/helpers/mail/batch_id.py b/sendgrid/helpers/mail/batch_id.py new file mode 100644 index 000000000..a4c0f8e9d --- /dev/null +++ b/sendgrid/helpers/mail/batch_id.py @@ -0,0 +1,50 @@ +class BatchId(object): + """This ID represents a batch of emails to be sent at the same time. + Including a batch_id in your request allows you include this email + in that batch, and also enables you to cancel or pause the delivery + of that batch. For more information, see + https://sendgrid.com/docs/API_Reference/Web_API_v3/cancel_schedule_send. + """ + def __init__(self, batch_id=None): + """Create a batch ID. + + :param batch_id: Batch Id + :type batch_id: string + """ + self._batch_id = None + + if batch_id is not None: + self.batch_id = batch_id + + @property + def batch_id(self): + """The batch ID. + + :rtype: string + """ + return self._batch_id + + @batch_id.setter + def batch_id(self, value): + """The batch ID. + + :param value: Batch Id + :type value: string + """ + self._batch_id = value + + def __str__(self): + """Get a JSON representation of this object. + + :rtype: string + """ + return str(self.get()) + + def get(self): + """ + Get a JSON-ready representation of this BatchId object. + + :returns: The BatchId, ready for use in a request body. + :rtype: string + """ + return self.batch_id diff --git a/sendgrid/helpers/mail/bcc_email.py b/sendgrid/helpers/mail/bcc_email.py new file mode 100644 index 000000000..e78f67030 --- /dev/null +++ b/sendgrid/helpers/mail/bcc_email.py @@ -0,0 +1,5 @@ +from .email import Email + + +class Bcc(Email): + """A bcc email address with an optional name.""" diff --git a/sendgrid/helpers/mail/bcc_settings.py b/sendgrid/helpers/mail/bcc_settings.py index 391792be0..eeb8ba100 100644 --- a/sendgrid/helpers/mail/bcc_settings.py +++ b/sendgrid/helpers/mail/bcc_settings.py @@ -1,4 +1,4 @@ -class BCCSettings(object): +class BccSettings(object): """Settings object for automatic BCC. This allows you to have a blind carbon copy automatically sent to the @@ -11,10 +11,16 @@ def __init__(self, enable=None, email=None): :param enable: Whether this BCCSettings is applied to sent emails. :type enable: boolean, optional :param email: Who should be BCCed. - :type email: Email, optional + :type email: BccSettingEmail, optional """ - self.enable = enable - self.email = email + self._enable = None + self._email = None + + if enable is not None: + self.enable = enable + + if email is not None: + self.email = email @property def enable(self): @@ -26,18 +32,28 @@ def enable(self): @enable.setter def enable(self, value): + """Indicates if this setting is enabled. + + :type param: Indicates if this setting is enabled. + :type value: boolean + """ self._enable = value @property def email(self): """The email address that you would like to receive the BCC. - :rtype: Email + :rtype: string """ return self._email @email.setter def email(self, value): + """The email address that you would like to receive the BCC. + + :param value: The email address that you would like to receive the BCC. + :type value: string + """ self._email = value def get(self): @@ -52,6 +68,5 @@ def get(self): bcc_settings["enable"] = self.enable if self.email is not None: - email = self.email.get() - bcc_settings["email"] = email["email"] + bcc_settings["email"] = self.email.get() return bcc_settings diff --git a/sendgrid/helpers/mail/bcc_settings_email.py b/sendgrid/helpers/mail/bcc_settings_email.py new file mode 100644 index 000000000..2c2847e23 --- /dev/null +++ b/sendgrid/helpers/mail/bcc_settings_email.py @@ -0,0 +1,40 @@ +class BccSettingsEmail(object): + """The BccSettingsEmail of an Attachment.""" + + def __init__(self, bcc_settings_email=None): + """Create a BccSettingsEmail object + + :param bcc_settings_email: The email address that you would like to + receive the BCC + :type bcc_settings_email: string, optional + """ + self._bcc_settings_email = None + + if bcc_settings_email is not None: + self.bcc_settings_email = bcc_settings_email + + @property + def bcc_settings_email(self): + """The email address that you would like to receive the BCC + + :rtype: string + """ + return self._bcc_settings_email + + @bcc_settings_email.setter + def bcc_settings_email(self, value): + """The email address that you would like to receive the BCC + + :param value: The email address that you would like to receive the BCC + :type value: string + """ + self._bcc_settings_email = value + + def get(self): + """ + Get a JSON-ready representation of this BccSettingsEmail. + + :returns: This BccSettingsEmail, ready for use in a request body. + :rtype: string + """ + return self.bcc_settings_email diff --git a/sendgrid/helpers/mail/bypass_bounce_management.py b/sendgrid/helpers/mail/bypass_bounce_management.py new file mode 100644 index 000000000..b0a35105c --- /dev/null +++ b/sendgrid/helpers/mail/bypass_bounce_management.py @@ -0,0 +1,48 @@ +class BypassBounceManagement(object): + """Setting for Bypass Bounce Management + + + Allows you to bypass the bounce list to ensure that the email is delivered to recipients. + Spam report and unsubscribe lists will still be checked; addresses on these other lists + will not receive the message. This filter cannot be combined with the bypass_list_management filter. + """ + + def __init__(self, enable=None): + """Create a BypassBounceManagement. + + :param enable: Whether emails should bypass bounce management. + :type enable: boolean, optional + """ + self._enable = None + + if enable is not None: + self.enable = enable + + @property + def enable(self): + """Indicates if this setting is enabled. + + :rtype: boolean + """ + return self._enable + + @enable.setter + def enable(self, value): + """Indicates if this setting is enabled. + + :param value: Indicates if this setting is enabled. + :type value: boolean + """ + self._enable = value + + def get(self): + """ + Get a JSON-ready representation of this BypassBounceManagement. + + :returns: This BypassBounceManagement, ready for use in a request body. + :rtype: dict + """ + bypass_bounce_management = {} + if self.enable is not None: + bypass_bounce_management["enable"] = self.enable + return bypass_bounce_management diff --git a/sendgrid/helpers/mail/bypass_list_management.py b/sendgrid/helpers/mail/bypass_list_management.py index bedc00c3d..ac13e3d75 100644 --- a/sendgrid/helpers/mail/bypass_list_management.py +++ b/sendgrid/helpers/mail/bypass_list_management.py @@ -13,7 +13,10 @@ def __init__(self, enable=None): :param enable: Whether emails should bypass list management. :type enable: boolean, optional """ - self.enable = enable + self._enable = None + + if enable is not None: + self.enable = enable @property def enable(self): @@ -25,6 +28,11 @@ def enable(self): @enable.setter def enable(self, value): + """Indicates if this setting is enabled. + + :param value: Indicates if this setting is enabled. + :type value: boolean + """ self._enable = value def get(self): diff --git a/sendgrid/helpers/mail/bypass_spam_management.py b/sendgrid/helpers/mail/bypass_spam_management.py new file mode 100644 index 000000000..9b2552eb9 --- /dev/null +++ b/sendgrid/helpers/mail/bypass_spam_management.py @@ -0,0 +1,47 @@ +class BypassSpamManagement(object): + """Setting for Bypass Spam Management + + Allows you to bypass the spam report list to ensure that the email is delivered to recipients. + Bounce and unsubscribe lists will still be checked; addresses on these other lists will not + receive the message. This filter cannot be combined with the bypass_list_management filter. + """ + + def __init__(self, enable=None): + """Create a BypassSpamManagement. + + :param enable: Whether emails should bypass spam management. + :type enable: boolean, optional + """ + self._enable = None + + if enable is not None: + self.enable = enable + + @property + def enable(self): + """Indicates if this setting is enabled. + + :rtype: boolean + """ + return self._enable + + @enable.setter + def enable(self, value): + """Indicates if this setting is enabled. + + :param value: Indicates if this setting is enabled. + :type value: boolean + """ + self._enable = value + + def get(self): + """ + Get a JSON-ready representation of this BypassSpamManagement. + + :returns: This BypassSpamManagement, ready for use in a request body. + :rtype: dict + """ + bypass_spam_management = {} + if self.enable is not None: + bypass_spam_management["enable"] = self.enable + return bypass_spam_management diff --git a/sendgrid/helpers/mail/bypass_unsubscribe_management.py b/sendgrid/helpers/mail/bypass_unsubscribe_management.py new file mode 100644 index 000000000..4867fac22 --- /dev/null +++ b/sendgrid/helpers/mail/bypass_unsubscribe_management.py @@ -0,0 +1,49 @@ +class BypassUnsubscribeManagement(object): + """Setting for Bypass Unsubscribe Management + + + Allows you to bypass the global unsubscribe list to ensure that the email is delivered to recipients. + Bounce and spam report lists will still be checked; addresses on these other lists will not receive + the message. This filter applies only to global unsubscribes and will not bypass group unsubscribes. + This filter cannot be combined with the bypass_list_management filter. + """ + + def __init__(self, enable=None): + """Create a BypassUnsubscribeManagement. + + :param enable: Whether emails should bypass unsubscribe management. + :type enable: boolean, optional + """ + self._enable = None + + if enable is not None: + self.enable = enable + + @property + def enable(self): + """Indicates if this setting is enabled. + + :rtype: boolean + """ + return self._enable + + @enable.setter + def enable(self, value): + """Indicates if this setting is enabled. + + :param value: Indicates if this setting is enabled. + :type value: boolean + """ + self._enable = value + + def get(self): + """ + Get a JSON-ready representation of this BypassUnsubscribeManagement. + + :returns: This BypassUnsubscribeManagement, ready for use in a request body. + :rtype: dict + """ + bypass_unsubscribe_management = {} + if self.enable is not None: + bypass_unsubscribe_management["enable"] = self.enable + return bypass_unsubscribe_management diff --git a/sendgrid/helpers/mail/category.py b/sendgrid/helpers/mail/category.py index f17da4ecf..0a6394c25 100644 --- a/sendgrid/helpers/mail/category.py +++ b/sendgrid/helpers/mail/category.py @@ -7,7 +7,10 @@ def __init__(self, name=None): :param name: The name of this category :type name: string, optional """ - self.name = name + self._name = None + + if name is not None: + self.name = name @property def name(self): @@ -19,6 +22,12 @@ def name(self): @name.setter def name(self, value): + """The name of this Category. Must be less than 255 characters. + + :param value: The name of this Category. Must be less than 255 + characters. + :type value: string + """ self._name = value def get(self): diff --git a/sendgrid/helpers/mail/cc_email.py b/sendgrid/helpers/mail/cc_email.py new file mode 100644 index 000000000..77b8ff285 --- /dev/null +++ b/sendgrid/helpers/mail/cc_email.py @@ -0,0 +1,5 @@ +from .email import Email + + +class Cc(Email): + """A cc email address with an optional name.""" diff --git a/sendgrid/helpers/mail/click_tracking.py b/sendgrid/helpers/mail/click_tracking.py index 90f0b2928..edfba41e8 100644 --- a/sendgrid/helpers/mail/click_tracking.py +++ b/sendgrid/helpers/mail/click_tracking.py @@ -9,8 +9,14 @@ def __init__(self, enable=None, enable_text=None): :param enable_text: If click tracking is on in your email's text/plain. :type enable_text: boolean, optional """ - self.enable = enable - self.enable_text = enable_text + self._enable = None + self._enable_text = None + + if enable is not None: + self.enable = enable + + if enable_text is not None: + self.enable_text = enable_text @property def enable(self): @@ -22,16 +28,31 @@ def enable(self): @enable.setter def enable(self, value): + """Indicates if this setting is enabled. + + :param value: Indicates if this setting is enabled. + :type value: boolean + """ self._enable = value @property def enable_text(self): """Indicates if this setting should be included in the text/plain - portion of your email.""" + portion of your email. + + :rtype: boolean + """ return self._enable_text @enable_text.setter def enable_text(self, value): + """Indicates if this setting should be included in the text/plain + portion of your email. + + :param value: Indicates if this setting should be included in the + text/plain portion of your email. + :type value: boolean + """ self._enable_text = value def get(self): diff --git a/sendgrid/helpers/mail/content.py b/sendgrid/helpers/mail/content.py index 604fee55d..618eee917 100644 --- a/sendgrid/helpers/mail/content.py +++ b/sendgrid/helpers/mail/content.py @@ -1,45 +1,69 @@ +from .validators import ValidateApiKey + + + class Content(object): """Content to be included in your email. You must specify at least one mime type in the Contents of your email. """ - def __init__(self, type_=None, value=None): - """Create a Content with the specified MIME type and value. + def __init__(self, mime_type, content): + """Create a Content with the specified MIME type and content. - :param type_: MIME type of this Content (e.g. "text/plain"). - :type type_: string, optional - :param value: The actual content. - :type value: string, optional + :param mime_type: MIME type of this Content (e.g. "text/plain"). + :type mime_type: string + :param content: The actual content. + :type content: string """ - self.type = type_ - self.value = value + self._mime_type = None + self._content = None + self._validator = ValidateApiKey() + + if mime_type is not None: + self.mime_type = mime_type + + if content is not None: + self.content = content @property - def type(self): + def mime_type(self): """The MIME type of the content you are including in your email. - - For example, "text/plain" or "text/html". + For example, "text/plain" or "text/html" or "text/x-amp-html". :rtype: string """ - return self._type + return self._mime_type - @type.setter - def type(self, value): - self._type = value + @mime_type.setter + def mime_type(self, value): + """The MIME type of the content you are including in your email. + For example, "text/plain" or "text/html" or "text/x-amp-html". + + :param value: The MIME type of the content you are including in your + email. + For example, "text/plain" or "text/html" or "text/x-amp-html". + :type value: string + """ + self._mime_type = value @property - def value(self): + def content(self): """The actual content (of the specified mime type). :rtype: string """ - return self._value + return self._content - @value.setter - def value(self, value): - self._value = value + @content.setter + def content(self, value): + """The actual content (of the specified mime type). + + :param value: The actual content (of the specified mime type). + :type value: string + """ + self._validator.validate_message_dict(value) + self._content = value def get(self): """ @@ -49,9 +73,9 @@ def get(self): :rtype: dict """ content = {} - if self.type is not None: - content["type"] = self.type + if self.mime_type is not None: + content["type"] = self.mime_type - if self.value is not None: - content["value"] = self.value + if self.content is not None: + content["value"] = self.content return content diff --git a/sendgrid/helpers/mail/content_id.py b/sendgrid/helpers/mail/content_id.py new file mode 100644 index 000000000..0fff30107 --- /dev/null +++ b/sendgrid/helpers/mail/content_id.py @@ -0,0 +1,50 @@ +class ContentId(object): + """The ContentId of an Attachment.""" + + def __init__(self, content_id=None): + """Create a ContentId object + + :param content_id: The content id for the attachment. + This is used when the Disposition is set to "inline" + and the attachment is an image, allowing the file to + be displayed within the email body. + :type content_id: string, optional + """ + self._content_id = None + + if content_id is not None: + self.content_id = content_id + + @property + def content_id(self): + """The content id for the attachment. + This is used when the Disposition is set to "inline" and the + attachment is an image, allowing the file to be displayed within + the email body. + + :rtype: string + """ + return self._content_id + + @content_id.setter + def content_id(self, value): + """The content id for the attachment. + This is used when the Disposition is set to "inline" and the + attachment is an image, allowing the file to be displayed within + the email body. + + :param value: The content id for the attachment. + This is used when the Disposition is set to "inline" and the attachment + is an image, allowing the file to be displayed within the email body. + :type value: string + """ + self._content_id = value + + def get(self): + """ + Get a JSON-ready representation of this ContentId. + + :returns: This ContentId, ready for use in a request body. + :rtype: string + """ + return self.content_id diff --git a/sendgrid/helpers/mail/custom_arg.py b/sendgrid/helpers/mail/custom_arg.py index f4d92601e..63b225573 100644 --- a/sendgrid/helpers/mail/custom_arg.py +++ b/sendgrid/helpers/mail/custom_arg.py @@ -7,10 +7,27 @@ class CustomArg(object): Personalization. May not exceed 10,000 bytes. """ - def __init__(self, key=None, value=None): - """Create a CustomArg with the given key and value.""" - self.key = key - self.value = value + def __init__(self, key=None, value=None, p=None): + """Create a CustomArg with the given key and value. + + :param key: Key for this CustomArg + :type key: string, optional + :param value: Value of this CustomArg + :type value: string, optional + :param p: p is the Personalization object or Personalization + object index + :type p: Personalization, integer, optional + """ + self._key = None + self._value = None + self._personalization = None + + if key is not None: + self.key = key + if value is not None: + self.value = value + if p is not None: + self.personalization = p @property def key(self): @@ -22,17 +39,48 @@ def key(self): @key.setter def key(self, value): + """Key for this CustomArg. + + :param value: Key for this CustomArg. + :type value: string + """ self._key = value @property def value(self): - """Value of this CustomArg.""" + """Value of this CustomArg. + + :rtype: string + """ return self._value @value.setter def value(self, value): + """Value of this CustomArg. + + :param value: Value of this CustomArg. + :type value: string + """ self._value = value + @property + def personalization(self): + """The Personalization object or Personalization object index + + :rtype: Personalization, integer + """ + return self._personalization + + @personalization.setter + def personalization(self, value): + """The Personalization object or Personalization object index + + :param value: The Personalization object or Personalization object + index + :type value: Personalization, integer + """ + self._personalization = value + def get(self): """ Get a JSON-ready representation of this CustomArg. diff --git a/sendgrid/helpers/mail/disposition.py b/sendgrid/helpers/mail/disposition.py new file mode 100644 index 000000000..a0bdc3543 --- /dev/null +++ b/sendgrid/helpers/mail/disposition.py @@ -0,0 +1,72 @@ +class Disposition(object): + """The content-disposition of the Attachment specifying how you would like + the attachment to be displayed.""" + + def __init__(self, disposition=None): + """Create a Disposition object + + :param disposition: The content-disposition of the attachment, + specifying display style. + Specifies how you would like the attachment to be + displayed. + - "inline" results in the attached file being + displayed automatically within the message. + - "attachment" results in the attached file + requiring some action to display (e.g. opening + or downloading the file). + If unspecified, "attachment" is used. Must be one + of the two choices. + :type disposition: string, optional + """ + self._disposition = None + + if disposition is not None: + self.disposition = disposition + + @property + def disposition(self): + """The content-disposition of the attachment, specifying display style. + Specifies how you would like the attachment to be displayed. + - "inline" results in the attached file being displayed + automatically within the message. + - "attachment" results in the attached file requiring some action to + display (e.g. opening or downloading the file). + If unspecified, "attachment" is used. Must be one of the two + choices. + + :rtype: string + """ + return self._disposition + + @disposition.setter + def disposition(self, value): + """The content-disposition of the attachment, specifying display style. + Specifies how you would like the attachment to be displayed. + - "inline" results in the attached file being displayed + automatically within the message. + - "attachment" results in the attached file requiring some action to + display (e.g. opening or downloading the file). + If unspecified, "attachment" is used. Must be one of the two + choices. + + :param value: The content-disposition of the attachment, specifying + display style. + Specifies how you would like the attachment to be displayed. + - "inline" results in the attached file being displayed + automatically within the message. + - "attachment" results in the attached file requiring some action to + display (e.g. opening or downloading the file). + If unspecified, "attachment" is used. Must be one of the two + choices. + :type value: string + """ + self._disposition = value + + def get(self): + """ + Get a JSON-ready representation of this Disposition. + + :returns: This Disposition, ready for use in a request body. + :rtype: string + """ + return self.disposition diff --git a/sendgrid/helpers/mail/dynamic_template_data.py b/sendgrid/helpers/mail/dynamic_template_data.py new file mode 100644 index 000000000..e12967b70 --- /dev/null +++ b/sendgrid/helpers/mail/dynamic_template_data.py @@ -0,0 +1,73 @@ +class DynamicTemplateData(object): + """To send a dynamic template, specify the template ID with the + template_id parameter. + """ + + def __init__(self, dynamic_template_data=None, p=0): + """Data for a transactional template. + Should be JSON-serializable structure. + + :param dynamic_template_data: Data for a transactional template. + :type dynamic_template_data: A JSON-serializable structure + :param name: p is the Personalization object or Personalization object + index + :type name: Personalization, integer, optional + """ + self._dynamic_template_data = None + self._personalization = None + + if dynamic_template_data is not None: + self.dynamic_template_data = dynamic_template_data + if p is not None: + self.personalization = p + + @property + def dynamic_template_data(self): + """Data for a transactional template. + + :rtype: A JSON-serializable structure + """ + return self._dynamic_template_data + + @dynamic_template_data.setter + def dynamic_template_data(self, value): + """Data for a transactional template. + + :param value: Data for a transactional template. + :type value: A JSON-serializable structure + """ + self._dynamic_template_data = value + + @property + def personalization(self): + """The Personalization object or Personalization object index + + :rtype: Personalization, integer + """ + return self._personalization + + @personalization.setter + def personalization(self, value): + """The Personalization object or Personalization object index + + :param value: The Personalization object or Personalization object + index + :type value: Personalization, integer + """ + self._personalization = value + + def __str__(self): + """Get a JSON representation of this object. + + :rtype: A JSON-serializable structure + """ + return str(self.get()) + + def get(self): + """ + Get a JSON-ready representation of this DynamicTemplateData object. + + :returns: Data for a transactional template. + :rtype: A JSON-serializable structure. + """ + return self.dynamic_template_data diff --git a/sendgrid/helpers/mail/email.py b/sendgrid/helpers/mail/email.py index 0cc633986..aeab26afa 100644 --- a/sendgrid/helpers/mail/email.py +++ b/sendgrid/helpers/mail/email.py @@ -3,27 +3,61 @@ except ImportError: import email.utils as rfc822 +try: + basestring = basestring +except NameError: + # Define basestring when Python >= 3.0 + basestring = str + class Email(object): """An email address with an optional name.""" - def __init__(self, email=None, name=None): + def __init__(self, + email=None, + name=None, + substitutions=None, + subject=None, + p=0, + dynamic_template_data=None): """Create an Email with the given address and name. Either fill the separate name and email fields, or pass all information in the email parameter (e.g. email="dude Fella "). :param email: Email address, or name and address in standard format. - :type email: string + :type email: string, optional :param name: Name for this sender or recipient. - :type name: string + :type name: string, optional + :param substitutions: String substitutions to be applied to the email. + :type substitutions: list(Substitution), optional + :param subject: Subject for this sender or recipient. + :type subject: string, optional + :param p: p is the Personalization object or Personalization object + index + :type p: Personalization, integer, optional + :param dynamic_template_data: Data for a dynamic transactional template. + :type dynamic_template_data: DynamicTemplateData, optional """ + self._name = None + self._email = None + self._personalization = p + if email and not name: - # allows passing emails as "dude Fella " + # allows passing emails as "Example Name " self.parse_email(email) else: # allows backwards compatibility for Email(email, name) - self.email = email - self.name = name + if email is not None: + self.email = email + + if name is not None: + self.name = name + + # Note that these only apply to To Emails (see Personalization.add_to) + # and should be moved but have not been for compatibility. + self._substitutions = substitutions + self._dynamic_template_data = dynamic_template_data + self._subject = subject @property def name(self): @@ -35,6 +69,14 @@ def name(self): @name.setter def name(self, value): + """Name associated with this email. + + :param value: Name associated with this email. + :type value: string + """ + if not (value is None or isinstance(value, basestring)): + raise TypeError('name must be of type string.') + self._name = value @property @@ -44,29 +86,115 @@ def email(self): See http://tools.ietf.org/html/rfc3696#section-3 and its errata http://www.rfc-editor.org/errata_search.php?rfc=3696 for information on valid email addresses. + + :rtype: string """ return self._email @email.setter def email(self, value): + """Email address. + + See http://tools.ietf.org/html/rfc3696#section-3 and its errata + http://www.rfc-editor.org/errata_search.php?rfc=3696 for information + on valid email addresses. + + :param value: Email address. + See http://tools.ietf.org/html/rfc3696#section-3 and its errata + http://www.rfc-editor.org/errata_search.php?rfc=3696 for information + on valid email addresses. + :type value: string + """ self._email = value - def get(self): + @property + def substitutions(self): + """A list of Substitution objects. These substitutions will apply to + the text and html content of the body of your email, in addition + to the subject and reply-to parameters. The total collective size + of your substitutions may not exceed 10,000 bytes per + personalization object. + + :rtype: list(Substitution) """ - Get a JSON-ready representation of this Email. + return self._substitutions + + @substitutions.setter + def substitutions(self, value): + """A list of Substitution objects. These substitutions will apply to + the text and html content of the body of your email, in addition to + the subject and reply-to parameters. The total collective size of + your substitutions may not exceed 10,000 bytes per personalization + object. + + :param value: A list of Substitution objects. These substitutions will + apply to the text and html content of the body of your email, in + addition to the subject and reply-to parameters. The total collective + size of your substitutions may not exceed 10,000 bytes per + personalization object. + :type value: list(Substitution) + """ + self._substitutions = value - :returns: This Email, ready for use in a request body. - :rtype: dict + @property + def dynamic_template_data(self): + """Data for a dynamic transactional template. + + :rtype: DynamicTemplateData """ - email = {} - if self.name is not None: - email["name"] = self.name + return self._dynamic_template_data - if self.email is not None: - email["email"] = self.email - return email + @dynamic_template_data.setter + def dynamic_template_data(self, value): + """Data for a dynamic transactional template. + + :param value: DynamicTemplateData + :type value: DynamicTemplateData + """ + self._dynamic_template_data = value + + @property + def subject(self): + """Subject for this sender or recipient. + + :rtype: string + """ + return self._subject + + @subject.setter + def subject(self, value): + """Subject for this sender or recipient. + + :param value: Subject for this sender or recipient. + :type value: string, optional + """ + self._subject = value + + @property + def personalization(self): + """The Personalization object or Personalization object index + + :rtype: Personalization, integer + """ + return self._personalization + + @personalization.setter + def personalization(self, value): + """The Personalization object or Personalization object index + + :param value: The Personalization object or Personalization object + index + :type value: Personalization, integer + """ + self._personalization = value def parse_email(self, email_info): + """Allows passing emails as "Example Name " + + :param email_info: Allows passing emails as + "Example Name " + :type email_info: string + """ name, email = rfc822.parseaddr(email_info) # more than likely a string was passed here instead of an email address @@ -83,3 +211,18 @@ def parse_email(self, email_info): self.name = name self.email = email return name, email + + def get(self): + """ + Get a JSON-ready representation of this Email. + + :returns: This Email, ready for use in a request body. + :rtype: dict + """ + email = {} + if self.name is not None: + email["name"] = self.name + + if self.email is not None: + email["email"] = self.email + return email diff --git a/sendgrid/helpers/mail/exceptions.py b/sendgrid/helpers/mail/exceptions.py new file mode 100644 index 000000000..cbc311345 --- /dev/null +++ b/sendgrid/helpers/mail/exceptions.py @@ -0,0 +1,65 @@ +################################################################ +# Various types of extensible Twilio SendGrid related exceptions +################################################################ + + +class SendGridException(Exception): + """Wrapper/default SendGrid-related exception""" + pass + + +class ApiKeyIncludedException(SendGridException): + """Exception raised for when Twilio SendGrid API Key included in message text""" + + def __init__(self, + expression="Email body", + message="Twilio SendGrid API Key detected"): + """Create an exception for when Twilio SendGrid API Key included in message text + + :param expression: Input expression in which the error occurred + :type expression: string + :param message: Explanation of the error + :type message: string + """ + self._expression = None + self._message = None + + if expression is not None: + self.expression = expression + + if message is not None: + self.message = message + + @property + def expression(self): + """Input expression in which the error occurred + + :rtype: string + """ + return self._expression + + @expression.setter + def expression(self, value): + """Input expression in which the error occurred + + :param value: Input expression in which the error occurred + :type value: string + """ + self._expression = value + + @property + def message(self): + """Explanation of the error + + :rtype: string + """ + return self._message + + @message.setter + def message(self, value): + """Explanation of the error + + :param value: Explanation of the error + :type value: string + """ + self._message = value diff --git a/sendgrid/helpers/mail/file_content.py b/sendgrid/helpers/mail/file_content.py new file mode 100644 index 000000000..c1eb81fc6 --- /dev/null +++ b/sendgrid/helpers/mail/file_content.py @@ -0,0 +1,39 @@ +class FileContent(object): + """The Base64 encoded content of an Attachment.""" + + def __init__(self, file_content=None): + """Create a FileContent object + + :param file_content: The Base64 encoded content of the attachment + :type file_content: string, optional + """ + self._file_content = None + + if file_content is not None: + self.file_content = file_content + + @property + def file_content(self): + """The Base64 encoded content of the attachment. + + :rtype: string + """ + return self._file_content + + @file_content.setter + def file_content(self, value): + """The Base64 encoded content of the attachment. + + :param value: The Base64 encoded content of the attachment. + :type value: string + """ + self._file_content = value + + def get(self): + """ + Get a JSON-ready representation of this FileContent. + + :returns: This FileContent, ready for use in a request body. + :rtype: string + """ + return self.file_content diff --git a/sendgrid/helpers/mail/file_name.py b/sendgrid/helpers/mail/file_name.py new file mode 100644 index 000000000..3a4e3ff2b --- /dev/null +++ b/sendgrid/helpers/mail/file_name.py @@ -0,0 +1,39 @@ +class FileName(object): + """The filename of an Attachment.""" + + def __init__(self, file_name=None): + """Create a FileName object + + :param file_name: The file name of the attachment + :type file_name: string, optional + """ + self._file_name = None + + if file_name is not None: + self.file_name = file_name + + @property + def file_name(self): + """The file name of the attachment. + + :rtype: string + """ + return self._file_name + + @file_name.setter + def file_name(self, value): + """The file name of the attachment. + + :param value: The file name of the attachment. + :type value: string + """ + self._file_name = value + + def get(self): + """ + Get a JSON-ready representation of this FileName. + + :returns: This FileName, ready for use in a request body. + :rtype: string + """ + return self.file_name diff --git a/sendgrid/helpers/mail/file_type.py b/sendgrid/helpers/mail/file_type.py new file mode 100644 index 000000000..a30e6edfa --- /dev/null +++ b/sendgrid/helpers/mail/file_type.py @@ -0,0 +1,39 @@ +class FileType(object): + """The MIME type of the content you are attaching to an Attachment.""" + + def __init__(self, file_type=None): + """Create a FileType object + + :param file_type: The MIME type of the content you are attaching + :type file_type: string, optional + """ + self._file_type = None + + if file_type is not None: + self.file_type = file_type + + @property + def file_type(self): + """The MIME type of the content you are attaching. + + :rtype: string + """ + return self._file_type + + @file_type.setter + def file_type(self, mime_type): + """The MIME type of the content you are attaching. + + :param mime_type: The MIME type of the content you are attaching. + :rtype mime_type: string + """ + self._file_type = mime_type + + def get(self): + """ + Get a JSON-ready representation of this FileType. + + :returns: This FileType, ready for use in a request body. + :rtype: string + """ + return self.file_type diff --git a/sendgrid/helpers/mail/footer_html.py b/sendgrid/helpers/mail/footer_html.py new file mode 100644 index 000000000..c8b5ac1a5 --- /dev/null +++ b/sendgrid/helpers/mail/footer_html.py @@ -0,0 +1,39 @@ +class FooterHtml(object): + """The HTML in a Footer.""" + + def __init__(self, footer_html=None): + """Create a FooterHtml object + + :param footer_html: The html content of your footer. + :type footer_html: string, optional + """ + self._footer_html = None + + if footer_html is not None: + self.footer_html = footer_html + + @property + def footer_html(self): + """The html content of your footer. + + :rtype: string + """ + return self._footer_html + + @footer_html.setter + def footer_html(self, html): + """The html content of your footer. + + :param html: The html content of your footer. + :type html: string + """ + self._footer_html = html + + def get(self): + """ + Get a JSON-ready representation of this FooterHtml. + + :returns: This FooterHtml, ready for use in a request body. + :rtype: string + """ + return self.footer_html diff --git a/sendgrid/helpers/mail/footer_settings.py b/sendgrid/helpers/mail/footer_settings.py index b50786309..1b0efeb1a 100644 --- a/sendgrid/helpers/mail/footer_settings.py +++ b/sendgrid/helpers/mail/footer_settings.py @@ -7,13 +7,22 @@ def __init__(self, enable=None, text=None, html=None): :param enable: Whether this footer should be applied. :type enable: boolean, optional :param text: Text content of this footer - :type text: string, optional + :type text: FooterText, optional :param html: HTML content of this footer - :type html: string, optional + :type html: FooterHtml, optional """ - self.enable = enable - self.text = text - self.html = html + self._enable = None + self._text = None + self._html = None + + if enable is not None: + self.enable = enable + + if text is not None: + self.text = text + + if html is not None: + self.html = html @property def enable(self): @@ -25,6 +34,11 @@ def enable(self): @enable.setter def enable(self, value): + """Indicates if this setting is enabled. + + :param value: Indicates if this setting is enabled. + :type value: boolean + """ self._enable = value @property @@ -37,6 +51,11 @@ def text(self): @text.setter def text(self, value): + """The plain text content of your footer. + + :param value: The plain text content of your footer. + :type value: string + """ self._text = value @property @@ -49,6 +68,11 @@ def html(self): @html.setter def html(self, value): + """The HTML content of your footer. + + :param value: The HTML content of your footer. + :type value: string + """ self._html = value def get(self): @@ -63,8 +87,8 @@ def get(self): footer_settings["enable"] = self.enable if self.text is not None: - footer_settings["text"] = self.text + footer_settings["text"] = self.text.get() if self.html is not None: - footer_settings["html"] = self.html + footer_settings["html"] = self.html.get() return footer_settings diff --git a/sendgrid/helpers/mail/footer_text.py b/sendgrid/helpers/mail/footer_text.py new file mode 100644 index 000000000..06f968920 --- /dev/null +++ b/sendgrid/helpers/mail/footer_text.py @@ -0,0 +1,39 @@ +class FooterText(object): + """The text in an Footer.""" + + def __init__(self, footer_text=None): + """Create a FooterText object + + :param footer_text: The plain text content of your footer. + :type footer_text: string, optional + """ + self._footer_text = None + + if footer_text is not None: + self.footer_text = footer_text + + @property + def footer_text(self): + """The plain text content of your footer. + + :rtype: string + """ + return self._footer_text + + @footer_text.setter + def footer_text(self, value): + """The plain text content of your footer. + + :param value: The plain text content of your footer. + :type value: string + """ + self._footer_text = value + + def get(self): + """ + Get a JSON-ready representation of this FooterText. + + :returns: This FooterText, ready for use in a request body. + :rtype: string + """ + return self.footer_text diff --git a/sendgrid/helpers/mail/from_email.py b/sendgrid/helpers/mail/from_email.py new file mode 100644 index 000000000..0f6f231ce --- /dev/null +++ b/sendgrid/helpers/mail/from_email.py @@ -0,0 +1,5 @@ +from .email import Email + + +class From(Email): + """A from email address with an optional name.""" diff --git a/sendgrid/helpers/mail/ganalytics.py b/sendgrid/helpers/mail/ganalytics.py index d8aa6cf4a..5f6327154 100644 --- a/sendgrid/helpers/mail/ganalytics.py +++ b/sendgrid/helpers/mail/ganalytics.py @@ -23,12 +23,30 @@ def __init__(self, :param utm_campaign: The name of the campaign. :type utm_campaign: string, optional """ - self.enable = enable - self.utm_source = utm_source - self.utm_medium = utm_medium - self.utm_term = utm_term - self.utm_content = utm_content - self.utm_campaign = utm_campaign + self._enable = None + self._utm_source = None + self._utm_medium = None + self._utm_term = None + self._utm_content = None + self._utm_campaign = None + + self.__set_field("enable", enable) + self.__set_field("utm_source", utm_source) + self.__set_field("utm_medium", utm_medium) + self.__set_field("utm_term", utm_term) + self.__set_field("utm_content", utm_content) + self.__set_field("utm_campaign", utm_campaign) + + def __set_field(self, field, value): + """ Sets a field to the provided value if value is not None + + :param field: Name of the field + :type field: string + :param value: Value to be set, ignored if None + :type value: Any + """ + if value is not None: + setattr(self, field, value) @property def enable(self): @@ -40,19 +58,31 @@ def enable(self): @enable.setter def enable(self, value): + """Indicates if this setting is enabled. + + :param value: Indicates if this setting is enabled. + :type value: boolean + """ self._enable = value @property def utm_source(self): """Name of the referrer source. - e.g. Google, SomeDomain.com, or Marketing Email + :rtype: string """ return self._utm_source @utm_source.setter def utm_source(self, value): + """Name of the referrer source. + e.g. Google, SomeDomain.com, or Marketing Email + + :param value: Name of the referrer source. + e.g. Google, SomeDomain.com, or Marketing Email + :type value: string + """ self._utm_source = value @property @@ -65,6 +95,11 @@ def utm_medium(self): @utm_medium.setter def utm_medium(self, value): + """Name of the marketing medium (e.g. Email). + + :param value: Name of the marketing medium (e.g. Email). + :type value: string + """ self._utm_medium = value @property @@ -77,6 +112,11 @@ def utm_term(self): @utm_term.setter def utm_term(self, value): + """Used to identify any paid keywords. + + :param value: Used to identify any paid keywords. + :type value: string + """ self._utm_term = value @property @@ -89,6 +129,11 @@ def utm_content(self): @utm_content.setter def utm_content(self, value): + """Used to differentiate your campaign from advertisements. + + :param value: Used to differentiate your campaign from advertisements. + :type value: string + """ self._utm_content = value @property @@ -101,6 +146,11 @@ def utm_campaign(self): @utm_campaign.setter def utm_campaign(self, value): + """The name of the campaign. + + :param value: The name of the campaign. + :type value: string + """ self._utm_campaign = value def get(self): @@ -110,17 +160,17 @@ def get(self): :returns: This Ganalytics, ready for use in a request body. :rtype: dict """ + keys = ["enable", "utm_source", "utm_medium", "utm_term", + "utm_content", "utm_campaign"] + ganalytics = {} - if self.enable is not None: - ganalytics["enable"] = self.enable - if self.utm_source is not None: - ganalytics["utm_source"] = self.utm_source - if self.utm_medium is not None: - ganalytics["utm_medium"] = self.utm_medium - if self.utm_term is not None: - ganalytics["utm_term"] = self.utm_term - if self.utm_content is not None: - ganalytics["utm_content"] = self.utm_content - if self.utm_campaign is not None: - ganalytics["utm_campaign"] = self.utm_campaign + + for key in keys: + value = getattr(self, key, None) + if value is not None: + if isinstance(value, bool) or isinstance(value, str): + ganalytics[key] = value + else: + ganalytics[key] = value.get() + return ganalytics diff --git a/sendgrid/helpers/mail/group_id.py b/sendgrid/helpers/mail/group_id.py new file mode 100644 index 000000000..667785310 --- /dev/null +++ b/sendgrid/helpers/mail/group_id.py @@ -0,0 +1,39 @@ +class GroupId(object): + """The unsubscribe group ID to associate with this email.""" + + def __init__(self, group_id=None): + """Create a GroupId object + + :param group_id: The unsubscribe group to associate with this email. + :type group_id: integer, optional + """ + self._group_id = None + + if group_id is not None: + self.group_id = group_id + + @property + def group_id(self): + """The unsubscribe group to associate with this email. + + :rtype: integer + """ + return self._group_id + + @group_id.setter + def group_id(self, value): + """The unsubscribe group to associate with this email. + + :param value: The unsubscribe group to associate with this email. + :type value: integer + """ + self._group_id = value + + def get(self): + """ + Get a JSON-ready representation of this GroupId. + + :returns: This GroupId, ready for use in a request body. + :rtype: integer + """ + return self.group_id diff --git a/sendgrid/helpers/mail/groups_to_display.py b/sendgrid/helpers/mail/groups_to_display.py new file mode 100644 index 000000000..2e3fca77a --- /dev/null +++ b/sendgrid/helpers/mail/groups_to_display.py @@ -0,0 +1,48 @@ +class GroupsToDisplay(object): + """The unsubscribe groups that you would like to be displayed on the + unsubscribe preferences page..""" + + def __init__(self, groups_to_display=None): + """Create a GroupsToDisplay object + + :param groups_to_display: An array containing the unsubscribe groups + that you would like to be displayed on the + unsubscribe preferences page. + :type groups_to_display: array of integers, optional + """ + self._groups_to_display = None + + if groups_to_display is not None: + self.groups_to_display = groups_to_display + + @property + def groups_to_display(self): + """An array containing the unsubscribe groups that you would like to be + displayed on the unsubscribe preferences page. + + :rtype: array(int) + """ + return self._groups_to_display + + @groups_to_display.setter + def groups_to_display(self, value): + """An array containing the unsubscribe groups that you would like to be + displayed on the unsubscribe preferences page. + + :param value: An array containing the unsubscribe groups that you + would like to be displayed on the unsubscribe + preferences page. + :type value: array(int) + """ + if value is not None and len(value) > 25: + raise ValueError("New groups_to_display exceeds max length of 25.") + self._groups_to_display = value + + def get(self): + """ + Get a JSON-ready representation of this GroupsToDisplay. + + :returns: This GroupsToDisplay, ready for use in a request body. + :rtype: array of integers + """ + return self.groups_to_display diff --git a/sendgrid/helpers/mail/header.py b/sendgrid/helpers/mail/header.py index 8d581cb4a..7f3bd4c4d 100644 --- a/sendgrid/helpers/mail/header.py +++ b/sendgrid/helpers/mail/header.py @@ -7,16 +7,27 @@ class Header(object): Content-Transfer-Encoding, To, From, Subject, Reply-To, CC, BCC """ - def __init__(self, key=None, value=None): + def __init__(self, key=None, value=None, p=None): """Create a Header. :param key: The name of the header (e.g. "Date") :type key: string, optional :param value: The header's value (e.g. "2013-02-27 1:23:45 PM PDT") :type value: string, optional + :param name: p is the Personalization object or Personalization object + index + :type name: Personalization, integer, optional """ - self.key = key - self.value = value + self._key = None + self._value = None + self._personalization = None + + if key is not None: + self.key = key + if value is not None: + self.value = value + if p is not None: + self.personalization = p @property def key(self): @@ -28,6 +39,11 @@ def key(self): @key.setter def key(self, value): + """The name of the header. + + :param value: The name of the header. + :type value: string + """ self._key = value @property @@ -40,8 +56,31 @@ def value(self): @value.setter def value(self, value): + """The value of the header. + + :param value: The value of the header. + :type value: string + """ self._value = value + @property + def personalization(self): + """The Personalization object or Personalization object index + + :rtype: Personalization, integer + """ + return self._personalization + + @personalization.setter + def personalization(self, value): + """The Personalization object or Personalization object index + + :param value: The Personalization object or Personalization object + index + :type value: Personalization, integer + """ + self._personalization = value + def get(self): """ Get a JSON-ready representation of this Header. diff --git a/sendgrid/helpers/mail/html_content.py b/sendgrid/helpers/mail/html_content.py new file mode 100644 index 000000000..c3f40d53c --- /dev/null +++ b/sendgrid/helpers/mail/html_content.py @@ -0,0 +1,59 @@ +from .content import Content +from .validators import ValidateApiKey + + +class HtmlContent(Content): + """HTML content to be included in your email.""" + + def __init__(self, content): + """Create an HtmlContent with the specified MIME type and content. + + :param content: The HTML content. + :type content: string + """ + self._content = None + self._validator = ValidateApiKey() + + if content is not None: + self.content = content + + @property + def mime_type(self): + """The MIME type for HTML content. + + :rtype: string + """ + return "text/html" + + @property + def content(self): + """The actual HTML content. + + :rtype: string + """ + return self._content + + @content.setter + def content(self, value): + """The actual HTML content. + + :param value: The actual HTML content. + :type value: string + """ + self._validator.validate_message_dict(value) + self._content = value + + def get(self): + """ + Get a JSON-ready representation of this HtmlContent. + + :returns: This HtmlContent, ready for use in a request body. + :rtype: dict + """ + content = {} + if self.mime_type is not None: + content["type"] = self.mime_type + + if self.content is not None: + content["value"] = self.content + return content diff --git a/sendgrid/helpers/mail/ip_pool_name.py b/sendgrid/helpers/mail/ip_pool_name.py new file mode 100644 index 000000000..8ba8d91b3 --- /dev/null +++ b/sendgrid/helpers/mail/ip_pool_name.py @@ -0,0 +1,40 @@ +class IpPoolName(object): + """The IP Pool that you would like to send this email from.""" + + def __init__(self, ip_pool_name=None): + """Create a IpPoolName object + + :param ip_pool_name: The IP Pool that you would like to send this + email from. + :type ip_pool_name: string, optional + """ + self._ip_pool_name = None + + if ip_pool_name is not None: + self.ip_pool_name = ip_pool_name + + @property + def ip_pool_name(self): + """The IP Pool that you would like to send this email from. + + :rtype: string + """ + return self._ip_pool_name + + @ip_pool_name.setter + def ip_pool_name(self, value): + """The IP Pool that you would like to send this email from. + + :param value: The IP Pool that you would like to send this email from. + :type value: string + """ + self._ip_pool_name = value + + def get(self): + """ + Get a JSON-ready representation of this IpPoolName. + + :returns: This IpPoolName, ready for use in a request body. + :rtype: string + """ + return self.ip_pool_name diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 116afb46e..e475fe764 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -1,381 +1,1041 @@ -"""v3/mail/send response body builder""" -from .personalization import Personalization +"""Twilio SendGrid v3/mail/send response body builder""" +from .bcc_email import Bcc +from .cc_email import Cc +from .content import Content +from .custom_arg import CustomArg +from .dynamic_template_data import DynamicTemplateData +from .email import Email +from .from_email import From from .header import Header +from .mime_type import MimeType +from .personalization import Personalization +from .reply_to import ReplyTo +from .send_at import SendAt +from .subject import Subject +from .substitution import Substitution +from .template_id import TemplateId +from .to_email import To class Mail(object): - """A request to be sent with the SendGrid v3 Mail Send API (v3/mail/send). + """Creates the response body for v3/mail/send""" - Use get() to get the request body. - """ def __init__( - self, from_email=None, subject=None, to_email=None, content=None): - """Create a Mail object. - - If parameters are supplied, all parameters must be present. - :param from_email: Email address to send from. - :type from_email: Email, optional - :param subject: Subject line of emails. - :type subject: string, optional - :param to_email: Email address to send to. - :type to_email: Email, optional - :param content: Content of the message. - :type content: Content, optional + self, + from_email=None, + to_emails=None, + subject=None, + plain_text_content=None, + html_content=None, + amp_html_content=None, + global_substitutions=None, + is_multiple=False): """ - self._from_email = None - self._subject = None - self._template_id = None - self._send_at = None - self._batch_id = None + Creates the response body for a v3/mail/send API call + + :param from_email: The email address of the sender + :type from_email: From, tuple, optional + :param subject: The subject of the email + :type subject: Subject, optional + :param to_emails: The email address of the recipient + :type to_emails: To, str, tuple, list(str), list(tuple), + list(To), optional + :param plain_text_content: The plain text body of the email + :type plain_text_content: string, optional + :param html_content: The html body of the email + :type html_content: string, optional + :param amp_html_content: The amp-html body of the email + :type amp_html_content: string, optional + """ + self._attachments = None + self._categories = None + self._contents = None + self._custom_args = None + self._headers = None + self._personalizations = [] + self._sections = None self._asm = None + self._batch_id = None + self._from_email = None self._ip_pool_name = None self._mail_settings = None - self._tracking_settings = None self._reply_to = None - self._personalizations = [] - self._contents = [] - self._attachments = [] - self._sections = [] - self._headers = [] - self._categories = [] - self._custom_args = [] - - # Minimum required to send an email - if from_email and subject and to_email and content: + self._reply_to_list = None + self._send_at = None + self._subject = None + self._template_id = None + self._tracking_settings = None + + # Minimum required data to send a single email + if from_email is not None: self.from_email = from_email + if to_emails is not None: + self.add_to(to_emails, global_substitutions, is_multiple) + if subject is not None: self.subject = subject - personalization = Personalization() - personalization.add_to(to_email) - self.add_personalization(personalization) - self.add_content(content) + if plain_text_content is not None: + self.add_content(plain_text_content, MimeType.text) + if amp_html_content is not None: + self.add_content(amp_html_content, MimeType.amp) + if html_content is not None: + self.add_content(html_content, MimeType.html) def __str__(self): - """Get a JSON representation of this Mail request. + """A JSON-ready string representation of this Mail object. + :returns: A JSON-ready string representation of this Mail object. :rtype: string """ return str(self.get()) - def get(self): - """Get a response body for this Mail. + def _ensure_append(self, new_items, append_to, index=0): + """Ensure an item is appended to a list or create a new empty list - :rtype: dict + :param new_items: the item(s) to append + :type new_items: list(obj) + :param append_to: the list on which to append the items + :type append_to: list() + :param index: index of the list on which to append the items + :type index: int """ - mail = {} - if self.from_email is not None: - mail["from"] = self.from_email.get() - if self.subject is not None: - mail["subject"] = self.subject - - if self.personalizations: - mail["personalizations"] = [ - personalization.get() - for personalization in self.personalizations - ] - - if self.contents: - mail["content"] = [ob.get() for ob in self.contents] - - if self.attachments: - mail["attachments"] = [ob.get() for ob in self.attachments] + append_to = append_to or [] + append_to.insert(index, new_items) + return append_to - if self.template_id is not None: - mail["template_id"] = self.template_id + def _ensure_insert(self, new_items, insert_to): + """Ensure an item is inserted to a list or create a new empty list - if self.sections: - sections = {} - for key in self.sections: - sections.update(key.get()) - mail["sections"] = sections + :param new_items: the item(s) to insert + :type new_items: list(obj) + :param insert_to: the list on which to insert the items at index 0 + :type insert_to: list() + """ + insert_to = insert_to or [] + insert_to.insert(0, new_items) + return insert_to - if self.headers: - headers = {} - for key in self.headers: - headers.update(key.get()) - mail["headers"] = headers + def _flatten_dicts(self, dicts): + """Flatten a dict - if self.categories: - mail["categories"] = [category.get() for category in - self.categories] + :param dicts: Flatten a dict + :type dicts: list(dict) + """ + d = dict() + list_of_dicts = [d.get() for d in dicts or []] + return {k: v for d in list_of_dicts for k, v in d.items()} - if self.custom_args: - custom_args = {} - for key in self.custom_args: - custom_args.update(key.get()) - mail["custom_args"] = custom_args + def _get_or_none(self, from_obj): + """Get the JSON representation of the object, else return None - if self.send_at is not None: - mail["send_at"] = self.send_at + :param from_obj: Get the JSON representation of the object, + else return None + :type from_obj: obj + """ + return from_obj.get() if from_obj is not None else None + + def _set_emails( + self, emails, global_substitutions=None, is_multiple=False, p=0): + """Adds emails to the Personalization object + + :param emails: An Email or list of Email objects + :type emails: Email, list(Email) + :param global_substitutions: A dict of substitutions for all recipients + :type global_substitutions: dict + :param is_multiple: Create a new personalization for each recipient + :type is_multiple: bool + :param p: p is the Personalization object or Personalization object + index + :type p: Personalization, integer, optional + """ + # Send multiple emails to multiple recipients + if is_multiple is True: + if isinstance(emails, list): + for email in emails: + personalization = Personalization() + personalization.add_email(email) + self.add_personalization(personalization) + else: + personalization = Personalization() + personalization.add_email(emails) + self.add_personalization(personalization) + if global_substitutions is not None: + if isinstance(global_substitutions, list): + for substitution in global_substitutions: + for p in self.personalizations: + p.add_substitution(substitution) + else: + for p in self.personalizations: + p.add_substitution(global_substitutions) + else: + try: + personalization = self._personalizations[p] + has_internal_personalization = True + except IndexError: + personalization = Personalization() + has_internal_personalization = False + + if isinstance(emails, list): + for email in emails: + personalization.add_email(email) + else: + personalization.add_email(emails) + + if global_substitutions is not None: + if isinstance(global_substitutions, list): + for substitution in global_substitutions: + personalization.add_substitution(substitution) + else: + personalization.add_substitution(global_substitutions) + + if not has_internal_personalization: + self.add_personalization(personalization, index=p) - if self.batch_id is not None: - mail["batch_id"] = self.batch_id + @property + def personalizations(self): + """A list of one or more Personalization objects - if self.asm is not None: - mail["asm"] = self.asm.get() + :rtype: list(Personalization) + """ + return self._personalizations - if self.ip_pool_name is not None: - mail["ip_pool_name"] = self.ip_pool_name + def add_personalization(self, personalization, index=0): + """Add a Personalization object - if self.mail_settings is not None: - mail["mail_settings"] = self.mail_settings.get() + :param personalization: Add a Personalization object + :type personalization: Personalization + :param index: The index where to add the Personalization + :type index: int + """ + self._personalizations = self._ensure_append( + personalization, self._personalizations, index) - if self.tracking_settings is not None: - mail["tracking_settings"] = self.tracking_settings.get() + @property + def to(self): + pass + + @to.setter + def to(self, to_emails, global_substitutions=None, is_multiple=False, p=0): + """Adds To objects to the Personalization object + + :param to_emails: The email addresses of all recipients + :type to_emails: To, str, tuple, list(str), list(tuple), list(To) + :param global_substitutions: A dict of substitutions for all recipients + :type global_substitutions: dict + :param is_multiple: Create a new personalization for each recipient + :type is_multiple: bool + :param p: p is the Personalization object or Personalization object + index + :type p: Personalization, integer, optional + """ + if isinstance(to_emails, list): + for email in to_emails: + if isinstance(email, str): + email = To(email, None) + if isinstance(email, tuple): + email = To(email[0], email[1]) + self.add_to(email, global_substitutions, is_multiple, p) + else: + if isinstance(to_emails, str): + to_emails = To(to_emails, None) + if isinstance(to_emails, tuple): + to_emails = To(to_emails[0], to_emails[1]) + self.add_to(to_emails, global_substitutions, is_multiple, p) + + def add_to( + self, to_email, global_substitutions=None, is_multiple=False, p=0): + """Adds a To object to the Personalization object + + :param to_email: A To object + :type to_email: To, str, tuple, list(str), list(tuple), list(To) + :param global_substitutions: A dict of substitutions for all recipients + :type global_substitutions: dict + :param is_multiple: Create a new personalization for each recipient + :type is_multiple: bool + :param p: p is the Personalization object or Personalization object + index + :type p: Personalization, integer, optional + """ - if self.reply_to is not None: - mail["reply_to"] = self.reply_to.get() - return mail + if isinstance(to_email, list): + for email in to_email: + if isinstance(email, str): + email = To(email, None) + elif isinstance(email, tuple): + email = To(email[0], email[1]) + elif not isinstance(email, Email): + raise ValueError( + 'Please use a To/Cc/Bcc, tuple, or a str for a to_email list.' + ) + self._set_emails(email, global_substitutions, is_multiple, p) + else: + if isinstance(to_email, str): + to_email = To(to_email, None) + if isinstance(to_email, tuple): + to_email = To(to_email[0], to_email[1]) + if isinstance(to_email, Email): + p = to_email.personalization + self._set_emails(to_email, global_substitutions, is_multiple, p) @property - def from_email(self): - """The email from which this Mail will be sent. - - :rtype: string + def cc(self): + pass + + @cc.setter + def cc(self, cc_emails, global_substitutions=None, is_multiple=False, p=0): + """Adds Cc objects to the Personalization object + + :param cc_emails: An Cc or list of Cc objects + :type cc_emails: Cc, list(Cc), tuple + :param global_substitutions: A dict of substitutions for all recipients + :type global_substitutions: dict + :param is_multiple: Create a new personalization for each recipient + :type is_multiple: bool + :param p: p is the Personalization object or Personalization object + index + :type p: Personalization, integer, optional """ - return self._from_email + if isinstance(cc_emails, list): + for email in cc_emails: + if isinstance(email, str): + email = Cc(email, None) + if isinstance(email, tuple): + email = Cc(email[0], email[1]) + self.add_cc(email, global_substitutions, is_multiple, p) + else: + if isinstance(cc_emails, str): + cc_emails = Cc(cc_emails, None) + if isinstance(cc_emails, tuple): + cc_emails = To(cc_emails[0], cc_emails[1]) + self.add_cc(cc_emails, global_substitutions, is_multiple, p) + + def add_cc( + self, cc_email, global_substitutions=None, is_multiple=False, p=0): + """Adds a Cc object to the Personalization object + + :param to_emails: An Cc object + :type to_emails: Cc + :param global_substitutions: A dict of substitutions for all recipients + :type global_substitutions: dict + :param is_multiple: Create a new personalization for each recipient + :type is_multiple: bool + :param p: p is the Personalization object or Personalization object + index + :type p: Personalization, integer, optional + """ + if isinstance(cc_email, str): + cc_email = Cc(cc_email, None) + if isinstance(cc_email, tuple): + cc_email = Cc(cc_email[0], cc_email[1]) + if isinstance(cc_email, Email): + p = cc_email.personalization + self._set_emails( + cc_email, global_substitutions, is_multiple=is_multiple, p=p) - @from_email.setter - def from_email(self, value): - self._from_email = value + @property + def bcc(self): + pass + + @bcc.setter + def bcc( + self, + bcc_emails, + global_substitutions=None, + is_multiple=False, + p=0): + """Adds Bcc objects to the Personalization object + + :param bcc_emails: An Bcc or list of Bcc objects + :type bcc_emails: Bcc, list(Bcc), tuple + :param global_substitutions: A dict of substitutions for all recipients + :type global_substitutions: dict + :param is_multiple: Create a new personalization for each recipient + :type is_multiple: bool + :param p: p is the Personalization object or Personalization object + index + :type p: Personalization, integer, optional + """ + if isinstance(bcc_emails, list): + for email in bcc_emails: + if isinstance(email, str): + email = Bcc(email, None) + if isinstance(email, tuple): + email = Bcc(email[0], email[1]) + self.add_bcc(email, global_substitutions, is_multiple, p) + else: + if isinstance(bcc_emails, str): + bcc_emails = Bcc(bcc_emails, None) + if isinstance(bcc_emails, tuple): + bcc_emails = Bcc(bcc_emails[0], bcc_emails[1]) + self.add_bcc(bcc_emails, global_substitutions, is_multiple, p) + + def add_bcc( + self, + bcc_email, + global_substitutions=None, + is_multiple=False, + p=0): + """Adds a Bcc object to the Personalization object + + :param to_emails: An Bcc object + :type to_emails: Bcc + :param global_substitutions: A dict of substitutions for all recipients + :type global_substitutions: dict + :param is_multiple: Create a new personalization for each recipient + :type is_multiple: bool + :param p: p is the Personalization object or Personalization object + index + :type p: Personalization, integer, optional + """ + if isinstance(bcc_email, str): + bcc_email = Bcc(bcc_email, None) + if isinstance(bcc_email, tuple): + bcc_email = Bcc(bcc_email[0], bcc_email[1]) + if isinstance(bcc_email, Email): + p = bcc_email.personalization + self._set_emails( + bcc_email, + global_substitutions, + is_multiple=is_multiple, + p=p) @property def subject(self): - """The global, or "message level", subject of this Mail. + """The global Subject object - This may be overridden by personalizations[x].subject. - :rtype: string + :rtype: Subject """ return self._subject @subject.setter def subject(self, value): - self._subject = value + """The subject of the email(s) + + :param value: The subject of the email(s) + :type value: Subject, string + """ + if isinstance(value, Subject): + if value.personalization is not None: + try: + personalization = \ + self._personalizations[value.personalization] + has_internal_personalization = True + except IndexError: + personalization = Personalization() + has_internal_personalization = False + personalization.subject = value.subject + + if not has_internal_personalization: + self.add_personalization( + personalization, + index=value.personalization) + else: + self._subject = value + else: + self._subject = Subject(value) @property - def template_id(self): - """The id of a template that you would like to use. + def headers(self): + """A list of global Header objects - If you use a template that contains a subject and content (either text - or html), you do not need to specify those at the personalizations nor - message level. + :rtype: list(Header) + """ + return self._headers - :rtype: int + @property + def header(self): + pass + + @header.setter + def header(self, headers): + """Add headers to the email + + :param value: A list of Header objects or a dict of header key/values + :type value: Header, list(Header), dict """ + if isinstance(headers, list): + for h in headers: + self.add_header(h) + else: + self.add_header(headers) - return self._template_id + def add_header(self, header): + """Add headers to the email globaly or to a specific Personalization - @template_id.setter - def template_id(self, value): - self._template_id = value + :param value: A Header object or a dict of header key/values + :type value: Header, dict + """ + if header.personalization is not None: + try: + personalization = \ + self._personalizations[header.personalization] + has_internal_personalization = True + except IndexError: + personalization = Personalization() + has_internal_personalization = False + if isinstance(header, dict): + (k, v) = list(header.items())[0] + personalization.add_header(Header(k, v)) + else: + personalization.add_header(header) + + if not has_internal_personalization: + self.add_personalization( + personalization, + index=header.personalization) + else: + if isinstance(header, dict): + (k, v) = list(header.items())[0] + self._headers = self._ensure_append( + Header(k, v), self._headers) + else: + self._headers = self._ensure_append(header, self._headers) @property - def send_at(self): - """A unix timestamp allowing you to specify when you want your email to - be delivered. This may be overridden by the personalizations[x].send_at - parameter. Scheduling more than 72 hours in advance is forbidden. + def substitution(self): + pass - :rtype: int + @substitution.setter + def substitution(self, substitution): + """Add substitutions to the email + + :param value: Add substitutions to the email + :type value: Substitution, list(Substitution) """ - return self._send_at + if isinstance(substitution, list): + for s in substitution: + self.add_substitution(s) + else: + self.add_substitution(substitution) - @send_at.setter - def send_at(self, value): - self._send_at = value + def add_substitution(self, substitution): + """Add a substitution to the email - @property - def batch_id(self): - """An ID for this batch of emails. + :param value: Add a substitution to the email + :type value: Substitution + """ + if substitution.personalization: + try: + personalization = \ + self._personalizations[substitution.personalization] + has_internal_personalization = True + except IndexError: + personalization = Personalization() + has_internal_personalization = False + personalization.add_substitution(substitution) + + if not has_internal_personalization: + self.add_personalization( + personalization, index=substitution.personalization) + else: + if isinstance(substitution, list): + for s in substitution: + for p in self.personalizations: + p.add_substitution(s) + else: + for p in self.personalizations: + p.add_substitution(substitution) - This represents a batch of emails sent at the same time. Including a - batch_id in your request allows you include this email in that batch, - and also enables you to cancel or pause the delivery of that batch. - For more information, see https://sendgrid.com/docs/API_Reference/Web_API_v3/cancel_schedule_send.html + @property + def custom_args(self): + """A list of global CustomArg objects - :rtype: int + :rtype: list(CustomArg) """ - return self._batch_id - - @batch_id.setter - def batch_id(self, value): - self._batch_id = value + return self._custom_args @property - def asm(self): - """The ASM for this Mail. + def custom_arg(self): + return self._custom_args + + @custom_arg.setter + def custom_arg(self, custom_arg): + """Add custom args to the email - :rtype: ASM + :param value: A list of CustomArg objects or a dict of custom arg + key/values + :type value: CustomArg, list(CustomArg), dict """ - return self._asm + if isinstance(custom_arg, list): + for c in custom_arg: + self.add_custom_arg(c) + else: + self.add_custom_arg(custom_arg) - @asm.setter - def asm(self, value): - self._asm = value + def add_custom_arg(self, custom_arg): + """Add custom args to the email globaly or to a specific Personalization + + :param value: A CustomArg object or a dict of custom arg key/values + :type value: CustomArg, dict + """ + if not isinstance(custom_arg, dict) and custom_arg.personalization is not None: + try: + personalization = \ + self._personalizations[custom_arg.personalization] + has_internal_personalization = True + except IndexError: + personalization = Personalization() + has_internal_personalization = False + if isinstance(custom_arg, dict): + (k, v) = list(custom_arg.items())[0] + personalization.add_custom_arg(CustomArg(k, v)) + else: + personalization.add_custom_arg(custom_arg) + + if not has_internal_personalization: + self.add_personalization( + personalization, index=custom_arg.personalization) + else: + if isinstance(custom_arg, dict): + (k, v) = list(custom_arg.items())[0] + self._custom_args = self._ensure_append( + CustomArg(k, v), self._custom_args) + else: + self._custom_args = self._ensure_append( + custom_arg, self._custom_args) @property - def mail_settings(self): - """The MailSettings for this Mail. + def send_at(self): + """The global SendAt object - :rtype: MailSettings + :rtype: SendAt """ - return self._mail_settings + return self._send_at - @mail_settings.setter - def mail_settings(self, value): - self._mail_settings = value + @send_at.setter + def send_at(self, value): + """A unix timestamp specifying when your email should + be delivered. + + :param value: A unix timestamp specifying when your email should + be delivered. + :type value: SendAt, int + """ + if isinstance(value, SendAt): + if value.personalization is not None: + try: + personalization = \ + self._personalizations[value.personalization] + has_internal_personalization = True + except IndexError: + personalization = Personalization() + has_internal_personalization = False + personalization.send_at = value.send_at + + if not has_internal_personalization: + self.add_personalization( + personalization, index=value.personalization) + else: + self._send_at = value + else: + self._send_at = SendAt(value) @property - def tracking_settings(self): - """The TrackingSettings for this Mail. + def dynamic_template_data(self): + pass - :rtype: TrackingSettings + @dynamic_template_data.setter + def dynamic_template_data(self, value): + """Data for a transactional template + + :param value: Data for a transactional template + :type value: DynamicTemplateData, a JSON-serializable structure """ - return self._tracking_settings + if not isinstance(value, DynamicTemplateData): + value = DynamicTemplateData(value) + try: + personalization = self._personalizations[value.personalization] + has_internal_personalization = True + except IndexError: + personalization = Personalization() + has_internal_personalization = False + personalization.dynamic_template_data = value.dynamic_template_data - @tracking_settings.setter - def tracking_settings(self, value): - self._tracking_settings = value + if not has_internal_personalization: + self.add_personalization( + personalization, index=value.personalization) @property - def ip_pool_name(self): - """The IP Pool that you would like to send this Mail email from. + def from_email(self): + """The email address of the sender - :rtype: string + :rtype: From """ - return self._ip_pool_name + return self._from_email - @ip_pool_name.setter - def ip_pool_name(self, value): - self._ip_pool_name = value + @from_email.setter + def from_email(self, value): + """The email address of the sender + + :param value: The email address of the sender + :type value: From, str, tuple + """ + if isinstance(value, str): + value = From(value, None) + if isinstance(value, tuple): + value = From(value[0], value[1]) + self._from_email = value @property def reply_to(self): - """The email address to use in the Reply-To header. + """The reply to email address - :rtype: Email + :rtype: ReplyTo """ return self._reply_to @reply_to.setter def reply_to(self, value): + """The reply to email address + + :param value: The reply to email address + :type value: ReplyTo, str, tuple + """ + if isinstance(value, str): + value = ReplyTo(value, None) + if isinstance(value, tuple): + value = ReplyTo(value[0], value[1]) self._reply_to = value @property - def personalizations(self): - """The Personalizations applied to this Mail. - - Each object within personalizations can be thought of as an envelope - - it defines who should receive an individual message and how that - message should be handled. A maximum of 1000 personalizations can be - included. + def reply_to_list(self): + """A list of ReplyTo email addresses - :rtype: list + :rtype: list(ReplyTo), tuple """ - return self._personalizations + return self._reply_to_list - def add_personalization(self, personalizations): - """Add a new Personalization to this Mail. + @reply_to_list.setter + def reply_to_list(self, value): + """A list of ReplyTo email addresses - :type personalizations: Personalization + :param value: A list of ReplyTo email addresses + :type value: list(ReplyTo), tuple """ - self._personalizations.append(personalizations) + if isinstance(value, list): + for reply in value: + if isinstance(reply, ReplyTo): + if not isinstance(reply.email, str): + raise ValueError('You must provide an email for each entry in a reply_to_list') + else: + raise ValueError( + 'Please use a list of ReplyTos for a reply_to_list.' + ) + self._reply_to_list = value @property def contents(self): - """The Contents of this Mail. Must include at least one MIME type. + """The contents of the email :rtype: list(Content) """ return self._contents - def add_content(self, content): - """Add a new Content to this Mail. Usually the plaintext or HTML - message contents. + @property + def content(self): + pass + + @content.setter + def content(self, contents): + """The content(s) of the email + + :param contents: The content(s) of the email + :type contents: Content, list(Content) + """ + if isinstance(contents, list): + for c in contents: + self.add_content(c) + else: + self.add_content(contents) - :type content: Content + def add_content(self, content, mime_type=None): + """Add content to the email + + :param contents: Content to be added to the email + :type contents: Content + :param mime_type: Override the mime type + :type mime_type: MimeType, str """ - self._contents.append(content) + if isinstance(content, str): + content = Content(mime_type, content) + # Content of mime type text/plain must always come first, followed by text/x-amp-html and then text/html + if content.mime_type == MimeType.text: + self._contents = self._ensure_insert(content, self._contents) + elif content.mime_type == MimeType.amp: + if self._contents: + for _content in self._contents: + # this is written in the context that plain text content will always come earlier than the html content + if _content.mime_type == MimeType.text: + index = 1 + break + elif _content.mime_type == MimeType.html: + index = 0 + break + else: + index = 0 + self._contents = self._ensure_append( + content, self._contents, index=index) + else: + if self._contents: + index = len(self._contents) + else: + index = 0 + self._contents = self._ensure_append( + content, self._contents, index=index) @property def attachments(self): - """The attachments included with this Mail. + """The attachments to this email - :returns: List of Attachment objects. :rtype: list(Attachment) """ return self._attachments + @property + def attachment(self): + pass + + @attachment.setter + def attachment(self, attachment): + """Add attachment(s) to this email + + :param attachment: Add attachment(s) to this email + :type attachment: Attachment, list(Attachment) + """ + if isinstance(attachment, list): + for a in attachment: + self.add_attachment(a) + else: + self.add_attachment(attachment) + def add_attachment(self, attachment): - """Add an Attachment to this Mail. + """Add an attachment to this email + :param attachment: Add an attachment to this email :type attachment: Attachment """ - self._attachments.append(attachment) + self._attachments = self._ensure_append(attachment, self._attachments) @property - def sections(self): - """The sections included with this Mail. + def template_id(self): + """The transactional template id for this email - :returns: List of Section objects. - :rtype: list(Section) + :rtype: TemplateId """ - return self._sections + return self._template_id - def add_section(self, section): - """Add a Section to this Mail. + @template_id.setter + def template_id(self, value): + """The transactional template id for this email - :type attachment: Section + :param value: The transactional template id for this email + :type value: TemplateId """ - self._sections.append(section) + if isinstance(value, TemplateId): + self._template_id = value + else: + self._template_id = TemplateId(value) @property - def headers(self): - """The Headers included with this Mail. + def sections(self): + """The block sections of code to be used as substitutions - :returns: List of Header objects. - :rtype: list(Header) + :rtype: Section """ - return self._headers + return self._sections - def add_header(self, header): - """Add a Header to this Mail. + @property + def section(self): + pass - The header provided can be a Header or a dictionary with a single - key-value pair. - :type header: object + @section.setter + def section(self, section): + """The block sections of code to be used as substitutions + + :rtype: Section, list(Section) """ - if isinstance(header, dict): - (k, v) = list(header.items())[0] - self._headers.append(Header(k, v)) + if isinstance(section, list): + for h in section: + self.add_section(h) else: - self._headers.append(header) + self.add_section(section) + + def add_section(self, section): + """A block section of code to be used as substitutions + + :param section: A block section of code to be used as substitutions + :type section: Section + """ + self._sections = self._ensure_append(section, self._sections) @property def categories(self): - """The Categories applied to this Mail. Must not exceed 10 items + """The categories assigned to this message :rtype: list(Category) """ return self._categories + @property + def category(self): + pass + + @category.setter + def category(self, categories): + """Add categories assigned to this message + + :rtype: list(Category) + """ + if isinstance(categories, list): + for c in categories: + self.add_category(c) + else: + self.add_category(categories) + def add_category(self, category): - """Add a Category to this Mail. Must be less than 255 characters. + """Add a category assigned to this message - :type category: string + :rtype: Category """ - self._categories.append(category) + self._categories = self._ensure_append(category, self._categories) @property - def custom_args(self): - """The CustomArgs attached to this Mail. + def batch_id(self): + """The batch id for this email - Must not exceed 10,000 characters. - :rtype: list(CustomArg) + :rtype: BatchId """ - return self._custom_args + return self._batch_id - def add_custom_arg(self, custom_arg): - """Add a CustomArg to this Mail. + @batch_id.setter + def batch_id(self, value): + """The batch id for this email + + :param value: The batch id for this email + :type value: BatchId + """ + self._batch_id = value + + @property + def asm(self): + """An object specifying unsubscribe behavior. + + :rtype: Asm + """ + return self._asm + + @asm.setter + def asm(self, value): + """An object specifying unsubscribe behavior. + + :param value: An object specifying unsubscribe behavior. + :type value: Asm + """ + self._asm = value + + @property + def ip_pool_name(self): + """The IP Pool that you would like to send this email from + + :rtype: IpPoolName + """ + return self._ip_pool_name + + @ip_pool_name.setter + def ip_pool_name(self, value): + """The IP Pool that you would like to send this email from + + :paran value: The IP Pool that you would like to send this email from + :type value: IpPoolName + """ + self._ip_pool_name = value + + @property + def mail_settings(self): + """The mail settings for this email + + :rtype: MailSettings + """ + return self._mail_settings + + @mail_settings.setter + def mail_settings(self, value): + """The mail settings for this email + + :param value: The mail settings for this email + :type value: MailSettings + """ + self._mail_settings = value - :type custom_arg: CustomArg + @property + def tracking_settings(self): + """The tracking settings for this email + + :rtype: TrackingSettings """ - self._custom_args.append(custom_arg) + return self._tracking_settings + + @tracking_settings.setter + def tracking_settings(self, value): + """The tracking settings for this email + + :param value: The tracking settings for this email + :type value: TrackingSettings + """ + self._tracking_settings = value + + def get(self): + """ + Get a JSON-ready representation of this Mail object. + + :returns: This Mail object, ready for use in a request body. + :rtype: dict + """ + mail = { + 'from': self._get_or_none(self.from_email), + 'subject': self._get_or_none(self.subject), + 'personalizations': [p.get() for p in self.personalizations or []], + 'content': [c.get() for c in self.contents or []], + 'attachments': [a.get() for a in self.attachments or []], + 'template_id': self._get_or_none(self.template_id), + 'sections': self._flatten_dicts(self.sections), + 'headers': self._flatten_dicts(self.headers), + 'categories': [c.get() for c in self.categories or []], + 'custom_args': self._flatten_dicts(self.custom_args), + 'send_at': self._get_or_none(self.send_at), + 'batch_id': self._get_or_none(self.batch_id), + 'asm': self._get_or_none(self.asm), + 'ip_pool_name': self._get_or_none(self.ip_pool_name), + 'mail_settings': self._get_or_none(self.mail_settings), + 'tracking_settings': self._get_or_none(self.tracking_settings), + 'reply_to': self._get_or_none(self.reply_to), + 'reply_to_list': [r.get() for r in self.reply_to_list or []], + } + + return {key: value for key, value in mail.items() + if value is not None and value != [] and value != {}} + + @classmethod + def from_EmailMessage(cls, message): + """Create a Mail object from an instance of + email.message.EmailMessage. + + :type message: email.message.EmailMessage + :rtype: Mail + """ + mail = cls( + from_email=Email(message.get('From')), + subject=message.get('Subject'), + to_emails=Email(message.get('To')), + ) + try: + body = message.get_content() + except AttributeError: + # Python2 + body = message.get_payload() + mail.add_content(Content( + message.get_content_type(), + body.strip() + )) + for k, v in message.items(): + mail.add_header(Header(k, v)) + return mail diff --git a/sendgrid/helpers/mail/mail_settings.py b/sendgrid/helpers/mail/mail_settings.py index 14d1e1a92..78499ac30 100644 --- a/sendgrid/helpers/mail/mail_settings.py +++ b/sendgrid/helpers/mail/mail_settings.py @@ -1,14 +1,73 @@ class MailSettings(object): """A collection of mail settings that specify how to handle this email.""" - def __init__(self): - """Create an empty MailSettings.""" + def __init__(self, + bcc_settings=None, + bypass_bounce_management=None, + bypass_list_management=None, + bypass_spam_management=None, + bypass_unsubscribe_management=None, + footer_settings=None, + sandbox_mode=None, + spam_check=None): + """Create a MailSettings object + + :param bcc_settings: The BCC Settings of this MailSettings + :type bcc_settings: BCCSettings, optional + :param bypass_bounce_management: Whether this MailSettings bypasses bounce management. + Should not be combined with bypass_list_management. + :type bypass_list_management: BypassBounceManagement, optional + :param bypass_list_management: Whether this MailSettings bypasses list + management + :type bypass_list_management: BypassListManagement, optional + :param bypass_spam_management: Whether this MailSettings bypasses spam management. + Should not be combined with bypass_list_management. + :type bypass_list_management: BypassSpamManagement, optional + :param bypass_unsubscribe_management: Whether this MailSettings bypasses unsubscribe management. + Should not be combined with bypass_list_management. + :type bypass_list_management: BypassUnsubscribeManagement, optional + :param footer_settings: The default footer specified by this + MailSettings + :type footer_settings: FooterSettings, optional + :param sandbox_mode: Whether this MailSettings enables sandbox mode + :type sandbox_mode: SandBoxMode, optional + :param spam_check: How this MailSettings requests email to be checked + for spam + :type spam_check: SpamCheck, optional + """ self._bcc_settings = None + self._bypass_bounce_management = None self._bypass_list_management = None + self._bypass_spam_management = None + self._bypass_unsubscribe_management = None self._footer_settings = None self._sandbox_mode = None self._spam_check = None + if bcc_settings is not None: + self.bcc_settings = bcc_settings + + if bypass_bounce_management is not None: + self.bypass_bounce_management = bypass_bounce_management + + if bypass_list_management is not None: + self.bypass_list_management = bypass_list_management + + if bypass_spam_management is not None: + self.bypass_spam_management = bypass_spam_management + + if bypass_unsubscribe_management is not None: + self.bypass_unsubscribe_management = bypass_unsubscribe_management + + if footer_settings is not None: + self.footer_settings = footer_settings + + if sandbox_mode is not None: + self.sandbox_mode = sandbox_mode + + if spam_check is not None: + self.spam_check = spam_check + @property def bcc_settings(self): """The BCC Settings of this MailSettings. @@ -19,8 +78,30 @@ def bcc_settings(self): @bcc_settings.setter def bcc_settings(self, value): + """The BCC Settings of this MailSettings. + + :param value: The BCC Settings of this MailSettings. + :type value: BCCSettings + """ self._bcc_settings = value + @property + def bypass_bounce_management(self): + """Whether this MailSettings bypasses bounce management. + + :rtype: BypassBounceManagement + """ + return self._bypass_bounce_management + + @bypass_bounce_management.setter + def bypass_bounce_management(self, value): + """Whether this MailSettings bypasses bounce management. + + :param value: Whether this MailSettings bypasses bounce management. + :type value: BypassBounceManagement + """ + self._bypass_bounce_management = value + @property def bypass_list_management(self): """Whether this MailSettings bypasses list management. @@ -31,8 +112,47 @@ def bypass_list_management(self): @bypass_list_management.setter def bypass_list_management(self, value): + """Whether this MailSettings bypasses list management. + + :param value: Whether this MailSettings bypasses list management. + :type value: BypassListManagement + """ self._bypass_list_management = value + @property + def bypass_spam_management(self): + """Whether this MailSettings bypasses spam management. + + :rtype: BypassSpamManagement + """ + return self._bypass_spam_management + + @bypass_spam_management.setter + def bypass_spam_management(self, value): + """Whether this MailSettings bypasses spam management. + + :param value: Whether this MailSettings bypasses spam management. + :type value: BypassSpamManagement + """ + self._bypass_spam_management = value + + @property + def bypass_unsubscribe_management(self): + """Whether this MailSettings bypasses unsubscribe management. + + :rtype: BypassUnsubscribeManagement + """ + return self._bypass_unsubscribe_management + + @bypass_unsubscribe_management.setter + def bypass_unsubscribe_management(self, value): + """Whether this MailSettings bypasses unsubscribe management. + + :param value: Whether this MailSettings bypasses unsubscribe management. + :type value: BypassUnsubscribeManagement + """ + self._bypass_unsubscribe_management = value + @property def footer_settings(self): """The default footer specified by this MailSettings. @@ -43,6 +163,11 @@ def footer_settings(self): @footer_settings.setter def footer_settings(self, value): + """The default footer specified by this MailSettings. + + :param value: The default footer specified by this MailSettings. + :type value: FooterSettings + """ self._footer_settings = value @property @@ -55,6 +180,11 @@ def sandbox_mode(self): @sandbox_mode.setter def sandbox_mode(self, value): + """Whether this MailSettings enables sandbox mode. + + :param value: Whether this MailSettings enables sandbox mode. + :type value: SandBoxMode + """ self._sandbox_mode = value @property @@ -67,6 +197,12 @@ def spam_check(self): @spam_check.setter def spam_check(self, value): + """How this MailSettings requests email to be checked for spam. + + :param value: How this MailSettings requests email to be checked + for spam. + :type value: SpamCheck + """ self._spam_check = value def get(self): @@ -80,10 +216,22 @@ def get(self): if self.bcc_settings is not None: mail_settings["bcc"] = self.bcc_settings.get() + if self.bypass_bounce_management is not None: + mail_settings[ + "bypass_bounce_management"] = self.bypass_bounce_management.get() + if self.bypass_list_management is not None: mail_settings[ "bypass_list_management"] = self.bypass_list_management.get() + if self.bypass_spam_management is not None: + mail_settings[ + "bypass_spam_management"] = self.bypass_spam_management.get() + + if self.bypass_unsubscribe_management is not None: + mail_settings[ + "bypass_unsubscribe_management"] = self.bypass_unsubscribe_management.get() + if self.footer_settings is not None: mail_settings["footer"] = self.footer_settings.get() diff --git a/sendgrid/helpers/mail/mime_type.py b/sendgrid/helpers/mail/mime_type.py new file mode 100644 index 000000000..a2f88c5af --- /dev/null +++ b/sendgrid/helpers/mail/mime_type.py @@ -0,0 +1,6 @@ +class MimeType(object): + """The MIME type of the content included in your email. + """ + text = "text/plain" + html = "text/html" + amp = "text/x-amp-html" diff --git a/sendgrid/helpers/mail/open_tracking.py b/sendgrid/helpers/mail/open_tracking.py index 633f2d9ed..7124a2e66 100644 --- a/sendgrid/helpers/mail/open_tracking.py +++ b/sendgrid/helpers/mail/open_tracking.py @@ -11,10 +11,16 @@ def __init__(self, enable=None, substitution_tag=None): :param enable: If open tracking is enabled. :type enable: boolean, optional :param substitution_tag: Tag in body to be replaced by tracking pixel. - :type substitution_tag: string, optional + :type substitution_tag: OpenTrackingSubstitionTag, optional """ - self.enable = enable - self.substitution_tag = substitution_tag + self._enable = None + self._substitution_tag = None + + if enable is not None: + self.enable = enable + + if substitution_tag is not None: + self.substitution_tag = substitution_tag @property def enable(self): @@ -26,6 +32,11 @@ def enable(self): @enable.setter def enable(self, value): + """Indicates if this setting is enabled. + + :param value: Indicates if this setting is enabled. + :type value: boolean + """ self._enable = value @property @@ -40,6 +51,17 @@ def substitution_tag(self): @substitution_tag.setter def substitution_tag(self, value): + """Allows you to specify a substitution tag that you can insert in the + body of your email at a location that you desire. This tag will be + replaced by the open tracking pixel. + + :param value: Allows you to specify a substitution tag that you can + insert in the body of your email at a location that you + desire. This tag will be replaced by the open tracking + pixel. + + :type value: string + """ self._substitution_tag = value def get(self): @@ -54,5 +76,5 @@ def get(self): open_tracking["enable"] = self.enable if self.substitution_tag is not None: - open_tracking["substitution_tag"] = self.substitution_tag + open_tracking["substitution_tag"] = self.substitution_tag.get() return open_tracking diff --git a/sendgrid/helpers/mail/open_tracking_substitution_tag.py b/sendgrid/helpers/mail/open_tracking_substitution_tag.py new file mode 100644 index 000000000..d9967f9cd --- /dev/null +++ b/sendgrid/helpers/mail/open_tracking_substitution_tag.py @@ -0,0 +1,49 @@ +class OpenTrackingSubstitutionTag(object): + """The open tracking substitution tag of an SubscriptionTracking object.""" + + def __init__(self, open_tracking_substitution_tag=None): + """Create a OpenTrackingSubstitutionTag object + + :param open_tracking_substitution_tag: Allows you to specify a + substitution tag that you can insert in the body of your + email at a location that you desire. This tag will be replaced + by the open tracking pixel. + """ + self._open_tracking_substitution_tag = None + + if open_tracking_substitution_tag is not None: + self.open_tracking_substitution_tag = \ + open_tracking_substitution_tag + + @property + def open_tracking_substitution_tag(self): + """Allows you to specify a substitution tag that you can insert in + the body of your email at a location that you desire. This tag + will be replaced by the open tracking pixel. + + :rtype: string + """ + return self._open_tracking_substitution_tag + + @open_tracking_substitution_tag.setter + def open_tracking_substitution_tag(self, value): + """Allows you to specify a substitution tag that you can insert in + the body of your email at a location that you desire. This tag will + be replaced by the open tracking pixel. + + :param value: Allows you to specify a substitution tag that you can + insert in the body of your email at a location that you desire. This + tag will be replaced by the open tracking pixel. + :type value: string + """ + self._open_tracking_substitution_tag = value + + def get(self): + """ + Get a JSON-ready representation of this OpenTrackingSubstitutionTag. + + :returns: This OpenTrackingSubstitutionTag, ready for use in a request + body. + :rtype: string + """ + return self.open_tracking_substitution_tag diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py index e49432cf5..a4e1c1de4 100644 --- a/sendgrid/helpers/mail/personalization.py +++ b/sendgrid/helpers/mail/personalization.py @@ -4,8 +4,9 @@ class Personalization(object): """ def __init__(self): - """Create an empty Personalization.""" + """Create an empty Personalization and initialize member variables.""" self._tos = [] + self._from_email = None self._ccs = [] self._bccs = [] self._subject = None @@ -13,6 +14,37 @@ def __init__(self): self._substitutions = [] self._custom_args = [] self._send_at = None + self._dynamic_template_data = None + + def add_email(self, email): + email_type = type(email) + if email_type.__name__ == 'To': + self.add_to(email) + return + if email_type.__name__ == 'Cc': + self.add_cc(email) + return + if email_type.__name__ == 'Bcc': + self.add_bcc(email) + return + if email_type.__name__ == 'From': + self.from_email = email + return + raise ValueError('Please use a To, From, Cc or Bcc object.') + + def _get_unique_recipients(self, recipients): + unique_recipients = [] + + for recipient in recipients: + recipient_email = recipient['email'].lower() if isinstance(recipient, dict) else recipient.email.lower() + if all( + unique_recipient['email'].lower() != recipient_email for unique_recipient in unique_recipients + ): + new_unique_recipient = recipient if isinstance(recipient, dict) else recipient.get() + unique_recipients.append(new_unique_recipient) + + return unique_recipients + @property def tos(self): @@ -20,7 +52,7 @@ def tos(self): :rtype: list(dict) """ - return self._tos + return self._get_unique_recipients(self._tos) @tos.setter def tos(self, value): @@ -31,15 +63,42 @@ def add_to(self, email): :type email: Email """ + if email.substitutions: + if isinstance(email.substitutions, list): + for substitution in email.substitutions: + self.add_substitution(substitution) + else: + self.add_substitution(email.substitutions) + + if email.dynamic_template_data: + self.dynamic_template_data = email.dynamic_template_data + + if email.subject: + if isinstance(email.subject, str): + self.subject = email.subject + else: + self.subject = email.subject.get() + self._tos.append(email.get()) + @property + def from_email(self): + return self._from_email + + @from_email.setter + def from_email(self, value): + self._from_email = value + + def set_from(self, email): + self._from_email = email.get() + @property def ccs(self): """A list of recipients who will receive copies of this email. :rtype: list(dict) """ - return self._ccs + return self._get_unique_recipients(self._ccs) @ccs.setter def ccs(self, value): @@ -59,7 +118,7 @@ def bccs(self): :rtype: list(dict) """ - return self._bccs + return self._get_unique_recipients(self._bccs) @bccs.setter def bccs(self, value): @@ -79,6 +138,7 @@ def subject(self): Char length requirements, according to the RFC: https://stackoverflow.com/a/1592310 + :rtype: string """ return self._subject @@ -116,14 +176,17 @@ def substitutions(self): @substitutions.setter def substitutions(self, value): - self.substitutions = value + self._substitutions = value def add_substitution(self, substitution): """Add a new Substitution to this Personalization. :type substitution: Substitution """ - self._substitutions.append(substitution.get()) + if not isinstance(substitution, dict): + substitution = substitution.get() + + self._substitutions.append(substitution) @property def custom_args(self): @@ -158,6 +221,22 @@ def send_at(self): def send_at(self, value): self._send_at = value + @property + def dynamic_template_data(self): + """Data for dynamic transactional template. + Should be JSON-serializable structure. + + :rtype: JSON-serializable structure + """ + return self._dynamic_template_data + + @dynamic_template_data.setter + def dynamic_template_data(self, value): + if not isinstance(value, dict): + value = value.get() + + self._dynamic_template_data = value + def get(self): """ Get a JSON-ready representation of this Personalization. @@ -166,36 +245,27 @@ def get(self): :rtype: dict """ personalization = {} - if self.tos: - personalization["to"] = self.tos - - if self.ccs: - personalization["cc"] = self.ccs - - if self.bccs: - personalization["bcc"] = self.bccs - - if self.subject is not None: - personalization["subject"] = self.subject - - if self.headers: - headers = {} - for key in self.headers: - headers.update(key) - personalization["headers"] = headers - - if self.substitutions: - substitutions = {} - for key in self.substitutions: - substitutions.update(key) - personalization["substitutions"] = substitutions - - if self.custom_args: - custom_args = {} - for key in self.custom_args: - custom_args.update(key) - personalization["custom_args"] = custom_args - - if self.send_at is not None: - personalization["send_at"] = self.send_at + + for key in ['tos', 'ccs', 'bccs']: + value = getattr(self, key) + if value: + personalization[key[:-1]] = value + + from_value = getattr(self, 'from_email') + if from_value: + personalization['from'] = from_value + + for key in ['subject', 'send_at', 'dynamic_template_data']: + value = getattr(self, key) + if value: + personalization[key] = value + + for prop_name in ['headers', 'substitutions', 'custom_args']: + prop = getattr(self, prop_name) + if prop: + obj = {} + for key in prop: + obj.update(key) + personalization[prop_name] = obj + return personalization diff --git a/sendgrid/helpers/mail/plain_text_content.py b/sendgrid/helpers/mail/plain_text_content.py new file mode 100644 index 000000000..78bce1a85 --- /dev/null +++ b/sendgrid/helpers/mail/plain_text_content.py @@ -0,0 +1,60 @@ +from .content import Content +from .validators import ValidateApiKey + + +class PlainTextContent(Content): + """Plain text content to be included in your email. + """ + + def __init__(self, content): + """Create a PlainTextContent with the specified MIME type and content. + + :param content: The actual text content. + :type content: string + """ + self._content = None + self._validator = ValidateApiKey() + + if content is not None: + self.content = content + + @property + def mime_type(self): + """The MIME type. + + :rtype: string + """ + return "text/plain" + + @property + def content(self): + """The actual text content. + + :rtype: string + """ + return self._content + + @content.setter + def content(self, value): + """The actual text content. + + :param value: The actual text content. + :type value: string + """ + self._validator.validate_message_dict(value) + self._content = value + + def get(self): + """ + Get a JSON-ready representation of this PlainTextContent. + + :returns: This PlainTextContent, ready for use in a request body. + :rtype: dict + """ + content = {} + if self.mime_type is not None: + content["type"] = self.mime_type + + if self.content is not None: + content["value"] = self.content + return content diff --git a/sendgrid/helpers/mail/reply_to.py b/sendgrid/helpers/mail/reply_to.py new file mode 100644 index 000000000..731e2ad8f --- /dev/null +++ b/sendgrid/helpers/mail/reply_to.py @@ -0,0 +1,5 @@ +from .email import Email + + +class ReplyTo(Email): + """A reply to email address with an optional name.""" diff --git a/sendgrid/helpers/mail/sandbox_mode.py b/sendgrid/helpers/mail/sandbox_mode.py index ce9d66935..e8990ee93 100644 --- a/sendgrid/helpers/mail/sandbox_mode.py +++ b/sendgrid/helpers/mail/sandbox_mode.py @@ -1,6 +1,5 @@ class SandBoxMode(object): """Setting for sandbox mode. - This allows you to send a test email to ensure that your request body is valid and formatted correctly. """ @@ -10,7 +9,10 @@ def __init__(self, enable=None): :param enable: Whether this is a test request. :type enable: boolean, optional """ - self.enable = enable + self._enable = None + + if enable is not None: + self.enable = enable @property def enable(self): @@ -22,6 +24,11 @@ def enable(self): @enable.setter def enable(self, value): + """Indicates if this setting is enabled. + + :param value: Indicates if this setting is enabled. + :type value: boolean + """ self._enable = value def get(self): diff --git a/sendgrid/helpers/mail/section.py b/sendgrid/helpers/mail/section.py index ac20e85a6..cea949b14 100644 --- a/sendgrid/helpers/mail/section.py +++ b/sendgrid/helpers/mail/section.py @@ -2,24 +2,53 @@ class Section(object): """A block section of code to be used as a substitution.""" def __init__(self, key=None, value=None): - """Create a section with the given key and value.""" - self.key = key - self.value = value + """Create a section with the given key and value. + + :param key: section of code key + :type key: string + :param value: section of code value + :type value: string + """ + self._key = None + self._value = None + + if key is not None: + self.key = key + if value is not None: + self.value = value @property def key(self): + """A section of code's key. + + :rtype key: string + """ return self._key @key.setter def key(self, value): + """A section of code's key. + + :param key: section of code key + :type key: string + """ self._key = value @property def value(self): + """A section of code's value. + + :rtype: string + """ return self._value @value.setter def value(self, value): + """A section of code's value. + + :param value: A section of code's value. + :type value: string + """ self._value = value def get(self): diff --git a/sendgrid/helpers/mail/send_at.py b/sendgrid/helpers/mail/send_at.py new file mode 100644 index 000000000..6e3a1541a --- /dev/null +++ b/sendgrid/helpers/mail/send_at.py @@ -0,0 +1,79 @@ +class SendAt(object): + """A unix timestamp allowing you to specify when you want your + email to be delivered. This may be overridden by the + personalizations[x].send_at parameter. You can't schedule more + than 72 hours in advance. If you have the flexibility, it's + better to schedule mail for off-peak times. Most emails are + scheduled and sent at the top of the hour or half hour. + Scheduling email to avoid those times (for example, scheduling + at 10:53) can result in lower deferral rates because it won't + be going through our servers at the same times as everyone else's + mail.""" + def __init__(self, send_at=None, p=None): + """Create a unix timestamp specifying when your email should + be delivered. + + :param send_at: Unix timestamp + :type send_at: integer + :param name: p is the Personalization object or Personalization object + index + :type name: Personalization, integer, optional + """ + self._send_at = None + self._personalization = None + + if send_at is not None: + self.send_at = send_at + if p is not None: + self.personalization = p + + @property + def send_at(self): + """A unix timestamp. + + :rtype: integer + """ + return self._send_at + + @send_at.setter + def send_at(self, value): + """A unix timestamp. + + :param value: A unix timestamp. + :type value: integer + """ + self._send_at = value + + @property + def personalization(self): + """The Personalization object or Personalization object index + + :rtype: Personalization, integer + """ + return self._personalization + + @personalization.setter + def personalization(self, value): + """The Personalization object or Personalization object index + + :param value: The Personalization object or Personalization object + index + :type value: Personalization, integer + """ + self._personalization = value + + def __str__(self): + """Get a JSON representation of this object. + + :rtype: integer + """ + return str(self.get()) + + def get(self): + """ + Get a JSON-ready representation of this SendAt object. + + :returns: The unix timestamp, ready for use in a request body. + :rtype: integer + """ + return self.send_at diff --git a/sendgrid/helpers/mail/spam_check.py b/sendgrid/helpers/mail/spam_check.py index 61eaa0cfc..c584f8cff 100644 --- a/sendgrid/helpers/mail/spam_check.py +++ b/sendgrid/helpers/mail/spam_check.py @@ -1,3 +1,7 @@ +from .spam_threshold import SpamThreshold +from .spam_url import SpamUrl + + class SpamCheck(object): """This allows you to test the content of your email for spam.""" @@ -11,9 +15,16 @@ def __init__(self, enable=None, threshold=None, post_to_url=None): :param post_to_url: Inbound Parse URL to send a copy of your email. :type post_to_url: string, optional """ - self.enable = enable - self.threshold = threshold - self.post_to_url = post_to_url + self._enable = None + self._threshold = None + self._post_to_url = None + + if enable is not None: + self.enable = enable + if threshold is not None: + self.threshold = threshold + if post_to_url is not None: + self.post_to_url = post_to_url @property def enable(self): @@ -25,34 +36,62 @@ def enable(self): @enable.setter def enable(self, value): + """Indicates if this setting is enabled. + + :param value: Indicates if this setting is enabled. + :type value: boolean + """ self._enable = value @property def threshold(self): """Threshold used to determine if your content qualifies as spam. - On a scale from 1 to 10, with 10 being most strict, or most likely to be considered as spam. + :rtype: int """ return self._threshold @threshold.setter def threshold(self, value): - self._threshold = value + """Threshold used to determine if your content qualifies as spam. + On a scale from 1 to 10, with 10 being most strict, or most likely to + be considered as spam. + + :param value: Threshold used to determine if your content qualifies as + spam. + On a scale from 1 to 10, with 10 being most strict, or + most likely to be considered as spam. + :type value: int + """ + if isinstance(value, SpamThreshold): + self._threshold = value + else: + self._threshold = SpamThreshold(value) @property def post_to_url(self): """An Inbound Parse URL to send a copy of your email. - If defined, a copy of your email and its spam report will be sent here. + :rtype: string """ return self._post_to_url @post_to_url.setter def post_to_url(self, value): - self._post_to_url = value + """An Inbound Parse URL to send a copy of your email. + If defined, a copy of your email and its spam report will be sent here. + + :param value: An Inbound Parse URL to send a copy of your email. + If defined, a copy of your email and its spam report will be sent here. + :type value: string + """ + if isinstance(value, SpamUrl): + self._post_to_url = value + else: + self._post_to_url = SpamUrl(value) def get(self): """ @@ -66,8 +105,8 @@ def get(self): spam_check["enable"] = self.enable if self.threshold is not None: - spam_check["threshold"] = self.threshold + spam_check["threshold"] = self.threshold.get() if self.post_to_url is not None: - spam_check["post_to_url"] = self.post_to_url + spam_check["post_to_url"] = self.post_to_url.get() return spam_check diff --git a/sendgrid/helpers/mail/spam_threshold.py b/sendgrid/helpers/mail/spam_threshold.py new file mode 100644 index 000000000..45053dbdf --- /dev/null +++ b/sendgrid/helpers/mail/spam_threshold.py @@ -0,0 +1,53 @@ +class SpamThreshold(object): + """The threshold used to determine if your content qualifies as spam + on a scale from 1 to 10, with 10 being most strict, or most likely + to be considered as spam.""" + + def __init__(self, spam_threshold=None): + """Create a SpamThreshold object + + :param spam_threshold: The threshold used to determine if your content + qualifies as spam on a scale from 1 to 10, with + 10 being most strict, or most likely to be + considered as spam. + :type spam_threshold: integer, optional + """ + self._spam_threshold = None + + if spam_threshold is not None: + self.spam_threshold = spam_threshold + + @property + def spam_threshold(self): + """The threshold used to determine if your content + qualifies as spam on a scale from 1 to 10, with + 10 being most strict, or most likely to be + considered as spam. + + :rtype: integer + """ + return self._spam_threshold + + @spam_threshold.setter + def spam_threshold(self, value): + """The threshold used to determine if your content + qualifies as spam on a scale from 1 to 10, with + 10 being most strict, or most likely to be + considered as spam. + + :param value: The threshold used to determine if your content + qualifies as spam on a scale from 1 to 10, with + 10 being most strict, or most likely to be + considered as spam. + :type value: integer + """ + self._spam_threshold = value + + def get(self): + """ + Get a JSON-ready representation of this SpamThreshold. + + :returns: This SpamThreshold, ready for use in a request body. + :rtype: integer + """ + return self.spam_threshold diff --git a/sendgrid/helpers/mail/spam_url.py b/sendgrid/helpers/mail/spam_url.py new file mode 100644 index 000000000..529438ced --- /dev/null +++ b/sendgrid/helpers/mail/spam_url.py @@ -0,0 +1,44 @@ +class SpamUrl(object): + """An Inbound Parse URL that you would like a copy of your email + along with the spam report to be sent to.""" + + def __init__(self, spam_url=None): + """Create a SpamUrl object + + :param spam_url: An Inbound Parse URL that you would like a copy of + your email along with the spam report to be sent to. + :type spam_url: string, optional + """ + self._spam_url = None + + if spam_url is not None: + self.spam_url = spam_url + + @property + def spam_url(self): + """An Inbound Parse URL that you would like a copy of your email + along with the spam report to be sent to. + + :rtype: string + """ + return self._spam_url + + @spam_url.setter + def spam_url(self, value): + """An Inbound Parse URL that you would like a copy of your email + along with the spam report to be sent to. + + :param value: An Inbound Parse URL that you would like a copy of your + email along with the spam report to be sent to. + :type value: string + """ + self._spam_url = value + + def get(self): + """ + Get a JSON-ready representation of this SpamUrl. + + :returns: This SpamUrl, ready for use in a request body. + :rtype: string + """ + return self.spam_url diff --git a/sendgrid/helpers/mail/subject.py b/sendgrid/helpers/mail/subject.py new file mode 100644 index 000000000..1637f4004 --- /dev/null +++ b/sendgrid/helpers/mail/subject.py @@ -0,0 +1,69 @@ +class Subject(object): + """A subject for an email message.""" + + def __init__(self, subject, p=None): + """Create a Subject. + + :param subject: The subject for an email + :type subject: string + :param name: p is the Personalization object or Personalization object + index + :type name: Personalization, integer, optional + """ + self._subject = None + self._personalization = None + + self.subject = subject + if p is not None: + self.personalization = p + + @property + def subject(self): + """The subject of an email. + + :rtype: string + """ + return self._subject + + @subject.setter + def subject(self, value): + """The subject of an email. + + :param value: The subject of an email. + :type value: string + """ + self._subject = value + + @property + def personalization(self): + """The Personalization object or Personalization object index + + :rtype: Personalization, integer + """ + return self._personalization + + @personalization.setter + def personalization(self, value): + """The Personalization object or Personalization object index + + :param value: The Personalization object or Personalization object + index + :type value: Personalization, integer + """ + self._personalization = value + + def __str__(self): + """Get a JSON representation of this Mail request. + + :rtype: string + """ + return str(self.get()) + + def get(self): + """ + Get a JSON-ready representation of this Subject. + + :returns: This Subject, ready for use in a request body. + :rtype: string + """ + return self.subject diff --git a/sendgrid/helpers/mail/subscription_html.py b/sendgrid/helpers/mail/subscription_html.py new file mode 100644 index 000000000..bfe8629f8 --- /dev/null +++ b/sendgrid/helpers/mail/subscription_html.py @@ -0,0 +1,45 @@ +class SubscriptionHtml(object): + """The HTML of an SubscriptionTracking.""" + + def __init__(self, subscription_html=None): + """Create a SubscriptionHtml object + + :param subscription_html: Html to be appended to the email, with the + subscription tracking link. You may control + where the link is by using the tag <% %> + :type subscription_html: string, optional + """ + self._subscription_html = None + + if subscription_html is not None: + self.subscription_html = subscription_html + + @property + def subscription_html(self): + """Html to be appended to the email, with the subscription tracking link. + You may control where the link is by using the tag <% %> + + :rtype: string + """ + return self._subscription_html + + @subscription_html.setter + def subscription_html(self, value): + """Html to be appended to the email, with the subscription tracking link. + You may control where the link is by using the tag <% %> + + :param value: Html to be appended to the email, with the subscription + tracking link. You may control where the link is by using + the tag <% %> + :type value: string + """ + self._subscription_html = value + + def get(self): + """ + Get a JSON-ready representation of this SubscriptionHtml. + + :returns: This SubscriptionHtml, ready for use in a request body. + :rtype: string + """ + return self.subscription_html diff --git a/sendgrid/helpers/mail/subscription_substitution_tag.py b/sendgrid/helpers/mail/subscription_substitution_tag.py new file mode 100644 index 000000000..9a7d6a95d --- /dev/null +++ b/sendgrid/helpers/mail/subscription_substitution_tag.py @@ -0,0 +1,58 @@ +class SubscriptionSubstitutionTag(object): + """The subscription substitution tag of an SubscriptionTracking.""" + + def __init__(self, subscription_substitution_tag=None): + """Create a SubscriptionSubstitutionTag object + + :param subscription_substitution_tag: A tag that will be replaced with + the unsubscribe URL. for example: + [unsubscribe_url]. If this + parameter is used, it will + override both the text and html + parameters. The URL of the link + will be placed at the + substitution tag's location, + with no additional formatting. + :type subscription_substitution_tag: string, optional + """ + self._subscription_substitution_tag = None + + if subscription_substitution_tag is not None: + self.subscription_substitution_tag = subscription_substitution_tag + + @property + def subscription_substitution_tag(self): + """A tag that will be replaced with the unsubscribe URL. for example: + [unsubscribe_url]. If this parameter is used, it will override both + the text and html parameters. The URL of the link will be placed at + the substitution tag's location, with no additional formatting. + + :rtype: string + """ + return self._subscription_substitution_tag + + @subscription_substitution_tag.setter + def subscription_substitution_tag(self, value): + """A tag that will be replaced with the unsubscribe URL. for example: + [unsubscribe_url]. If this parameter is used, it will override both + the text and html parameters. The URL of the link will be placed at + the substitution tag's location, with no additional formatting. + + :param value: A tag that will be replaced with the unsubscribe URL. + for example: [unsubscribe_url]. If this parameter is + used, it will override both the text and html parameters. + The URL of the link will be placed at the substitution + tag's location, with no additional formatting. + :type value: string + """ + self._subscription_substitution_tag = value + + def get(self): + """ + Get a JSON-ready representation of this SubscriptionSubstitutionTag. + + :returns: This SubscriptionSubstitutionTag, ready for use in a request + body. + :rtype: string + """ + return self.subscription_substitution_tag diff --git a/sendgrid/helpers/mail/subscription_text.py b/sendgrid/helpers/mail/subscription_text.py new file mode 100644 index 000000000..ca20894af --- /dev/null +++ b/sendgrid/helpers/mail/subscription_text.py @@ -0,0 +1,45 @@ +class SubscriptionText(object): + """The text of an SubscriptionTracking.""" + + def __init__(self, subscription_text=None): + """Create a SubscriptionText object + + :param subscription_text: Text to be appended to the email, with the + subscription tracking link. You may control + where the link is by using the tag <% %> + :type subscription_text: string, optional + """ + self._subscription_text = None + + if subscription_text is not None: + self.subscription_text = subscription_text + + @property + def subscription_text(self): + """Text to be appended to the email, with the subscription tracking link. + You may control where the link is by using the tag <% %> + + :rtype: string + """ + return self._subscription_text + + @subscription_text.setter + def subscription_text(self, value): + """Text to be appended to the email, with the subscription tracking link. + You may control where the link is by using the tag <% %> + + :param value: Text to be appended to the email, with the subscription + tracking link. You may control where the link is by using + the tag <% %> + :type value: string + """ + self._subscription_text = value + + def get(self): + """ + Get a JSON-ready representation of this SubscriptionText. + + :returns: This SubscriptionText, ready for use in a request body. + :rtype: string + """ + return self.subscription_text diff --git a/sendgrid/helpers/mail/subscription_tracking.py b/sendgrid/helpers/mail/subscription_tracking.py index 204e427f0..8db653728 100644 --- a/sendgrid/helpers/mail/subscription_tracking.py +++ b/sendgrid/helpers/mail/subscription_tracking.py @@ -4,22 +4,33 @@ class SubscriptionTracking(object): location of the link within your email, you may use the substitution_tag. """ - def __init__(self, enable=None, text=None, html=None, substitution_tag=None): + def __init__( + self, enable=None, text=None, html=None, substitution_tag=None): """Create a SubscriptionTracking to customize subscription management. :param enable: Whether this setting is enabled. :type enable: boolean, optional :param text: Text to be appended to the email with the link as "<% %>". - :type text: string, optional + :type text: SubscriptionText, optional :param html: HTML to be appended to the email with the link as "<% %>". - :type html: string, optional - :param substitution_tag: Tag replaced with URL. Overrides text, html params. - :type substitution_tag: string, optional + :type html: SubscriptionHtml, optional + :param substitution_tag: Tag replaced with URL. Overrides text, html + params. + :type substitution_tag: SubscriptionSubstitutionTag, optional """ - self.enable = enable - self.text = text - self.html = html - self.substitution_tag = substitution_tag + self._enable = None + self._text = None + self._html = None + self._substitution_tag = None + + if enable is not None: + self.enable = enable + if text is not None: + self.text = text + if html is not None: + self.html = html + if substitution_tag is not None: + self.substitution_tag = substitution_tag @property def enable(self): @@ -31,30 +42,53 @@ def enable(self): @enable.setter def enable(self, value): + """Indicates if this setting is enabled. + + :param value: Indicates if this setting is enabled. + :type value: boolean + """ self._enable = value @property def text(self): """Text to be appended to the email, with the subscription tracking link. You may control where the link is by using the tag <% %> + :rtype: string """ return self._text @text.setter def text(self, value): + """Text to be appended to the email, with the subscription tracking + link. You may control where the link is by using the tag <% %> + + :param value: Text to be appended to the email, with the subscription + tracking link. You may control where the link is by + using the tag <% %> + :type value: string + """ self._text = value @property def html(self): """HTML to be appended to the email, with the subscription tracking link. You may control where the link is by using the tag <% %> + :rtype: string """ return self._html @html.setter def html(self, value): + """HTML to be appended to the email, with the subscription tracking + link. You may control where the link is by using the tag <% %> + + :param value: HTML to be appended to the email, with the subscription + tracking link. You may control where the link is by + using the tag <% %> + :type value: string + """ self._html = value @property @@ -63,12 +97,26 @@ def substitution_tag(self): [unsubscribe_url]. If this parameter is used, it will override both the `text` and `html` parameters. The URL of the link will be placed at the substitution tag's location, with no additional formatting. + :rtype: string """ return self._substitution_tag @substitution_tag.setter def substitution_tag(self, value): + """"A tag that will be replaced with the unsubscribe URL. for example: + [unsubscribe_url]. If this parameter is used, it will override both the + `text` and `html` parameters. The URL of the link will be placed at the + substitution tag's location, with no additional formatting. + + :param value: A tag that will be replaced with the unsubscribe URL. + For example: [unsubscribe_url]. If this parameter is + used, it will override both the `text` and `html` + parameters. The URL of the link will be placed at the + substitution tag's location, with no additional + formatting. + :type value: string + """ self._substitution_tag = value def get(self): @@ -83,11 +131,12 @@ def get(self): subscription_tracking["enable"] = self.enable if self.text is not None: - subscription_tracking["text"] = self.text + subscription_tracking["text"] = self.text.get() if self.html is not None: - subscription_tracking["html"] = self.html + subscription_tracking["html"] = self.html.get() if self.substitution_tag is not None: - subscription_tracking["substitution_tag"] = self.substitution_tag + subscription_tracking["substitution_tag"] = \ + self.substitution_tag.get() return subscription_tracking diff --git a/sendgrid/helpers/mail/substitution.py b/sendgrid/helpers/mail/substitution.py index a441ebe64..d515e885b 100644 --- a/sendgrid/helpers/mail/substitution.py +++ b/sendgrid/helpers/mail/substitution.py @@ -3,33 +3,80 @@ class Substitution(object): the body of your email, as well as in the Subject and Reply-To parameters. """ - def __init__(self, key=None, value=None): + def __init__(self, key=None, value=None, p=None): """Create a Substitution with the given key and value. :param key: Text to be replaced with "value" param :type key: string, optional :param value: Value to substitute into email :type value: string, optional + :param name: p is the Personalization object or Personalization object + index + :type name: Personalization, integer, optional """ - self.key = key - self.value = value + self._key = None + self._value = None + self._personalization = None + + if key is not None: + self.key = key + if value is not None: + self.value = value + if p is not None: + self.personalization = p @property def key(self): + """The substitution key. + + :rtype key: string + """ return self._key @key.setter def key(self, value): + """The substitution key. + + :param key: The substitution key. + :type key: string + """ self._key = value @property def value(self): - return self._value + """The substitution value. + + :rtype value: string + """ + return str(self._value) if isinstance(self._value, int) else self._value @value.setter def value(self, value): + """The substitution value. + + :param value: The substitution value. + :type value: string + """ self._value = value + @property + def personalization(self): + """The Personalization object or Personalization object index + + :rtype: Personalization, integer + """ + return self._personalization + + @personalization.setter + def personalization(self, value): + """The Personalization object or Personalization object index + + :param value: The Personalization object or Personalization object + index + :type value: Personalization, integer + """ + self._personalization = value + def get(self): """ Get a JSON-ready representation of this Substitution. diff --git a/sendgrid/helpers/mail/template_id.py b/sendgrid/helpers/mail/template_id.py new file mode 100644 index 000000000..f18c5f588 --- /dev/null +++ b/sendgrid/helpers/mail/template_id.py @@ -0,0 +1,39 @@ +class TemplateId(object): + """The template ID of an Attachment object.""" + + def __init__(self, template_id=None): + """Create a TemplateId object + + :param template_id: The template id for the message + :type template_id: string, optional + """ + self._template_id = None + + if template_id is not None: + self.template_id = template_id + + @property + def template_id(self): + """The template id for the message + + :rtype: string + """ + return self._template_id + + @template_id.setter + def template_id(self, value): + """The template id for the message + + :param value: The template id for the message + :type value: string + """ + self._template_id = value + + def get(self): + """ + Get a JSON-ready representation of this TemplateId. + + :returns: This TemplateId, ready for use in a request body. + :rtype: string + """ + return self.template_id diff --git a/sendgrid/helpers/mail/to_email.py b/sendgrid/helpers/mail/to_email.py new file mode 100644 index 000000000..e4f294f03 --- /dev/null +++ b/sendgrid/helpers/mail/to_email.py @@ -0,0 +1,5 @@ +from .email import Email + + +class To(Email): + """A to email address with an optional name.""" diff --git a/sendgrid/helpers/mail/tracking_settings.py b/sendgrid/helpers/mail/tracking_settings.py index cb19e2bae..d5f2e4fd5 100644 --- a/sendgrid/helpers/mail/tracking_settings.py +++ b/sendgrid/helpers/mail/tracking_settings.py @@ -1,13 +1,49 @@ class TrackingSettings(object): """Settings to track how recipients interact with your email.""" - def __init__(self): - """Create an empty TrackingSettings.""" + def __init__(self, + click_tracking=None, + open_tracking=None, + subscription_tracking=None, + ganalytics=None): + """Create a TrackingSettings object + + :param click_tracking: Allows you to track whether a recipient clicked + a link in your email. + :type click_tracking: ClickTracking, optional + :param open_tracking: Allows you to track whether the email was opened + or not, but including a single pixel image in + the body of the content. When the pixel is + loaded, we can log that the email was opened. + :type open_tracking: OpenTracking, optional + :param subscription_tracking: Allows you to insert a subscription + management link at the bottom of the + text and html bodies of your email. If + you would like to specify the location + of the link within your email, you may + use the substitution_tag. + :type subscription_tracking: SubscriptionTracking, optional + :param ganalytics: Allows you to enable tracking provided by Google + Analytics. + :type ganalytics: Ganalytics, optional + """ self._click_tracking = None self._open_tracking = None self._subscription_tracking = None self._ganalytics = None + if click_tracking is not None: + self._click_tracking = click_tracking + + if open_tracking is not None: + self._open_tracking = open_tracking + + if subscription_tracking is not None: + self._subscription_tracking = subscription_tracking + + if ganalytics is not None: + self._ganalytics = ganalytics + @property def click_tracking(self): """Allows you to track whether a recipient clicked a link in your email. @@ -18,6 +54,12 @@ def click_tracking(self): @click_tracking.setter def click_tracking(self, value): + """Allows you to track whether a recipient clicked a link in your email. + + :param value: Allows you to track whether a recipient clicked a link + in your email. + :type value: ClickTracking + """ self._click_tracking = value @property @@ -30,6 +72,12 @@ def open_tracking(self): @open_tracking.setter def open_tracking(self, value): + """Allows you to track whether a recipient opened your email. + + :param value: Allows you to track whether a recipient opened your + email. + :type value: OpenTracking + """ self._open_tracking = value @property @@ -42,6 +90,11 @@ def subscription_tracking(self): @subscription_tracking.setter def subscription_tracking(self, value): + """Settings for the subscription management link. + + :param value: Settings for the subscription management link. + :type value: SubscriptionTracking + """ self._subscription_tracking = value @property @@ -54,6 +107,11 @@ def ganalytics(self): @ganalytics.setter def ganalytics(self, value): + """Settings for Google Analytics. + + :param value: Settings for Google Analytics. + :type value: Ganalytics + """ self._ganalytics = value def get(self): diff --git a/sendgrid/helpers/mail/utm_campaign.py b/sendgrid/helpers/mail/utm_campaign.py new file mode 100644 index 000000000..5b2006824 --- /dev/null +++ b/sendgrid/helpers/mail/utm_campaign.py @@ -0,0 +1,40 @@ +class UtmCampaign(object): + """The utm campaign of an Ganalytics object.""" + + def __init__(self, utm_campaign=None): + """Create a UtmCampaign object + + :param utm_campaign: The name of the campaign + + :type utm_campaign: string, optional + """ + self._utm_campaign = None + + if utm_campaign is not None: + self.utm_campaign = utm_campaign + + @property + def utm_campaign(self): + """The name of the campaign + + :rtype: string + """ + return self._utm_campaign + + @utm_campaign.setter + def utm_campaign(self, value): + """The name of the campaign + + :param value: The name of the campaign + :type value: string + """ + self._utm_campaign = value + + def get(self): + """ + Get a JSON-ready representation of this UtmCampaign. + + :returns: This UtmCampaign, ready for use in a request body. + :rtype: string + """ + return self.utm_campaign diff --git a/sendgrid/helpers/mail/utm_content.py b/sendgrid/helpers/mail/utm_content.py new file mode 100644 index 000000000..e2a8ccff6 --- /dev/null +++ b/sendgrid/helpers/mail/utm_content.py @@ -0,0 +1,40 @@ +class UtmContent(object): + """The utm content of an Ganalytics object.""" + + def __init__(self, utm_content=None): + """Create a UtmContent object + + :param utm_content: Used to differentiate your campaign from advertisements. + + :type utm_content: string, optional + """ + self._utm_content = None + + if utm_content is not None: + self.utm_content = utm_content + + @property + def utm_content(self): + """Used to differentiate your campaign from advertisements. + + :rtype: string + """ + return self._utm_content + + @utm_content.setter + def utm_content(self, value): + """Used to differentiate your campaign from advertisements. + + :param value: Used to differentiate your campaign from advertisements. + :type value: string + """ + self._utm_content = value + + def get(self): + """ + Get a JSON-ready representation of this UtmContent. + + :returns: This UtmContent, ready for use in a request body. + :rtype: string + """ + return self.utm_content diff --git a/sendgrid/helpers/mail/utm_medium.py b/sendgrid/helpers/mail/utm_medium.py new file mode 100644 index 000000000..3687a0b2b --- /dev/null +++ b/sendgrid/helpers/mail/utm_medium.py @@ -0,0 +1,40 @@ +class UtmMedium(object): + """The utm medium of an Ganalytics object.""" + + def __init__(self, utm_medium=None): + """Create a UtmMedium object + + :param utm_medium: Name of the marketing medium. (e.g. Email) + + :type utm_medium: string, optional + """ + self._utm_medium = None + + if utm_medium is not None: + self.utm_medium = utm_medium + + @property + def utm_medium(self): + """Name of the marketing medium. (e.g. Email) + + :rtype: string + """ + return self._utm_medium + + @utm_medium.setter + def utm_medium(self, value): + """Name of the marketing medium. (e.g. Email) + + :param value: Name of the marketing medium. (e.g. Email) + :type value: string + """ + self._utm_medium = value + + def get(self): + """ + Get a JSON-ready representation of this UtmMedium. + + :returns: This UtmMedium, ready for use in a request body. + :rtype: string + """ + return self.utm_medium diff --git a/sendgrid/helpers/mail/utm_source.py b/sendgrid/helpers/mail/utm_source.py new file mode 100644 index 000000000..841ba0a80 --- /dev/null +++ b/sendgrid/helpers/mail/utm_source.py @@ -0,0 +1,43 @@ +class UtmSource(object): + """The utm source of an Ganalytics object.""" + + def __init__(self, utm_source=None): + """Create a UtmSource object + + :param utm_source: Name of the referrer source. + (e.g. Google, SomeDomain.com, or Marketing Email) + :type utm_source: string, optional + """ + self._utm_source = None + + if utm_source is not None: + self.utm_source = utm_source + + @property + def utm_source(self): + """Name of the referrer source. (e.g. Google, SomeDomain.com, or + Marketing Email) + + :rtype: string + """ + return self._utm_source + + @utm_source.setter + def utm_source(self, value): + """Name of the referrer source. (e.g. Google, SomeDomain.com, or + Marketing Email) + + :param value: Name of the referrer source. + (e.g. Google, SomeDomain.com, or Marketing Email) + :type value: string + """ + self._utm_source = value + + def get(self): + """ + Get a JSON-ready representation of this UtmSource. + + :returns: This UtmSource, ready for use in a request body. + :rtype: string + """ + return self.utm_source diff --git a/sendgrid/helpers/mail/utm_term.py b/sendgrid/helpers/mail/utm_term.py new file mode 100644 index 000000000..e8a9518a6 --- /dev/null +++ b/sendgrid/helpers/mail/utm_term.py @@ -0,0 +1,40 @@ +class UtmTerm(object): + """The utm term of an Ganalytics object.""" + + def __init__(self, utm_term=None): + """Create a UtmTerm object + + :param utm_term: Used to identify any paid keywords. + + :type utm_term: string, optional + """ + self._utm_term = None + + if utm_term is not None: + self.utm_term = utm_term + + @property + def utm_term(self): + """Used to identify any paid keywords. + + :rtype: string + """ + return self._utm_term + + @utm_term.setter + def utm_term(self, value): + """Used to identify any paid keywords. + + :param value: Used to identify any paid keywords. + :type value: string + """ + self._utm_term = value + + def get(self): + """ + Get a JSON-ready representation of this UtmTerm. + + :returns: This UtmTerm, ready for use in a request body. + :rtype: string + """ + return self.utm_term diff --git a/sendgrid/helpers/mail/validators.py b/sendgrid/helpers/mail/validators.py new file mode 100644 index 000000000..00e3276e4 --- /dev/null +++ b/sendgrid/helpers/mail/validators.py @@ -0,0 +1,69 @@ +from .exceptions import ApiKeyIncludedException + + +class ValidateApiKey(object): + """Validates content to ensure SendGrid API key is not present""" + + regexes = None + + def __init__(self, regex_strings=None, use_default=True): + """Create an API key validator + + :param regex_strings: list of regex strings + :type regex_strings: list(str) + :param use_default: Whether or not to include default regex + :type use_default: bool + """ + + import re + self.regexes = set() + + # Compile the regex strings into patterns, add them to our set + if regex_strings is not None: + for regex_string in regex_strings: + self.regexes.add(re.compile(regex_string)) + + if use_default: + default_regex_string = r'SG\.[0-9a-zA-Z]+\.[0-9a-zA-Z]+' + self.regexes.add(re.compile(default_regex_string)) + + def validate_message_dict(self, request_body): + """With the JSON dict that will be sent to SendGrid's API, + check the content for SendGrid API keys - throw exception if found. + + :param request_body: The JSON dict that will be sent to SendGrid's + API. + :type request_body: JSON serializable structure + :raise ApiKeyIncludedException: If any content in request_body + matches regex + """ + + # Handle string in edge-case + if isinstance(request_body, str): + self.validate_message_text(request_body) + + # Default param + elif isinstance(request_body, dict): + + contents = request_body.get("content", list()) + + for content in contents: + if content is not None: + if (content.get("type") == "text/html" or + isinstance(content.get("value"), str)): + message_text = content.get("value", "") + self.validate_message_text(message_text) + + def validate_message_text(self, message_string): + """With a message string, check to see if it contains a SendGrid API Key + If a key is found, throw an exception + + :param message_string: message that will be sent + :type message_string: string + :raises ApiKeyIncludedException: If message_string matches a regex + string + """ + if isinstance(message_string, str): + for regex in self.regexes: + if regex.match(message_string) is not None: + raise ApiKeyIncludedException() diff --git a/sendgrid/helpers/stats/README.md b/sendgrid/helpers/stats/README.md new file mode 100644 index 000000000..f1591ecce --- /dev/null +++ b/sendgrid/helpers/stats/README.md @@ -0,0 +1,10 @@ +**This helper allows you to quickly and easily build a Stats object for sending your email stats to a database.** + +# Quick Start + +Run the [example](../../../examples/helpers/stats) (make sure you have set your environment variable to include your SENDGRID_API_KEY). + +## Usage + +- See the [examples](../../../examples/helpers/stats) for complete working examples. +- [Documentation](https://sendgrid.com/docs/API_Reference/Web_API_v3/Stats/index.html) diff --git a/sendgrid/helpers/stats/__init__.py b/sendgrid/helpers/stats/__init__.py new file mode 100644 index 000000000..9ee4dcdd8 --- /dev/null +++ b/sendgrid/helpers/stats/__init__.py @@ -0,0 +1 @@ +from .stats import * # noqa diff --git a/sendgrid/helpers/stats/stats.py b/sendgrid/helpers/stats/stats.py new file mode 100644 index 000000000..b866093b5 --- /dev/null +++ b/sendgrid/helpers/stats/stats.py @@ -0,0 +1,384 @@ +class Stats(object): + """ + Object for building query params for a global email statistics request + """ + def __init__( + self, start_date=None): + """Create a Stats object + + :param start_date: Date of when stats should begin in YYYY-MM-DD format, defaults to None + :type start_date: string, optional + """ + self._start_date = None + self._end_date = None + self._aggregated_by = None + self._sort_by_metric = None + self._sort_by_direction = None + self._limit = None + self._offset = None + + # Minimum required for stats + if start_date: + self.start_date = start_date + + def __str__(self): + """Get a JSON representation of this object. + + :rtype: string + """ + return str(self.get()) + + def get(self): + """ + Get a JSON-ready representation of Stats + + :returns: This GlobalStats, ready for use in a request body. + :rtype: response stats dict + """ + stats = {} + if self.start_date is not None: + stats["start_date"] = self.start_date + if self.end_date is not None: + stats["end_date"] = self.end_date + if self.aggregated_by is not None: + stats["aggregated_by"] = self.aggregated_by + if self.sort_by_metric is not None: + stats["sort_by_metric"] = self.sort_by_metric + if self.sort_by_direction is not None: + stats["sort_by_direction"] = self.sort_by_direction + if self.limit is not None: + stats["limit"] = self.limit + if self.offset is not None: + stats["offset"] = self.offset + return stats + + @property + def start_date(self): + """Date of when stats should begin in YYYY-MM-DD format + + :rtype: string + """ + return self._start_date + + @start_date.setter + def start_date(self, value): + """Date of when stats should begin in YYYY-MM-DD format + + :param value: Date representing when stats should begin + :type value: string + """ + self._start_date = value + + @property + def end_date(self): + """Date of when stats should end in YYYY-MM-DD format + + :rtype: string + """ + return self._end_date + + @end_date.setter + def end_date(self, value): + """Date of when stats should end in YYYY-MM-DD format + + :param value: Date representing when stats should end + :type value: string + """ + self._end_date = value + + @property + def aggregated_by(self): + """Chosen period (e.g. 'day', 'week', 'month') for how stats get grouped + + :rtype: string + """ + return self._aggregated_by + + @aggregated_by.setter + def aggregated_by(self, value): + """Chosen period (e.g. 'day', 'week', 'month') for how stats get grouped + + :param value: Period for how keys will get formatted + :type value: string + """ + self._aggregated_by = value + + @property + def sort_by_metric(self): + """Metric to sort stats by + + :rtype: string + """ + return self._sort_by_metric + + @sort_by_metric.setter + def sort_by_metric(self, value): + """Metric to sort stats by + + :param value: Chosen metric stats will by sorted by + :type value: string + """ + self._sort_by_metric = value + + @property + def sort_by_direction(self): + """Direction data will be sorted, either 'asc' or 'desc' + + :rtype: string + """ + return self._sort_by_direction + + @sort_by_direction.setter + def sort_by_direction(self, value): + """Direction data will be sorted, either 'asc' or 'desc' + + :param value: Direction of data, either 'asc' or 'desc' + :type value: string + """ + self._sort_by_direction = value + + @property + def limit(self): + """Max amount of results to be returned + + :rtype: int + """ + return self._limit + + @limit.setter + def limit(self, value): + """Max amount of results to be returned + + :param value: Max amount of results + :type value: int + """ + self._limit = value + + @property + def offset(self): + """Number of places a starting point of a data set will move + + :rtype: int + """ + return self._offset + + @offset.setter + def offset(self, value): + """Number of places a starting point of a data set will move + + :param value: Number of positions to move from starting point + :type value: int + """ + self._offset = value + + +class CategoryStats(Stats): + """ + object for building query params for a category statistics request + """ + def __init__(self, start_date=None, categories=None): + """Create a CategoryStats object + + :param start_date: Date of when stats should begin in YYYY-MM-DD format, defaults to None + :type start_date: string, optional + :param categories: list of categories to get results of, defaults to None + :type categories: list(string), optional + """ + self._categories = None + super(CategoryStats, self).__init__() + + # Minimum required for category stats + if start_date and categories: + self.start_date = start_date + for cat_name in categories: + self.add_category(Category(cat_name)) + + def get(self): + """ + Get a JSON-ready representation of this CategoryStats. + + :return: response category stats dict + """ + stats = {} + if self.start_date is not None: + stats["start_date"] = self.start_date + if self.end_date is not None: + stats["end_date"] = self.end_date + if self.aggregated_by is not None: + stats["aggregated_by"] = self.aggregated_by + if self.sort_by_metric is not None: + stats["sort_by_metric"] = self.sort_by_metric + if self.sort_by_direction is not None: + stats["sort_by_direction"] = self.sort_by_direction + if self.limit is not None: + stats["limit"] = self.limit + if self.offset is not None: + stats["offset"] = self.offset + if self.categories is not None: + stats['categories'] = [category.get() for category in + self.categories] + return stats + + @property + def categories(self): + """List of categories + + :rtype: list(Category) + """ + return self._categories + + def add_category(self, category): + """Appends a category to this object's category list + + :param category: Category to append to CategoryStats + :type category: Category + """ + if self._categories is None: + self._categories = [] + self._categories.append(category) + + +class SubuserStats(Stats): + """ + object of building query params for a subuser statistics request + """ + def __init__(self, start_date=None, subusers=None): + """Create a SubuserStats object + + :param start_date: Date of when stats should begin in YYYY-MM-DD format, defaults to None + :type start_date: string, optional + :param subusers: list of subusers to get results of, defaults to None + :type subusers: list(string), optional + """ + self._subusers = None + super(SubuserStats, self).__init__() + + # Minimum required for subusers stats + if start_date and subusers: + self.start_date = start_date + for subuser_name in subusers: + self.add_subuser(Subuser(subuser_name)) + + def get(self): + """ + Get a JSON-ready representation of this SubuserStats. + + :return: response subuser stats dict + """ + stats = {} + if self.start_date is not None: + stats["start_date"] = self.start_date + if self.end_date is not None: + stats["end_date"] = self.end_date + if self.aggregated_by is not None: + stats["aggregated_by"] = self.aggregated_by + if self.sort_by_metric is not None: + stats["sort_by_metric"] = self.sort_by_metric + if self.sort_by_direction is not None: + stats["sort_by_direction"] = self.sort_by_direction + if self.limit is not None: + stats["limit"] = self.limit + if self.offset is not None: + stats["offset"] = self.offset + if self.subusers is not None: + stats['subusers'] = [subuser.get() for subuser in + self.subusers] + return stats + + @property + def subusers(self): + """List of subusers + + :rtype: list(Subuser) + """ + return self._subusers + + def add_subuser(self, subuser): + """Appends a subuser to this object's subuser list + + :param subuser: Subuser to append to SubuserStats + :type subuser: Subuser + """ + if self._subusers is None: + self._subusers = [] + self._subusers.append(subuser) + + +class Category(object): + """ + Represents a searchable statistics category to be used in a CategoryStats object + """ + def __init__(self, name=None): + """Create a Category object + + :param name: name of category, defaults to None + :type name: string, optional + """ + self._name = None + if name is not None: + self._name = name + + @property + def name(self): + """Get name of category + + :rtype: string + """ + return self._name + + @name.setter + def name(self, value): + """Set name of category + + :param value: name of the statistical category + :type value: string + """ + self._name = value + + def get(self): + """ + Get a string representation of Category. + + :return: string of the category's name + """ + return self.name + + +class Subuser(object): + """ + Represents a searchable subuser to be used in a SubuserStats object + """ + def __init__(self, name=None): + """Create a Subuser object + + :param name: name of subuser, defaults to None + :type name: string, optional + """ + self._name = None + if name is not None: + self._name = name + + @property + def name(self): + """Get name of the subuser + + :rtype: string + """ + return self._name + + @name.setter + def name(self, value): + """Set name of the subuser + + :param value: name of the subuser + :type value: string + """ + self._name = value + + def get(self): + """ + Get a string representation of Subuser. + + :return: string of the subuser's name + """ + return self.name diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index b6c731b94..912d8336e 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -1,101 +1,58 @@ -""" -This library allows you to quickly and easily use the SendGrid Web API v3 via -Python. - -For more information on this library, see the README on Github. - http://github.com/sendgrid/sendgrid-python -For more information on the SendGrid v3 API, see the v3 docs: - http://sendgrid.com/docs/API_Reference/api_v3.html -For the user guide, code examples, and more, visit the main docs page: - http://sendgrid.com/docs/index.html - -This file provides the SendGrid API Client. -""" - - -import os -import python_http_client - -from .version import __version__ - - -class SendGridAPIClient(object): - """The SendGrid API Client. - - Use this object to interact with the v3 API. For example: - sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - ... - mail = Mail(from_email, subject, to_email, content) - response = sg.client.mail.send.post(request_body=mail.get()) - - For examples and detailed use instructions, see - https://github.com/sendgrid/sendgrid-python - """ - - def __init__(self, **opts): - """ - Construct SendGrid v3 API object. - - :params host: Base URL for the API call - :type host: string - :params apikey: SendGrid API key to use. Defaults to environment var. - :type apikey: string - """ - self.path = opts.get( - 'path', os.path.abspath(os.path.dirname(__file__))) - self._apikey = opts.get('apikey', os.environ.get('SENDGRID_API_KEY')) - # Support v2 api_key naming - self._apikey = opts.get('api_key', self._apikey) - self._api_key = self._apikey - # Support impersonation of subusers - self._impersonate_subuser = opts.get('impersonate_subuser', None) - self.useragent = 'sendgrid/{0};python'.format(__version__) - self.host = opts.get('host', 'https://api.sendgrid.com') - self.version = __version__ - - headers = self._get_default_headers() - - self.client = python_http_client.Client(host=self.host, - request_headers=headers, - version=3) - - def _get_default_headers(self): - headers = { - "Authorization": 'Bearer {0}'.format(self._apikey), - "User-agent": self.useragent, - "Accept": 'application/json' - } - if self._impersonate_subuser: - headers['On-Behalf-Of'] = self._impersonate_subuser - - return headers - - def reset_request_headers(self): - self.client.request_headers = self._get_default_headers() - - @property - def apikey(self): - """The API key (also accessible as api_key).""" - return self._apikey - - @apikey.setter - def apikey(self, value): - self._apikey = value - - @property - def api_key(self): - """The API key (also accessible as apikey).""" - return self._apikey - - @api_key.setter - def api_key(self, value): - self._apikey = value - - @property - def impersonate_subuser(self): - """ - The subuser you are impersonating. - - If present, this is the value of the "On-Behalf-Of" header. - """ - return self._impersonate_subuser +""" +This library allows you to quickly and easily use the Twilio SendGrid Web API v3 via Python. + +For more information on this library, see the README on GitHub. + http://github.com/sendgrid/sendgrid-python +For more information on the Twilio SendGrid v3 API, see the v3 docs: + http://sendgrid.com/docs/API_Reference/api_v3.html +For the user guide, code examples, and more, visit the main docs page: + http://sendgrid.com/docs/index.html + +This file provides the Twilio SendGrid API Client. +""" + +import os + +from .base_interface import BaseInterface + + +class SendGridAPIClient(BaseInterface): + """The Twilio SendGrid API Client. + + Use this object to interact with the v3 API. For example: + mail_client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + ... + mail = Mail(from_email, subject, to_email, content) + response = mail_client.send(mail) + + For examples and detailed use instructions, see + https://github.com/sendgrid/sendgrid-python + """ + + def __init__( + self, + api_key=None, + host='https://api.sendgrid.com', + impersonate_subuser=None): + """ + Construct the Twilio SendGrid v3 API object. + Note that the underlying client is being set up during initialization, + therefore changing attributes in runtime will not affect HTTP client + behaviour. + + :param api_key: Twilio SendGrid API key to use. If not provided, value + will be read from environment variable "SENDGRID_API_KEY" + :type api_key: string + :param impersonate_subuser: the subuser to impersonate. Will be passed + by "On-Behalf-Of" header by underlying + client. See + https://sendgrid.com/docs/User_Guide/Settings/subusers.html + for more details + :type impersonate_subuser: string + :param host: base URL for API calls + :type host: string + """ + self.api_key = api_key or os.environ.get('SENDGRID_API_KEY') + auth = 'Bearer {}'.format(self.api_key) + + super(SendGridAPIClient, self).__init__(auth, host, impersonate_subuser) diff --git a/sendgrid/twilio_email.py b/sendgrid/twilio_email.py new file mode 100644 index 000000000..78bd01815 --- /dev/null +++ b/sendgrid/twilio_email.py @@ -0,0 +1,73 @@ +""" +This library allows you to quickly and easily use the Twilio Email Web API v3 via Python. + +For more information on this library, see the README on GitHub. + http://github.com/sendgrid/sendgrid-python +For more information on the Twilio SendGrid v3 API, see the v3 docs: + http://sendgrid.com/docs/API_Reference/api_v3.html +For the user guide, code examples, and more, visit the main docs page: + http://sendgrid.com/docs/index.html + +This file provides the Twilio Email API Client. +""" +import os +from base64 import b64encode + +from .base_interface import BaseInterface + + +class TwilioEmailAPIClient(BaseInterface): + """The Twilio Email API Client. + + Use this object to interact with the v3 API. For example: + mail_client = sendgrid.TwilioEmailAPIClient(os.environ.get('TWILIO_API_KEY'), + os.environ.get('TWILIO_API_SECRET')) + ... + mail = Mail(from_email, subject, to_email, content) + response = mail_client.send(mail) + + For examples and detailed use instructions, see + https://github.com/sendgrid/sendgrid-python + """ + + def __init__( + self, + username=None, + password=None, + host='https://email.twilio.com', + impersonate_subuser=None): + """ + Construct the Twilio Email v3 API object. + Note that the underlying client is being set up during initialization, + therefore changing attributes in runtime will not affect HTTP client + behaviour. + + :param username: Twilio Email API key SID or Account SID to use. If not + provided, value will be read from the environment + variable "TWILIO_API_KEY" or "TWILIO_ACCOUNT_SID" + :type username: string + :param password: Twilio Email API key secret or Account Auth Token to + use. If not provided, value will be read from the + environment variable "TWILIO_API_SECRET" or + "TWILIO_AUTH_TOKEN" + :type password: string + :param impersonate_subuser: the subuser to impersonate. Will be passed + by "On-Behalf-Of" header by underlying + client. See + https://sendgrid.com/docs/User_Guide/Settings/subusers.html + for more details + :type impersonate_subuser: string + :param host: base URL for API calls + :type host: string + """ + self.username = username or \ + os.environ.get('TWILIO_API_KEY') or \ + os.environ.get('TWILIO_ACCOUNT_SID') + + self.password = password or \ + os.environ.get('TWILIO_API_SECRET') or \ + os.environ.get('TWILIO_AUTH_TOKEN') + + auth = 'Basic ' + b64encode('{}:{}'.format(self.username, self.password).encode()).decode() + + super(TwilioEmailAPIClient, self).__init__(auth, host, impersonate_subuser) diff --git a/sendgrid/version.py b/sendgrid/version.py index 3098f851d..c1d623f90 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1,2 +1 @@ -version_info = (5, 3, 0) -__version__ = '.'.join(str(v) for v in version_info) +__version__ = '6.12.5' diff --git a/setup.py b/setup.py index f95e4ee40..904cd654a 100644 --- a/setup.py +++ b/setup.py @@ -1,43 +1,54 @@ -import sys -import os -from setuptools import setup, find_packages - -__version__ = None -with open('sendgrid/version.py') as f: - exec(f.read()) - -long_description = 'Please see our GitHub README' -if os.path.exists('README.txt'): - long_description = open('README.txt').read() - - -def getRequires(): - deps = ['python_http_client>=3.0'] - if sys.version_info < (2, 7): - deps.append('unittest2') - elif (3, 0) <= sys.version_info < (3, 2): - deps.append('unittest2py3k') - return deps - -setup( - name='sendgrid', - version=str(__version__), - author='Elmer Thomas, Yamil Asusta', - author_email='dx@sendgrid.com', - url='https://github.com/sendgrid/sendgrid-python/', - packages=find_packages(exclude=["temp*.py", "register.py", "test"]), - include_package_data=True, - license='MIT', - description='SendGrid library for Python', - long_description=long_description, - install_requires=getRequires(), - classifiers=[ - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6' - ] -) +import io +import os +from setuptools import setup, find_packages + + +__version__ = None +with open('sendgrid/version.py') as f: + exec(f.read()) + +def getRequires(): + deps = [ + 'python_http_client>=3.2.1', + 'cryptography>=45.0.6', + "werkzeug>=0.11.15,<1.0.0 ; python_version < '3.0'", + "werkzeug>=0.15.0,<2.0.0 ; python_version >= '3.0' and python_version < '3.7'", + "werkzeug>=0.15.0,<2.3.0 ; python_version >= '3.0' and python_version < '3.8'", # version 2.3.0 dropped support for Python 3.7 + "werkzeug>=0.16.0,<3.1.0 ; python_version >= '3.0' and python_version < '3.9'", # version 3.1.0 dropped support for Python 3.8 + "werkzeug>=1.0.0 ; python_version >= '3.9'", + "werkzeug>=2.2.0 ; python_version >= '3.11'", + "werkzeug>=2.3.5 ; python_version >= '3.12'" + ] + return deps + + +dir_path = os.path.abspath(os.path.dirname(__file__)) +readme = io.open(os.path.join(dir_path, 'README.rst'), encoding='utf-8').read() + +setup( + name='sendgrid', + version=str(__version__), + author='Elmer Thomas, Yamil Asusta', + author_email='help@twilio.com', + url='https://github.com/sendgrid/sendgrid-python/', + packages=find_packages(exclude=["temp*.py", "test"]), + include_package_data=True, + license='MIT', + description='Twilio SendGrid library for Python', + long_description=readme, + install_requires=getRequires(), + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', + classifiers=[ + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', + ] +) diff --git a/static/img/github-fork.png b/static/img/github-fork.png new file mode 100644 index 000000000..6503be362 Binary files /dev/null and b/static/img/github-fork.png differ diff --git a/static/img/github-sign-up.png b/static/img/github-sign-up.png new file mode 100644 index 000000000..491392b8a Binary files /dev/null and b/static/img/github-sign-up.png differ diff --git a/test.csv b/test.csv new file mode 100644 index 000000000..119b886e0 --- /dev/null +++ b/test.csv @@ -0,0 +1 @@ +1, 2, 3, 4 \ No newline at end of file diff --git a/test/integ/__init__.py b/test/integ/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/test_sendgrid.py b/test/integ/test_sendgrid.py similarity index 93% rename from test/test_sendgrid.py rename to test/integ/test_sendgrid.py index d095d8be2..0c63851eb 100644 --- a/test/test_sendgrid.py +++ b/test/integ/test_sendgrid.py @@ -1,125 +1,57 @@ -import sendgrid -from sendgrid.helpers.mail import * -from sendgrid.version import __version__ -try: - import unittest2 as unittest -except ImportError: - import unittest -import os -import subprocess -import sys -import time import datetime +import os +import unittest -host = "http://localhost:4010" +import sendgrid +from sendgrid.helpers.endpoints.ip.unassigned import unassigned class UnitTests(unittest.TestCase): @classmethod def setUpClass(cls): - cls.host = host - cls.path = '{0}{1}'.format( + cls.path = '{}{}'.format( os.path.abspath( os.path.dirname(__file__)), '/..') - cls.sg = sendgrid.SendGridAPIClient( - host=host, path=cls.path, - api_key=os.environ.get('SENDGRID_API_KEY')) + cls.sg = sendgrid.SendGridAPIClient() cls.devnull = open(os.devnull, 'w') - prism_cmd = None - try: - # check for prism in the PATH - if subprocess.call('prism version'.split(), stdout=cls.devnull) == 0: - prism_cmd = 'prism' - except OSError: - prism_cmd = None - - if not prism_cmd: - # check for known prism locations - for path in ('/usr/local/bin/prism', os.path.expanduser(os.path.join('~', 'bin', 'prism')), - os.path.abspath(os.path.join(os.getcwd(), 'prism', 'bin', 'prism'))): - prism_cmd = path if os.path.isfile(path) else None - if prism_cmd: - break - if not prism_cmd: - if sys.platform != 'win32': - # try to install with prism.sh - try: - print("Warning: no prism detected, I will try to install it locally") - prism_sh = os.path.abspath(os.path.join(cls.path, 'test', 'prism.sh')) - if subprocess.call(prism_sh) == 0: - prism_cmd = os.path.expanduser(os.path.join('~', 'bin', 'prism')) - else: - raise RuntimeError() - except Exception as e: - print( - "Error installing the prism binary, you can try " - "downloading directly here " - "(https://github.com/stoplightio/prism/releases) " - "and place in your $PATH", e) - sys.exit() - else: - print("Please download the Windows binary " - "(https://github.com/stoplightio/prism/releases) " - "and place it in your %PATH% ") - sys.exit() - - print("Activating Prism (~20 seconds)") - cls.p = subprocess.Popen([ - prism_cmd, "run", "-s", - "https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/" - "oai_stoplight.json"], stdout=cls.devnull, stderr=subprocess.STDOUT) - time.sleep(15) - print("Prism Started") - - def test_apikey_init(self): - self.assertEqual(self.sg.apikey, os.environ.get('SENDGRID_API_KEY')) - # Support the previous naming convention for API keys - self.assertEqual(self.sg.api_key, self.sg.apikey) - my_sendgrid = sendgrid.SendGridAPIClient(apikey="THISISMYKEY") - self.assertEqual(my_sendgrid.apikey, "THISISMYKEY") - - def test_apikey_setter(self): - sg_apikey_setter = sendgrid.SendGridAPIClient(apikey="THISISMYKEY") - self.assertEqual(sg_apikey_setter.apikey, "THISISMYKEY") - # Use apikey setter to change api key - sg_apikey_setter.apikey = "THISISMYNEWAPIKEY" - self.assertEqual(sg_apikey_setter.apikey, "THISISMYNEWAPIKEY") + def test_api_key_init(self): + self.assertEqual(self.sg.api_key, os.environ.get('SENDGRID_API_KEY')) + my_sendgrid = sendgrid.SendGridAPIClient(api_key="THISISMYKEY") + self.assertEqual(my_sendgrid.api_key, "THISISMYKEY") def test_api_key_setter(self): - sg_api_key_setter = sendgrid.SendGridAPIClient(apikey="THISISMYKEY") - self.assertEqual(sg_api_key_setter.apikey, "THISISMYKEY") + sg_api_key_setter = sendgrid.SendGridAPIClient(api_key="THISISMYKEY") + self.assertEqual(sg_api_key_setter.api_key, "THISISMYKEY") # Use api_key setter to change api key - sg_api_key_setter.api_key = "THISISMYNEWAPI_KEY" - self.assertEqual(sg_api_key_setter.apikey, "THISISMYNEWAPI_KEY") + sg_api_key_setter.api_key = "THISISMYNEWAPIKEY" + self.assertEqual(sg_api_key_setter.api_key, "THISISMYNEWAPIKEY") def test_impersonate_subuser_init(self): temp_subuser = 'abcxyz@this.is.a.test.subuser' sg_impersonate = sendgrid.SendGridAPIClient( - host=host, path=self.path, - api_key=os.environ.get('SENDGRID_API_KEY'), impersonate_subuser=temp_subuser) self.assertEqual(sg_impersonate.impersonate_subuser, temp_subuser) def test_useragent(self): - useragent = '{0}{1}{2}'.format('sendgrid/', __version__, ';python') + useragent = '{}{}{}'.format('sendgrid/', sendgrid.__version__, ';python') self.assertEqual(self.sg.useragent, useragent) def test_host(self): - self.assertEqual(self.sg.host, self.host) + self.assertEqual(self.sg.host, 'https://api.sendgrid.com') def test_get_default_headers(self): - headers = self.sg._get_default_headers() + headers = self.sg._default_headers self.assertIn('Authorization', headers) - self.assertIn('User-agent', headers) + self.assertIn('User-Agent', headers) self.assertIn('Accept', headers) self.assertNotIn('On-Behalf-Of', headers) - self.sg._impersonate_subuser = 'ladida@testsubuser.sendgrid' - headers = self.sg._get_default_headers() + self.sg.impersonate_subuser = 'ladida@testsubuser.sendgrid' + headers = self.sg._default_headers self.assertIn('Authorization', headers) - self.assertIn('User-agent', headers) + self.assertIn('User-Agent', headers) self.assertIn('Accept', headers) self.assertIn('On-Behalf-Of', headers) @@ -136,17 +68,9 @@ def test_reset_request_headers(self): self.assertNotIn('blah', self.sg.client.request_headers) self.assertNotIn('blah2x', self.sg.client.request_headers) - for k,v in self.sg._get_default_headers().items(): + for k, v in self.sg._default_headers.items(): self.assertEqual(v, self.sg.client.request_headers[k]) - def test_hello_world(self): - from_email = Email("test@example.com") - to_email = Email("test@example.com") - subject = "Sending with SendGrid is Fun" - content = Content("text/plain", "and easy to do anywhere, even with Python") - mail = Mail(from_email, subject, to_email, content) - self.assertTrue(mail.get() == {'content': [{'type': 'text/plain', 'value': 'and easy to do anywhere, even with Python'}], 'personalizations': [{'to': [{'email': 'test@example.com'}]}], 'from': {'email': 'test@example.com'}, 'subject': 'Sending with SendGrid is Fun'}) - def test_access_settings_activity_get(self): params = {'limit': 1} headers = {'X-Mock': 200} @@ -929,6 +853,9 @@ def test_ips_get(self): headers = {'X-Mock': 200} response = self.sg.client.ips.get( query_params=params, request_headers=headers) + data = response.body + unused = unassigned(data) + self.assertEqual(type(unused), list) self.assertEqual(response.status_code, 200) def test_ips_assigned_get(self): @@ -1179,7 +1106,7 @@ def test_mail_send_post(self): "receiving these emails <% clickhere %>.", "substitution_tag": "<%click here%>", "text": "If you would like to unsubscribe and stop " - "receiveing these emails <% click here %>." + "receiving these emails <% click here %>." } } } @@ -1722,14 +1649,14 @@ def test_suppression_invalid_emails__email__delete(self): def test_suppression_spam_report__email__get(self): email = "test_url_param" headers = {'X-Mock': 200} - response = self.sg.client.suppression.spam_report._( + response = self.sg.client.suppression.spam_reports._( email).get(request_headers=headers) self.assertEqual(response.status_code, 200) def test_suppression_spam_report__email__delete(self): email = "test_url_param" headers = {'X-Mock': 204} - response = self.sg.client.suppression.spam_report._( + response = self.sg.client.suppression.spam_reports._( email).delete(request_headers=headers) self.assertEqual(response.status_code, 204) @@ -2228,25 +2155,25 @@ def test_whitelabel_domains__id__ips_post(self): data = { "ip": "192.168.0.1" } - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 200} response = self.sg.client.whitelabel.domains._( - id).ips.post(request_body=data, request_headers=headers) + id_).ips.post(request_body=data, request_headers=headers) self.assertEqual(response.status_code, 200) def test_whitelabel_domains__id__ips__ip__delete(self): - id = "test_url_param" + id_ = "test_url_param" ip = "test_url_param" headers = {'X-Mock': 200} response = self.sg.client.whitelabel.domains._( - id).ips._(ip).delete(request_headers=headers) + id_).ips._(ip).delete(request_headers=headers) self.assertEqual(response.status_code, 200) def test_whitelabel_domains__id__validate_post(self): - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 200} response = self.sg.client.whitelabel.domains._( - id).validate.post(request_headers=headers) + id_).validate.post(request_headers=headers) self.assertEqual(response.status_code, 200) def test_whitelabel_ips_post(self): @@ -2268,24 +2195,24 @@ def test_whitelabel_ips_get(self): self.assertEqual(response.status_code, 200) def test_whitelabel_ips__id__get(self): - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 200} response = self.sg.client.whitelabel.ips._( - id).get(request_headers=headers) + id_).get(request_headers=headers) self.assertEqual(response.status_code, 200) def test_whitelabel_ips__id__delete(self): - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 204} response = self.sg.client.whitelabel.ips._( - id).delete(request_headers=headers) + id_).delete(request_headers=headers) self.assertEqual(response.status_code, 204) def test_whitelabel_ips__id__validate_post(self): - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 200} response = self.sg.client.whitelabel.ips._( - id).validate.post(request_headers=headers) + id_).validate.post(request_headers=headers) self.assertEqual(response.status_code, 200) def test_whitelabel_links_post(self): @@ -2332,31 +2259,31 @@ def test_whitelabel_links__id__patch(self): data = { "default": True } - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 200} - response = self.sg.client.whitelabel.links._(id).patch( + response = self.sg.client.whitelabel.links._(id_).patch( request_body=data, request_headers=headers) self.assertEqual(response.status_code, 200) def test_whitelabel_links__id__get(self): - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 200} response = self.sg.client.whitelabel.links._( - id).get(request_headers=headers) + id_).get(request_headers=headers) self.assertEqual(response.status_code, 200) def test_whitelabel_links__id__delete(self): - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 204} response = self.sg.client.whitelabel.links._( - id).delete(request_headers=headers) + id_).delete(request_headers=headers) self.assertEqual(response.status_code, 204) def test_whitelabel_links__id__validate_post(self): - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 200} response = self.sg.client.whitelabel.links._( - id).validate.post(request_headers=headers) + id_).validate.post(request_headers=headers) self.assertEqual(response.status_code, 200) def test_whitelabel_links__link_id__subuser_post(self): @@ -2370,12 +2297,13 @@ def test_whitelabel_links__link_id__subuser_post(self): self.assertEqual(response.status_code, 200) def test_license_year(self): - LICENSE_FILE = 'LICENSE.txt' + LICENSE_FILE = 'LICENSE' + copyright_line = '' with open(LICENSE_FILE, 'r') as f: - copyright_line = f.readline().rstrip() - self.assertEqual('Copyright (c) 2012-%s SendGrid, Inc.' % datetime.datetime.now().year, copyright_line) - - @classmethod - def tearDownClass(cls): - cls.p.kill() - print("Prism Shut Down") + for line in f: + if line.startswith('Copyright'): + copyright_line = line.strip() + break + self.assertEqual( + 'Copyright (C) %s, Twilio SendGrid, Inc. ' % datetime.datetime.now().year, + copyright_line) diff --git a/test/prism.sh b/test/prism.sh deleted file mode 100755 index 9a37f7299..000000000 --- a/test/prism.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash - -install () { - -set -eu - -UNAME=$(uname) -ARCH=$(uname -m) -if [ "$UNAME" != "Linux" ] && [ "$UNAME" != "Darwin" ] && [ "$ARCH" != "x86_64" ] && [ "$ARCH" != "i686" ]; then - echo "Sorry, OS/Architecture not supported: ${UNAME}/${ARCH}. Download binary from https://github.com/stoplightio/prism/releases" - exit 1 -fi - -if [ "$UNAME" = "Darwin" ] ; then - OSX_ARCH=$(uname -m) - if [ "${OSX_ARCH}" = "x86_64" ] ; then - PLATFORM="darwin_amd64" - fi -elif [ "$UNAME" = "Linux" ] ; then - LINUX_ARCH=$(uname -m) - if [ "${LINUX_ARCH}" = "i686" ] ; then - PLATFORM="linux_386" - elif [ "${LINUX_ARCH}" = "x86_64" ] ; then - PLATFORM="linux_amd64" - fi -fi - -#LATEST=$(curl -s https://api.github.com/repos/stoplightio/prism/tags | grep -Eo '"name":.*?[^\\]",' | head -n 1 | sed 's/[," ]//g' | cut -d ':' -f 2) -LATEST="v0.6.21" -URL="https://github.com/stoplightio/prism/releases/download/$LATEST/prism_$PLATFORM" -DESTDIR=~/bin -DEST=$DESTDIR/prism - -if [ -z $LATEST ] ; then - echo "Error requesting. Download binary from ${URL}" - exit 1 -else - mkdir -p $DESTDIR - curl -L $URL -o $DEST - chmod +x $DEST - export PATH=$PATH:$DESTDIR - prism version -fi -} - -install - -# this is needed for travis internal scripts -set +u \ No newline at end of file diff --git a/test/requirements.txt b/test/requirements.txt new file mode 100644 index 000000000..40552deba --- /dev/null +++ b/test/requirements.txt @@ -0,0 +1,8 @@ +pyyaml +Flask==1.1.4 +six +coverage +mock +itsdangerous==1.1.0 +markupsafe==1.1.1 +setuptools diff --git a/test/test_config.py b/test/test_config.py deleted file mode 100644 index 301bacc92..000000000 --- a/test/test_config.py +++ /dev/null @@ -1,51 +0,0 @@ -import os -import sendgrid.helpers.inbound.config -from sendgrid.helpers.inbound.config import Config -try: - import unittest2 as unittest -except ImportError: - import unittest - - -class UnitTests(unittest.TestCase): - - def setUp(self): - self.config = Config() - - def test_initialization(self): - endpoint = '/inbound' - port = 5000 - debug_mode = True - keys = ['from', - 'attachments', - 'headers', - 'text', - 'envelope', - 'to', - 'html', - 'sender_ip', - 'attachment-info', - 'subject', - 'dkim', - 'SPF', - 'charsets', - 'content-ids', - 'spam_report', - 'spam_score', - 'email'] - host = 'http://127.0.0.1:5000/inbound' - self.assertTrue(debug_mode, self.config.debug_mode) - self.assertTrue(endpoint, self.config.endpoint) - self.assertTrue(host, self.config.host) - self.assertTrue(port, self.config.port) - for key in keys: - self.assertTrue(key in self.config.keys) - - def test_init_environment(self): - config_file = sendgrid.helpers.inbound.config.__file__ - env_file_path = os.path.abspath(os.path.dirname(config_file)) + '/.env' - with open(env_file_path, 'w') as f: - f.write('RANDOM_VARIABLE=RANDOM_VALUE') - Config() - os.remove(env_file_path) - self.assertEqual('RANDOM_VALUE', os.environ['RANDOM_VARIABLE']) diff --git a/test/test_mail.py b/test/test_mail.py deleted file mode 100644 index 8b88f5b14..000000000 --- a/test/test_mail.py +++ /dev/null @@ -1,481 +0,0 @@ -# -*- coding: utf-8 -*- -import json - -from sendgrid.helpers.mail import ( - ASM, - Attachment, - BCCSettings, - BypassListManagement, - Category, - ClickTracking, - Content, - CustomArg, - Email, - FooterSettings, - Ganalytics, - Header, - Mail, - MailSettings, - OpenTracking, - Personalization, - SandBoxMode, - Section, - SpamCheck, - SubscriptionTracking, - Substitution, - TrackingSettings -) - -try: - import unittest2 as unittest -except ImportError: - import unittest - - -class UnitTests(unittest.TestCase): - - def test_helloEmail(self): - self.maxDiff = None - - """Minimum required to send an email""" - mail = Mail() - - mail.from_email = Email("test@example.com") - - mail.subject = "Hello World from the SendGrid Python Library" - - personalization = Personalization() - personalization.add_to(Email("test@example.com")) - mail.add_personalization(personalization) - - mail.add_content(Content("text/plain", "some text here")) - mail.add_content( - Content( - "text/html", - "some text here")) - - self.assertEqual( - json.dumps( - mail.get(), - sort_keys=True), - '{"content": [{"type": "text/plain", "value": "some text here"}, ' - '{"type": "text/html", ' - '"value": "some text here"}], ' - '"from": {"email": "test@example.com"}, "personalizations": ' - '[{"to": [{"email": "test@example.com"}]}], ' - '"subject": "Hello World from the SendGrid Python Library"}' - ) - - self.assertTrue(isinstance(str(mail), str)) - - def test_kitchenSink(self): - self.maxDiff = None - - """All settings set""" - mail = Mail() - - mail.from_email = Email("test@example.com", "Example User") - - mail.subject = "Hello World from the SendGrid Python Library" - - personalization = Personalization() - personalization.add_to(Email("test@example.com", "Example User")) - personalization.add_to(Email("test@example.com", "Example User")) - personalization.add_cc(Email("test@example.com", "Example User")) - personalization.add_cc(Email("test@example.com", "Example User")) - personalization.add_bcc(Email("test@example.com")) - personalization.add_bcc(Email("test@example.com")) - personalization.subject = "Hello World from the Personalized SendGrid Python Library" - personalization.add_header(Header("X-Test", "test")) - personalization.add_header(Header("X-Mock", "true")) - personalization.add_substitution( - Substitution("%name%", "Example User")) - personalization.add_substitution(Substitution("%city%", "Denver")) - personalization.add_custom_arg(CustomArg("user_id", "343")) - personalization.add_custom_arg(CustomArg("type", "marketing")) - personalization.send_at = 1443636843 - mail.add_personalization(personalization) - - personalization2 = Personalization() - personalization2.add_to(Email("test@example.com", "Example User")) - personalization2.add_to(Email("test@example.com", "Example User")) - personalization2.add_cc(Email("test@example.com", "Example User")) - personalization2.add_cc(Email("test@example.com", "Example User")) - personalization2.add_bcc(Email("test@example.com")) - personalization2.add_bcc(Email("test@example.com")) - personalization2.subject = "Hello World from the Personalized SendGrid Python Library" - personalization2.add_header(Header("X-Test", "test")) - personalization2.add_header(Header("X-Mock", "true")) - personalization2.add_substitution( - Substitution("%name%", "Example User")) - personalization2.add_substitution(Substitution("%city%", "Denver")) - personalization2.add_custom_arg(CustomArg("user_id", "343")) - personalization2.add_custom_arg(CustomArg("type", "marketing")) - personalization2.send_at = 1443636843 - mail.add_personalization(personalization2) - - mail.add_content(Content("text/plain", "some text here")) - mail.add_content( - Content( - "text/html", - "some text here")) - - attachment = Attachment() - attachment.content = "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12" - attachment.type = "application/pdf" - attachment.filename = "balance_001.pdf" - attachment.disposition = "attachment" - attachment.content_id = "Balance Sheet" - mail.add_attachment(attachment) - - attachment2 = Attachment() - attachment2.content = "BwdW" - attachment2.type = "image/png" - attachment2.filename = "banner.png" - attachment2.disposition = "inline" - attachment2.content_id = "Banner" - mail.add_attachment(attachment2) - - mail.template_id = "13b8f94f-bcae-4ec6-b752-70d6cb59f932" - - mail.add_section( - Section( - "%section1%", - "Substitution Text for Section 1")) - mail.add_section( - Section( - "%section2%", - "Substitution Text for Section 2")) - - mail.add_header(Header("X-Test1", "test1")) - mail.add_header(Header("X-Test3", "test2")) - - mail.add_header({"X-Test4": "test4"}) - - mail.add_category(Category("May")) - mail.add_category(Category("2016")) - - mail.add_custom_arg(CustomArg("campaign", "welcome")) - mail.add_custom_arg(CustomArg("weekday", "morning")) - - mail.send_at = 1443636842 - - mail.batch_id = "sendgrid_batch_id" - - mail.asm = ASM(99, [4, 5, 6, 7, 8]) - - mail.ip_pool_name = "24" - - mail_settings = MailSettings() - mail_settings.bcc_settings = BCCSettings( - True, Email("test@example.com")) - mail_settings.bypass_list_management = BypassListManagement(True) - mail_settings.footer_settings = FooterSettings( - True, - "Footer Text", - "Footer Text") - mail_settings.sandbox_mode = SandBoxMode(True) - mail_settings.spam_check = SpamCheck( - True, 1, "https://spamcatcher.sendgrid.com") - mail.mail_settings = mail_settings - - tracking_settings = TrackingSettings() - tracking_settings.click_tracking = ClickTracking( - True, True) - tracking_settings.open_tracking = OpenTracking( - True, - "Optional tag to replace with the open image in the body of the message") - tracking_settings.subscription_tracking = SubscriptionTracking( - True, - "text to insert into the text/plain portion of the message", - "html to insert into the text/html portion of the message", - "Optional tag to replace with the open image in the body of the message") - tracking_settings.ganalytics = Ganalytics( - True, - "some source", - "some medium", - "some term", - "some content", - "some campaign") - mail.tracking_settings = tracking_settings - - mail.reply_to = Email("test@example.com") - - expected_result = { - "asm": { - "group_id": 99, - "groups_to_display": [4, 5, 6, 7, 8] - }, - "attachments": [ - { - "content": "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3" - "RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12", - "content_id": "Balance Sheet", - "disposition": "attachment", - "filename": "balance_001.pdf", - "type": "application/pdf" - }, - { - "content": "BwdW", - "content_id": "Banner", - "disposition": "inline", - "filename": "banner.png", - "type": "image/png" - } - ], - "batch_id": "sendgrid_batch_id", - "categories": [ - "May", - "2016" - ], - "content": [ - { - "type": "text/plain", - "value": "some text here" - }, - { - "type": "text/html", - "value": "some text here" - } - ], - "custom_args": { - "campaign": "welcome", - "weekday": "morning" - }, - "from": { - "email": "test@example.com", - "name": "Example User" - }, - "headers": { - "X-Test1": "test1", - "X-Test3": "test2", - "X-Test4": "test4" - }, - "ip_pool_name": "24", - "mail_settings": { - "bcc": { - "email": "test@example.com", - "enable": True - }, - "bypass_list_management": { - "enable": True - }, - "footer": { - "enable": True, - "html": "Footer Text", - "text": "Footer Text" - }, - "sandbox_mode": { - "enable": True - }, - "spam_check": { - "enable": True, - "post_to_url": "https://spamcatcher.sendgrid.com", - "threshold": 1 - } - }, - "personalizations": [ - { - "bcc": [ - { - "email": "test@example.com" - }, - { - "email": "test@example.com" - } - ], - "cc": [ - { - "email": "test@example.com", - "name": "Example User" - }, - { - "email": "test@example.com", - "name": "Example User" - } - ], - "custom_args": { - "type": "marketing", - "user_id": "343" - }, - "headers": { - "X-Mock": "true", - "X-Test": "test" - }, - "send_at": 1443636843, - "subject": "Hello World from the Personalized SendGrid " - "Python Library", - "substitutions": { - "%city%": "Denver", - "%name%": "Example User" - }, - "to": [ - { - "email": "test@example.com", - "name": "Example User" - }, - { - "email": "test@example.com", - "name": "Example User" - } - ] - }, - { - "bcc": [ - { - "email": "test@example.com" - }, - { - "email": "test@example.com" - } - ], - "cc": [ - { - "email": "test@example.com", - "name": "Example User" - }, - { - "email": "test@example.com", - "name": "Example User" - } - ], - "custom_args": { - "type": "marketing", - "user_id": "343" - }, - "headers": { - "X-Mock": "true", - "X-Test": "test" - }, - "send_at": 1443636843, - "subject": "Hello World from the Personalized SendGrid " - "Python Library", - "substitutions": { - "%city%": "Denver", - "%name%": "Example User" - }, - "to": [ - { - "email": "test@example.com", - "name": "Example User" - }, - { - "email": "test@example.com", - "name": "Example User" - } - ] - } - ], - "reply_to": { - "email": "test@example.com" - }, - "sections": { - "%section1%": "Substitution Text for Section 1", - "%section2%": "Substitution Text for Section 2" - }, - "send_at": 1443636842, - "subject": "Hello World from the SendGrid Python Library", - "template_id": "13b8f94f-bcae-4ec6-b752-70d6cb59f932", - "tracking_settings": { - "click_tracking": { - "enable": True, - "enable_text": True - }, - "ganalytics": { - "enable": True, - "utm_campaign": "some campaign", - "utm_content": "some content", - "utm_medium": "some medium", - "utm_source": "some source", - "utm_term": "some term" - }, - "open_tracking": { - "enable": True, - "substitution_tag": "Optional tag to replace with the " - "open image in the body of the message" - }, - "subscription_tracking": { - "enable": True, - "html": "html to insert into the text/html " - "portion of the message", - "substitution_tag": "Optional tag to replace with the open" - " image in the body of the message", - "text": "text to insert into the text/plain portion of" - " the message" - } - } - } - self.assertEqual( - json.dumps(mail.get(), sort_keys=True), - json.dumps(expected_result, sort_keys=True) - ) - - def test_unicode_values_in_substitutions_helper(self): - - """ Test that the Substitutions helper accepts unicode values """ - - self.maxDiff = None - - """Minimum required to send an email""" - mail = Mail() - - mail.from_email = Email("test@example.com") - - mail.subject = "Testing unicode substitutions with the SendGrid Python Library" - - personalization = Personalization() - personalization.add_to(Email("test@example.com")) - personalization.add_substitution(Substitution("%city%", u"Αθήνα")) - mail.add_personalization(personalization) - - mail.add_content(Content("text/plain", "some text here")) - mail.add_content( - Content( - "text/html", - "some text here")) - - expected_result = { - "content": [ - { - "type": "text/plain", - "value": "some text here" - }, - { - "type": "text/html", - "value": "some text here" - } - ], - "from": { - "email": "test@example.com" - }, - "personalizations": [ - { - "substitutions": { - "%city%": u"Αθήνα" - }, - "to": [ - { - "email": "test@example.com" - } - ] - } - ], - "subject": "Testing unicode substitutions with the SendGrid Python Library", - } - - self.assertEqual( - json.dumps(mail.get(), sort_keys=True), - json.dumps(expected_result, sort_keys=True) - ) - - def test_asm_display_group_limit(self): - self.assertRaises(ValueError, ASM, 1, list(range(26))) - - def test_disable_tracking(self): - tracking_settings = TrackingSettings() - tracking_settings.click_tracking = ClickTracking(False, False) - - self.assertEqual( - tracking_settings.get(), - {'click_tracking': {'enable': False, 'enable_text': False}} - ) diff --git a/test/test_project.py b/test/test_project.py deleted file mode 100644 index 861f1ffe0..000000000 --- a/test/test_project.py +++ /dev/null @@ -1,75 +0,0 @@ -import os - -try: - import unittest2 as unittest -except ImportError: - import unittest - -class ProjectTests(unittest.TestCase): - - # ./Docker or docker/Docker - def test_docker_dir(self): - self.assertTrue(os.path.isdir("./docker/Dockerfile")) - - # ./docker-compose.yml or ./docker/docker-compose.yml - def test_docker_compose(self): - self.assertTrue(os.path.isfile('docker-compose.yml')) - - # ./.env_sample - def test_env(self): - self.assertTrue(os.path.isfile('./env_sample')) - - # ./.gitignore - def test_gitignore(self): - self.assertTrue(os.path.isfile('./.gitignore')) - - # ./.travis.yml - def test_travis(self): - self.assertTrue(os.path.isfile('./.travis.yml')) - - # ./.codeclimate.yml - def test_codeclimate(self): - self.assertTrue(os.path.isfile('./.codeclimate.yml')) - - # ./CHANGELOG.md - def test_changelog(self): - self.assertTrue(os.path.isfile('./CHANGELOG.md')) - - # ./CODE_OF_CONDUCT.md - def test_code_of_conduct(self): - self.assertTrue(os.path.isfile('./CODE_OF_CONDUCT.md')) - - # ./CONTRIBUTING.md - def test_contributing(self): - self.assertTrue(os.path.isfile('./CONTRIBUTING.md')) - - # ./.github/ISSUE_TEMPLATE - def test_issue_template(self): - self.assertTrue(os.path.isfile('./.github/ISSUE_TEMPLATE')) - - # ./LICENSE.md - def test_license(self): - self.assertTrue(os.path.isfile('./LICENSE.txt')) - - # ./.github/PULL_REQUEST_TEMPLATE - def test_pr_template(self): - self.assertTrue(os.path.isfile('./.github/PULL_REQUEST_TEMPLATE')) - - # ./README.md - def test_readme(self): - self.assertTrue(os.path.isfile('./README.md')) - - # ./TROUBLESHOOTING.md - def test_troubleshooting(self): - self.assertTrue(os.path.isfile('./TROUBLESHOOTING.md')) - - # ./USAGE.md - def test_usage(self): - self.assertTrue(os.path.isfile('./USAGE.md')) - - # ./USE_CASES.md - def test_use_cases(self): - self.assertTrue(os.path.isfile('./USE_CASES.md')) - -if __name__ == '__main__': - unittest.main() diff --git a/test/unit/__init__.py b/test/unit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/test_app.py b/test/unit/test_app.py similarity index 80% rename from test/test_app.py rename to test/unit/test_app.py index 1a8e4a698..56027d570 100644 --- a/test/test_app.py +++ b/test/unit/test_app.py @@ -1,13 +1,9 @@ import os +import unittest from sendgrid.helpers.inbound.config import Config from sendgrid.helpers.inbound.app import app -try: - import unittest2 as unittest -except ImportError: - import unittest - class UnitTests(unittest.TestCase): @@ -23,4 +19,4 @@ def test_up_and_running(self): def test_used_port_true(self): if self.config.debug_mode: port = int(os.environ.get("PORT", self.config.port)) - self.assertEqual(port, self.config.port) \ No newline at end of file + self.assertEqual(port, self.config.port) diff --git a/test/unit/test_config.py b/test/unit/test_config.py new file mode 100644 index 000000000..f60306a5e --- /dev/null +++ b/test/unit/test_config.py @@ -0,0 +1,60 @@ +import os +import unittest + +import sendgrid.helpers.inbound.config +from sendgrid.helpers.inbound.config import Config + + +class UnitTests(unittest.TestCase): + + def setUp(self): + self.config = Config() + + def test_initialization(self): + endpoint = '/inbound' + port = 5000 + keys = [ + 'from', + 'attachments', + 'headers', + 'text', + 'envelope', + 'to', + 'html', + 'sender_ip', + 'attachment-info', + 'subject', + 'dkim', + 'SPF', + 'charsets', + 'content-ids', + 'spam_report', + 'spam_score', + 'email', + ] + host = 'http://127.0.0.1:5000/inbound' + + self.assertTrue(self.config.debug_mode) + self.assertEqual(self.config.endpoint, endpoint) + self.assertEqual(self.config.host, host) + self.assertEqual(self.config.port, port) + for key in keys: + self.assertIn(key, self.config.keys) + + def test_init_environment_should_set_env_from_dotenv(self): + config_file = sendgrid.helpers.inbound.config.__file__ + env_file_path = '{0}/.env'.format(os.path.abspath(os.path.dirname(config_file))) + with open(env_file_path, 'w') as f: + f.write('RANDOM_VARIABLE=RANDOM_VALUE') + Config() + os.remove(env_file_path) + self.assertEqual(os.environ['RANDOM_VARIABLE'], 'RANDOM_VALUE') + + def test_init_environment_should_not_set_env_if_format_is_invalid(self): + config_file = sendgrid.helpers.inbound.config.__file__ + env_file_path = os.path.abspath(os.path.dirname(config_file)) + '/.env' + with open(env_file_path, 'w') as f: + f.write('RANDOM_VARIABLE=RANDOM_VALUE=ANOTHER_RANDOM_VALUE') + Config() + os.remove(env_file_path) + self.assertIsNone(os.environ.get('RANDOM_VARIABLE')) diff --git a/test/test_email.py b/test/unit/test_email.py similarity index 72% rename from test/test_email.py rename to test/unit/test_email.py index 92ae10aaa..9db060705 100644 --- a/test/test_email.py +++ b/test/unit/test_email.py @@ -1,27 +1,29 @@ # -*- coding: utf-8 -*- -import json +import unittest from sendgrid.helpers.mail import (Email) -try: - import unittest2 as unittest -except ImportError: - import unittest - class TestEmailObject(unittest.TestCase): + def test_add_email_address(self): address = "test@example.com" email = Email(address) self.assertEqual(email.email, "test@example.com") - + def test_add_name(self): name = "SomeName" email = Email(name=name) self.assertEqual(email.name, name) - + + def test_add_unicode_name(self): + name = u"SomeName" + email = Email(name=name) + + self.assertEqual(email.name, name) + def test_add_name_email(self): name = "SomeName" address = "test@example.com" @@ -29,6 +31,13 @@ def test_add_name_email(self): self.assertEqual(email.name, name) self.assertEqual(email.email, "test@example.com") + def test_add_unicode_name_email(self): + name = u"SomeName" + address = u"test@example.com" + email = Email(email=address, name=name) + self.assertEqual(email.name, name) + self.assertEqual(email.email, u"test@example.com") + def test_add_rfc_function_finds_name_not_email(self): name = "SomeName" email = Email(name) @@ -39,11 +48,11 @@ def test_add_rfc_function_finds_name_not_email(self): def test_add_rfc_email(self): name = "SomeName" address = "test@example.com" - name_address = "{0} <{1}>".format(name, address) + name_address = "{} <{}>".format(name, address) email = Email(name_address) self.assertEqual(email.name, name) self.assertEqual(email.email, "test@example.com") - + def test_empty_obj_add_name(self): email = Email() name = "SomeName" @@ -56,4 +65,4 @@ def test_empty_obj_add_email(self): address = "test@example.com" email.email = address - self.assertEqual(email.email, address) \ No newline at end of file + self.assertEqual(email.email, address) diff --git a/test/unit/test_eventwebhook.py b/test/unit/test_eventwebhook.py new file mode 100644 index 000000000..eee1eabf9 --- /dev/null +++ b/test/unit/test_eventwebhook.py @@ -0,0 +1,50 @@ +import json +import unittest + +from sendgrid import EventWebhook + + +class UnitTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.PUBLIC_KEY = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE83T4O/n84iotIvIW4mdBgQ/7dAfSmpqIM8kF9mN1flpVKS3GRqe62gw+2fNNRaINXvVpiglSI8eNEc6wEA3F+g==' + cls.SIGNATURE = 'MEUCIGHQVtGj+Y3LkG9fLcxf3qfI10QysgDWmMOVmxG0u6ZUAiEAyBiXDWzM+uOe5W0JuG+luQAbPIqHh89M15TluLtEZtM=' + cls.TIMESTAMP = '1600112502' + cls.PAYLOAD = json.dumps( + [ + { + 'email': 'hello@world.com', + 'event': 'dropped', + 'reason': 'Bounced Address', + 'sg_event_id': 'ZHJvcC0xMDk5NDkxOS1MUnpYbF9OSFN0T0doUTRrb2ZTbV9BLTA', + 'sg_message_id': 'LRzXl_NHStOGhQ4kofSm_A.filterdrecv-p3mdw1-756b745b58-kmzbl-18-5F5FC76C-9.0', + 'smtp-id': '', + 'timestamp': 1600112492, + } + ], sort_keys=True, separators=(',', ':') + ) + '\r\n' # Be sure to include the trailing carriage return and newline! + + def test_verify_valid_signature(self): + ew = EventWebhook() + key = ew.convert_public_key_to_ecdsa(self.PUBLIC_KEY) + self.assertTrue(ew.verify_signature(self.PAYLOAD, self.SIGNATURE, self.TIMESTAMP, key)) + + def test_verify_bad_key(self): + ew = EventWebhook('MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqTxd43gyp8IOEto2LdIfjRQrIbsd4SXZkLW6jDutdhXSJCWHw8REntlo7aNDthvj+y7GjUuFDb/R1NGe1OPzpA==') + self.assertFalse(ew.verify_signature(self.PAYLOAD, self.SIGNATURE, self.TIMESTAMP)) + + def test_verify_bad_payload(self): + ew = EventWebhook(self.PUBLIC_KEY) + self.assertFalse(ew.verify_signature('payload', self.SIGNATURE, self.TIMESTAMP)) + + def test_verify_bad_signature(self): + ew = EventWebhook(self.PUBLIC_KEY) + self.assertFalse(ew.verify_signature( + self.PAYLOAD, + 'MEUCIQCtIHJeH93Y+qpYeWrySphQgpNGNr/U+UyUlBkU6n7RAwIgJTz2C+8a8xonZGi6BpSzoQsbVRamr2nlxFDWYNH3j/0=', + self.TIMESTAMP + )) + + def test_verify_bad_timestamp(self): + ew = EventWebhook(self.PUBLIC_KEY) + self.assertFalse(ew.verify_signature(self.PAYLOAD, self.SIGNATURE, 'timestamp')) diff --git a/test/test_send.py b/test/unit/test_inbound_send.py similarity index 57% rename from test/test_send.py rename to test/unit/test_inbound_send.py index 16d496b85..19ee5de1d 100644 --- a/test/test_send.py +++ b/test/unit/test_inbound_send.py @@ -1,10 +1,7 @@ import argparse -from sendgrid.helpers.inbound import send +import unittest -try: - import unittest2 as unittest -except ImportError: - import unittest +from sendgrid.helpers.inbound import send try: import unittest.mock as mock @@ -30,13 +27,23 @@ def test_send(self): x = send.Send(fake_url) x.test_payload(fake_url) - send.Client.assert_called_once_with(host=fake_url, request_headers={'User-Agent': 'SendGrid-Test', - 'Content-Type': 'multipart/form-data; boundary=xYzZY'}) + send.Client.assert_called_once_with( + host=fake_url, + request_headers={ + 'User-Agent': 'SendGrid-Test', + 'Content-Type': 'multipart/form-data; boundary=xYzZY' + }) def test_main_call(self): fake_url = 'https://fake_url' - with mock.patch('argparse.ArgumentParser.parse_args', return_value=argparse.Namespace(host=fake_url, data='test_file.txt')): + with mock.patch( + 'argparse.ArgumentParser.parse_args', + return_value=argparse.Namespace( + host=fake_url, data='test_file.txt')): send.main() - send.Client.assert_called_once_with(host=fake_url, request_headers={'User-Agent': 'SendGrid-Test', - 'Content-Type': 'multipart/form-data; boundary=xYzZY'}) + send.Client.assert_called_once_with( + host=fake_url, + request_headers={ + 'User-Agent': 'SendGrid-Test', + 'Content-Type': 'multipart/form-data; boundary=xYzZY'}) diff --git a/test/unit/test_mail_helpers.py b/test/unit/test_mail_helpers.py new file mode 100644 index 000000000..c00307d46 --- /dev/null +++ b/test/unit/test_mail_helpers.py @@ -0,0 +1,1766 @@ +# -*- coding: utf-8 -*- +import json +import unittest + +try: + from email.message import EmailMessage +except ImportError: + # Python2 + from email import message + EmailMessage = message.Message + +from sendgrid.helpers.mail import ( + Asm, Attachment, + ClickTracking, Content, + DynamicTemplateData, Email, From, + Mail, Personalization, + Subject, Substitution, To, Cc, Bcc, TrackingSettings +) + +# The below amp html email content is taken from [Google AMP Hello World Email](https://amp.dev/documentation/examples/introduction/hello_world_email/) +amp_html_content = '''

Hello!

''' + +response_content_with_all_three_mime_contents = json.dumps({ + "content": [ + { + "type": "text/plain", + "value": "and easy to do anywhere, even with Python" + }, + { + "type": "text/x-amp-html", + "value": amp_html_content + }, + { + "type": "text/html", + "value": "and easy to do anywhere, even with Python" + } + ], + "from": { + "email": "test+from@example.com", + "name": "Example From Name" + }, + "personalizations": [ + { + "to": [ + { + "email": "test+to@example.com", + "name": "Example To Name" + } + ] + } + ], + "subject": "Sending with SendGrid is Fun" +}) + +class UnitTests(unittest.TestCase): + + def test_asm(self): + from sendgrid.helpers.mail import (GroupId, GroupsToDisplay) + asm1 = Asm(GroupId(1), GroupsToDisplay([1, 2, 3])) + asm2 = Asm(1, [1, 2, 3]) + self.assertEqual( + asm1.group_id.get(), asm2.group_id.get()) + self.assertEqual( + asm1.groups_to_display.get(), asm2.groups_to_display.get()) + + def test_attachment(self): + from sendgrid.helpers.mail import (FileContent, FileType, FileName, + Disposition, ContentId) + a1 = Attachment( + FileContent('Base64EncodedString'), + FileName('example.pdf'), + FileType('application/pdf'), + Disposition('attachment'), + ContentId('123') + ) + a2 = Attachment( + 'Base64EncodedString', + 'example.pdf', + 'application/pdf', + 'attachment', + '123' + ) + self.assertEqual(a1.file_content.get(), a2.file_content.get()) + self.assertEqual(a1.file_name.get(), a2.file_name.get()) + self.assertEqual(a1.file_type.get(), a2.file_type.get()) + self.assertEqual(a1.disposition.get(), a2.disposition.get()) + self.assertEqual(a1.content_id.get(), a2.content_id.get()) + + def test_batch_id(self): + from sendgrid.helpers.mail import BatchId + + b1 = BatchId('1') + self.assertEqual('1', b1.get()) + + # Send a Single Email to a Single Recipient + def test_single_email_to_a_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + + self.assertEqual( + message.get(), + json.loads(r'''{ + "content": [ + { + "type": "text/plain", + "value": "and easy to do anywhere, even with Python" + }, + { + "type": "text/html", + "value": "and easy to do anywhere, even with Python" + } + ], + "from": { + "email": "test+from@example.com", + "name": "Example From Name" + }, + "personalizations": [ + { + "to": [ + { + "email": "test+to@example.com", + "name": "Example To Name" + } + ] + } + ], + "subject": "Sending with SendGrid is Fun" + }''') + ) + + def test_single_email_to_a_single_recipient_content_reversed(self): + """Tests bug found in Issue-451 with Content ordering causing a crash + """ + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent) + self.maxDiff = None + message = Mail() + message.from_email = From('test+from@example.com', 'Example From Name') + message.to = To('test+to@example.com', 'Example To Name') + message.subject = Subject('Sending with SendGrid is Fun') + message.content = HtmlContent( + 'and easy to do anywhere, even with Python') + message.content = PlainTextContent( + 'and easy to do anywhere, even with Python') + + self.assertEqual( + message.get(), + json.loads(r'''{ + "content": [ + { + "type": "text/plain", + "value": "and easy to do anywhere, even with Python" + }, + { + "type": "text/html", + "value": "and easy to do anywhere, even with Python" + } + ], + "from": { + "email": "test+from@example.com", + "name": "Example From Name" + }, + "personalizations": [ + { + "to": [ + { + "email": "test+to@example.com", + "name": "Example To Name" + } + ] + } + ], + "subject": "Sending with SendGrid is Fun" + }''') + ) + + def test_send_a_single_email_to_multiple_recipients(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = [ + To('test+to0@example.com', 'Example To Name 0'), + To('test+to1@example.com', 'Example To Name 1') + ] + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + + self.assertEqual( + message.get(), + json.loads(r'''{ + "content": [ + { + "type": "text/plain", + "value": "and easy to do anywhere, even with Python" + }, + { + "type": "text/html", + "value": "and easy to do anywhere, even with Python" + } + ], + "from": { + "email": "test+from@example.com", + "name": "Example From Name" + }, + "personalizations": [ + { + "to": [ + { + "email": "test+to0@example.com", + "name": "Example To Name 0" + }, + { + "email": "test+to1@example.com", + "name": "Example To Name 1" + } + ] + } + ], + "subject": "Sending with SendGrid is Fun" + }''') + ) + + def test_send_a_single_email_with_multiple_reply_to_addresses(self): + from sendgrid.helpers.mail import (Mail, From, ReplyTo, To, Subject, + PlainTextContent, HtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to0@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), + html_content=HtmlContent('and easy to do anywhere, even with Python')) + + message.reply_to_list = [ReplyTo(email = 'test+reply_to_1@example.com'), ReplyTo(email = 'test+reply_to_2@example.com')] + + self.assertEqual( + message.get(), + json.loads(r'''{ + "content": [ + { + "type": "text/plain", + "value": "and easy to do anywhere, even with Python" + }, + { + "type": "text/html", + "value": "and easy to do anywhere, even with Python" + } + ], + "from": { + "email": "test+from@example.com", + "name": "Example From Name" + }, + "personalizations": [ + { + "to": [ + { + "email": "test+to0@example.com", + "name": "Example To Name" + } + ] + } + ], + "reply_to_list": [ + { + "email": "test+reply_to_1@example.com" + }, + { + "email": "test+reply_to_2@example.com" + } + ], + "subject": "Sending with SendGrid is Fun" + }''') + ) + + def test_multiple_emails_to_multiple_recipients(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, + Substitution) + self.maxDiff = None + + to_emails = [ + To(email='test+to0@example.com', + name='Example Name 0', + substitutions=[ + Substitution('-name-', 'Example Name Substitution 0'), + Substitution('-github-', 'https://example.com/test0'), + ], + subject=Subject('Override Global Subject')), + To(email='test+to1@example.com', + name='Example Name 1', + substitutions=[ + Substitution('-name-', 'Example Name Substitution 1'), + Substitution('-github-', 'https://example.com/test1'), + ]) + ] + global_substitutions = Substitution('-time-', '2019-01-01 00:00:00') + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Hi -name-'), + plain_text_content=PlainTextContent( + 'Hello -name-, your URL is -github-, email sent at -time-'), + html_content=HtmlContent( + 'Hello -name-, your URL is here email sent at -time-'), + global_substitutions=global_substitutions, + is_multiple=True) + + self.assertEqual( + message.get(), + json.loads(r'''{ + "content": [ + { + "type": "text/plain", + "value": "Hello -name-, your URL is -github-, email sent at -time-" + }, + { + "type": "text/html", + "value": "Hello -name-, your URL is here email sent at -time-" + } + ], + "from": { + "email": "test+from@example.com", + "name": "Example From Name" + }, + "personalizations": [ + { + "substitutions": { + "-github-": "https://example.com/test1", + "-name-": "Example Name Substitution 1", + "-time-": "2019-01-01 00:00:00" + }, + "to": [ + { + "email": "test+to1@example.com", + "name": "Example Name 1" + } + ] + }, + { + "subject": "Override Global Subject", + "substitutions": { + "-github-": "https://example.com/test0", + "-name-": "Example Name Substitution 0", + "-time-": "2019-01-01 00:00:00" + }, + "to": [ + { + "email": "test+to0@example.com", + "name": "Example Name 0" + } + ] + } + ], + "subject": "Hi -name-" + }''') + ) + + def test_single_email_with_all_three_email_contents_to_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + amp_html_content=AmpHtmlContent(amp_html_content), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python') + ) + + self.assertEqual( + message.get(), + json.loads(response_content_with_all_three_mime_contents) + ) + + def test_single_email_with_amp_and_html_contents_to_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun'), + amp_html_content=AmpHtmlContent(amp_html_content), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python') + ) + + response_content = json.dumps({ + "content": [ + { + "type": "text/x-amp-html", + "value": amp_html_content + }, + { + "type": "text/html", + "value": "and easy to do anywhere, even with Python" + } + ], + "from": { + "email": "test+from@example.com", + "name": "Example From Name" + }, + "personalizations": [ + { + "to": [ + { + "email": "test+to@example.com", + "name": "Example To Name" + } + ] + } + ], + "subject": "Sending with SendGrid is Fun" + }) + self.assertEqual( + message.get(), + json.loads(response_content) + ) + + def test_single_email_with_amp_and_plain_contents_to_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + amp_html_content=AmpHtmlContent(amp_html_content) + ) + + response_content = json.dumps({ + "content": [ + { + "type": "text/plain", + "value": "and easy to do anywhere, even with Python" + }, + { + "type": "text/x-amp-html", + "value": amp_html_content + } + ], + "from": { + "email": "test+from@example.com", + "name": "Example From Name" + }, + "personalizations": [ + { + "to": [ + { + "email": "test+to@example.com", + "name": "Example To Name" + } + ] + } + ], + "subject": "Sending with SendGrid is Fun" + }) + self.assertEqual( + message.get(), + json.loads(response_content) + ) + + ## Check ordering of MIME types in different variants - start + def test_single_email_with_all_three_contents_in_collapsed_order_of_plain_amp_html_content_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun') + ) + message.content = PlainTextContent( + 'and easy to do anywhere, even with Python') + message.content = AmpHtmlContent(amp_html_content) + message.content = HtmlContent( + 'and easy to do anywhere, even with Python') + + self.assertEqual( + message.get(), + json.loads(response_content_with_all_three_mime_contents) + ) + + def test_single_email_with_all_three_contents_in_collapsed_order_of_plain_html_amp_content_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun') + ) + message.content = PlainTextContent( + 'and easy to do anywhere, even with Python') + message.content = HtmlContent( + 'and easy to do anywhere, even with Python') + message.content = AmpHtmlContent(amp_html_content) + + self.assertEqual( + message.get(), + json.loads(response_content_with_all_three_mime_contents) + ) + + def test_single_email_with_all_three_contents_in_collapsed_order_of_html_plain_amp_content_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun') + ) + message.content = HtmlContent( + 'and easy to do anywhere, even with Python') + message.content = PlainTextContent( + 'and easy to do anywhere, even with Python') + message.content = AmpHtmlContent(amp_html_content) + + self.assertEqual( + message.get(), + json.loads(response_content_with_all_three_mime_contents) + ) + + def test_single_email_with_all_three_contents_in_collapsed_order_of_html_amp_plain_content_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun') + ) + message.content = HtmlContent( + 'and easy to do anywhere, even with Python') + message.content = AmpHtmlContent(amp_html_content) + message.content = PlainTextContent( + 'and easy to do anywhere, even with Python') + + self.assertEqual( + message.get(), + json.loads(response_content_with_all_three_mime_contents) + ) + + def test_single_email_with_all_three_contents_in_collapsed_order_of_amp_html_plain_content_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun') + ) + message.content = AmpHtmlContent(amp_html_content) + message.content = HtmlContent( + 'and easy to do anywhere, even with Python') + message.content = PlainTextContent( + 'and easy to do anywhere, even with Python') + + self.assertEqual( + message.get(), + json.loads(response_content_with_all_three_mime_contents) + ) + + def test_single_email_with_all_three_contents_in_collapsed_order_of_amp_plain_html_content_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun') + ) + message.content = AmpHtmlContent(amp_html_content) + message.content = PlainTextContent( + 'and easy to do anywhere, even with Python') + message.content = HtmlContent( + 'and easy to do anywhere, even with Python') + + self.assertEqual( + message.get(), + json.loads(response_content_with_all_three_mime_contents) + ) + + ## end + + def test_value_error_is_raised_on_to_emails_set_to_list_of_lists(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = [ + ['test+to0@example.com', 'Example To Name 0'], + ['test+to1@example.com', 'Example To Name 1'] + ] + + with self.assertRaises(ValueError): + Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + + def test_value_error_is_raised_on_to_emails_set_to_reply_to_list_of_strs(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = [ + ('test+to0@example.com', 'Example To Name 0'), + ('test+to1@example.com', 'Example To Name 1') + ] + + mail = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + with self.assertRaises(ValueError): + mail.reply_to_list = ['test+reply_to0@example.com', 'test+reply_to1@example.com'] + + def test_value_error_is_raised_on_to_emails_set_to_reply_to_list_of_tuples(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = [ + ('test+to0@example.com', 'Example To Name 0'), + ('test+to1@example.com', 'Example To Name 1') + ] + + mail = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + with self.assertRaises(ValueError): + mail.reply_to_list = [('test+reply_to@example.com', 'Test Name')] + + def test_error_is_not_raised_on_to_emails_set_to_list_of_tuples(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = [ + ('test+to0@example.com', 'Example To Name 0'), + ('test+to1@example.com', 'Example To Name 1') + ] + + Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + + def test_error_is_not_raised_on_to_emails_set_to_list_of_strs(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = ['test+to0@example.com', 'test+to1@example.com'] + + Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + + def test_error_is_not_raised_on_to_emails_set_to_a_str(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = 'test+to0@example.com' + + Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + + def test_error_is_not_raised_on_to_emails_set_to_a_tuple(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = ('test+to0@example.com', 'Example To Name 0') + + Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + + def test_error_is_not_raised_on_to_emails_includes_bcc_cc(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = [ + To('test+to0@example.com', 'Example To Name 0'), + Bcc('test+bcc@example.com', 'Example Bcc Name 1'), + Cc('test+cc@example.com', 'Example Cc Name 2') + ] + + Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + + def test_personalization_add_email_filters_out_duplicate_to_emails(self): + self.maxDiff = None + + p = Personalization() + to_email = To('test+to0@example.com', 'Example To Name 0') + p.add_email(to_email) + p.add_email(to_email) + + self.assertEqual([to_email.get()], p.tos) + + def test_personalization_add_email_filters_out_duplicate_to_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + to_email = To('test+to0@example.com', 'Example To Name 0') + to_email_with_caps = To('test+TO0@example.com', 'Example To Name 0') + p.add_email(to_email) + p.add_email(to_email_with_caps) + + self.assertEqual([to_email.get()], p.tos) + + def test_personalization_set_from_email(self): + self.maxDiff = None + + p = Personalization() + from_email = From('test+from@example.com', 'Example From') + p.set_from(from_email) + + self.assertEqual(from_email.get(), p.from_email) + + def test_personalization_filters_out_duplicate_cc_emails(self): + self.maxDiff = None + + p = Personalization() + cc_email = Cc('test+cc0@example.com', 'Example Cc Name 0') + p.add_email(cc_email) + p.add_email(cc_email) + + self.assertEqual([cc_email.get()], p.ccs) + + def test_personalization_filters_out_duplicate_cc_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + cc_email = Cc('test+cc0@example.com', 'Example Cc Name 0') + cc_email_with_caps = Cc('test+CC0@example.com', 'Example Cc Name 0') + p.add_email(cc_email) + p.add_email(cc_email_with_caps) + + self.assertEqual([cc_email.get()], p.ccs) + + def test_personalization_filters_out_duplicate_bcc_emails(self): + self.maxDiff = None + + p = Personalization() + bcc_email = Bcc('test+bcc0@example.com', 'Example Bcc Name 0') + p.add_email(bcc_email) + p.add_email(bcc_email) + + self.assertEqual([bcc_email.get()], p.bccs) + + def test_personalization_filters_out_duplicate_bcc_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + bcc_email = Bcc('test+bcc0@example.com', 'Example Bcc Name 0') + bcc_email_with_caps = Bcc('test+BCC0@example.com', 'Example Bcc Name 0') + p.add_email(bcc_email) + p.add_email(bcc_email_with_caps) + + self.assertEqual([bcc_email.get()], p.bccs) + + def test_personalization_tos_setter_filters_out_duplicate_dict_emails(self): + self.maxDiff = None + + p = Personalization() + to_emails = [{ 'email': 'test+to0@example.com', 'name': 'Example To Name 0' }] * 2 + p.tos = to_emails + + self.assertEqual([to_emails[0]], p.tos) + + def test_personalization_tos_setter_filters_out_duplicate_dict_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + to_email = { 'email': 'test+to0@example.com', 'name': 'Example To Name 0' } + to_email_with_caps = { 'email': 'test+TO0@example.com', 'name': 'Example To Name 0' } + to_emails = [to_email, to_email_with_caps] + p.tos = to_emails + + self.assertEqual([to_email], p.tos) + + def test_personalization_tos_setter_filters_out_duplicate_to_emails(self): + self.maxDiff = None + + p = Personalization() + to_emails = [To('test+to0@example.com', 'Example To Name 0')] * 2 + p.tos = to_emails + + self.assertEqual([to_emails[0].get()], p.tos) + + + def test_personalization_tos_setter_filters_out_duplicate_to_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + to_email = To('test+to0@example.com', 'Example To Name 0') + to_email_with_caps = To('test+TO0@example.com', 'Example To Name 0') + to_emails = [to_email, to_email_with_caps] + p.tos = to_emails + + self.assertEqual([to_email.get()], p.tos) + + def test_personalization_ccs_setter_filters_out_duplicate_dict_emails(self): + self.maxDiff = None + + p = Personalization() + cc_emails = [{ 'email': 'test+cc0@example.com', 'name': 'Example Cc Name 0' }] * 2 + p.ccs = cc_emails + + self.assertEqual([cc_emails[0]], p.ccs) + + def test_personalization_ccs_setter_filters_out_duplicate_dict_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + cc_email = { 'email': 'test+cc0@example.com', 'name': 'Example Cc Name 0' } + cc_email_with_caps = { 'email': 'test+CC0@example.com', 'name': 'Example Cc Name 0' } + cc_emails = [cc_email, cc_email_with_caps] + p.ccs = cc_emails + + self.assertEqual([cc_email], p.ccs) + + def test_personalization_ccs_setter_filters_out_duplicate_cc_emails(self): + self.maxDiff = None + + p = Personalization() + cc_emails = [Cc('test+cc0@example.com', 'Example Cc Name 0')] * 2 + p.ccs = cc_emails + + self.assertEqual([cc_emails[0].get()], p.ccs) + + def test_personalization_ccs_setter_filters_out_duplicate_cc_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + cc_email = Cc('test+cc0@example.com', 'Example Cc Name 0') + cc_email_with_caps = Cc('test+CC0@example.com', 'Example Cc Name 0') + p.ccs = [cc_email, cc_email_with_caps] + + self.assertEqual([cc_email.get()], p.ccs) + + def test_personalization_bccs_setter_filters_out_duplicate_dict_emails(self): + self.maxDiff = None + + p = Personalization() + bcc_emails = [{ 'email': 'test+bcc0@example.com', 'name': 'Example Bcc Name 0' }] * 2 + p.bccs = bcc_emails + + self.assertEqual([bcc_emails[0]], p.bccs) + + def test_personalization_bccs_setter_filters_out_duplicate_dict_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + bcc_email = { 'email': 'test+bcc0@example.com', 'name': 'Example Bcc Name 0' } + bcc_email_with_caps = { 'email': 'test+BCC0@example.com', 'name': 'Example Bcc Name 0' } + bcc_emails = [bcc_email, bcc_email_with_caps] + p.bccs = bcc_emails + + self.assertEqual([bcc_email], p.bccs) + + def test_personalization_bccs_setter_filters_out_duplicate_bcc_emails(self): + self.maxDiff = None + + p = Personalization() + bcc_emails = [Bcc('test+bcc0@example.com', 'Example Bcc Name 0')] * 2 + p.bccs = bcc_emails + + self.assertEqual([bcc_emails[0].get()], p.bccs) + + def test_personalization_bccs_setter_filters_out_duplicate_bcc_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + bcc_email = Bcc('test+bcc0@example.com', 'Example Bcc Name 0') + bcc_email_with_caps = Bcc('test+BCC0@example.com', 'Example Bcc Name 0') + p.bccs = [bcc_email, bcc_email_with_caps] + + self.assertEqual([bcc_email.get()], p.bccs) + + def test_personalization_add_to_filters_out_duplicate_to_emails(self): + self.maxDiff = None + + p = Personalization() + to_email = To('test+to0@example.com', 'Example To Name 0') + p.add_to(to_email) + p.add_to(to_email) + + expected = [to_email.get()] + + self.assertEqual(expected, p.tos) + + def test_personalization_add_bcc_filters_out_duplicate_bcc_emails(self): + self.maxDiff = None + + p = Personalization() + bcc_email = Bcc('test+to0@example.com', 'Example To Name 0') + p.add_bcc(bcc_email) + p.add_bcc(bcc_email) + + expected = [bcc_email.get()] + + self.assertEqual(expected, p.bccs) + + def test_personalization_add_cc_filters_out_duplicate_cc_emails(self): + self.maxDiff = None + + p = Personalization() + cc_email = Cc('test+to0@example.com', 'Example To Name 0') + p.add_cc(cc_email) + p.add_cc(cc_email) + + expected = [cc_email.get()] + + self.assertEqual(expected, p.ccs) + + def test_dynamic_template_data(self): + self.maxDiff = None + + to_emails = [ + To(email='test+to+0@example.com', + name='Example To 0 Name', + dynamic_template_data=DynamicTemplateData({'name': 'Example 0 Name'})), + To(email='test+to+1@example.com', + name='Example To 1 Name', + dynamic_template_data={'name': 'Example 1 Name'}) + ] + message = Mail( + from_email=From('test@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Hi!'), + plain_text_content='Hello!', + html_content='Hello!', + is_multiple=True) + + self.assertEqual( + message.get(), + json.loads(r'''{ + "content": [ + { + "type": "text/plain", + "value": "Hello!" + }, + { + "type": "text/html", + "value": "Hello!" + } + ], + "from": { + "email": "test@example.com", + "name": "Example From Name" + }, + "personalizations": [ + { + "dynamic_template_data": { + "name": "Example 1 Name" + }, + "to": [ + { + "email": "test+to+1@example.com", + "name": "Example To 1 Name" + } + ] + }, + { + "dynamic_template_data": { + "name": "Example 0 Name" + }, + "to": [ + { + "email": "test+to+0@example.com", + "name": "Example To 0 Name" + } + ] + } + ], + "subject": "Hi!" + }''') + ) + + def test_kitchen_sink(self): + from sendgrid.helpers.mail import ( + Mail, From, To, Cc, Bcc, Subject, Substitution, Header, + CustomArg, SendAt, Content, MimeType, Attachment, FileName, + FileContent, FileType, Disposition, ContentId, TemplateId, + Section, ReplyTo, Category, BatchId, Asm, GroupId, GroupsToDisplay, + IpPoolName, MailSettings, BccSettings, BccSettingsEmail, + BypassBounceManagement, BypassListManagement, BypassSpamManagement, + BypassUnsubscribeManagement, FooterSettings, FooterText, + FooterHtml, SandBoxMode, SpamCheck, SpamThreshold, SpamUrl, + TrackingSettings, ClickTracking, SubscriptionTracking, + SubscriptionText, SubscriptionHtml, SubscriptionSubstitutionTag, + OpenTracking, OpenTrackingSubstitutionTag, Ganalytics, + UtmSource, UtmMedium, UtmTerm, UtmContent, UtmCampaign) + + self.maxDiff = None + + message = Mail() + + # Define Personalizations + + message.to = To('test1@example.com', 'Example User1', p=0) + message.to = [ + To('test2@example.com', 'Example User2', p=0), + To('test3@example.com', 'Example User3', p=0) + ] + + message.cc = Cc('test4@example.com', 'Example User4', p=0) + message.cc = [ + Cc('test5@example.com', 'Example User5', p=0), + Cc('test6@example.com', 'Example User6', p=0) + ] + + message.bcc = Bcc('test7@example.com', 'Example User7', p=0) + message.bcc = [ + Bcc('test8@example.com', 'Example User8', p=0), + Bcc('test9@example.com', 'Example User9', p=0) + ] + + message.subject = Subject('Sending with SendGrid is Fun 0', p=0) + + message.header = Header('X-Test1', 'Test1', p=0) + message.header = Header('X-Test2', 'Test2', p=0) + message.header = [ + Header('X-Test3', 'Test3', p=0), + Header('X-Test4', 'Test4', p=0) + ] + + message.substitution = Substitution('%name1%', 'Example Name 1', p=0) + message.substitution = Substitution('%city1%', 'Example City 1', p=0) + message.substitution = [ + Substitution('%name2%', 'Example Name 2', p=0), + Substitution('%city2%', 'Example City 2', p=0) + ] + + message.custom_arg = CustomArg('marketing1', 'true', p=0) + message.custom_arg = CustomArg('transactional1', 'false', p=0) + message.custom_arg = [ + CustomArg('marketing2', 'false', p=0), + CustomArg('transactional2', 'true', p=0) + ] + + message.send_at = SendAt(1461775051, p=0) + + message.to = To('test10@example.com', 'Example User10', p=1) + message.to = [ + To('test11@example.com', 'Example User11', p=1), + To('test12@example.com', 'Example User12', p=1) + ] + + message.cc = Cc('test13@example.com', 'Example User13', p=1) + message.cc = [ + Cc('test14@example.com', 'Example User14', p=1), + Cc('test15@example.com', 'Example User15', p=1) + ] + + message.bcc = Bcc('test16@example.com', 'Example User16', p=1) + message.bcc = [ + Bcc('test17@example.com', 'Example User17', p=1), + Bcc('test18@example.com', 'Example User18', p=1) + ] + + message.header = Header('X-Test5', 'Test5', p=1) + message.header = Header('X-Test6', 'Test6', p=1) + message.header = [ + Header('X-Test7', 'Test7', p=1), + Header('X-Test8', 'Test8', p=1) + ] + + message.substitution = Substitution('%name3%', 'Example Name 3', p=1) + message.substitution = Substitution('%city3%', 'Example City 3', p=1) + message.substitution = [ + Substitution('%name4%', 'Example Name 4', p=1), + Substitution('%city4%', 'Example City 4', p=1) + ] + + message.custom_arg = CustomArg('marketing3', 'true', p=1) + message.custom_arg = CustomArg('transactional3', 'false', p=1) + message.custom_arg = [ + CustomArg('marketing4', 'false', p=1), + CustomArg('transactional4', 'true', p=1) + ] + + message.send_at = SendAt(1461775052, p=1) + + message.subject = Subject('Sending with SendGrid is Fun 1', p=1) + + # The values below this comment are global to entire message + + message.from_email = From('help@twilio.com', 'Twilio SendGrid') + + message.reply_to = ReplyTo('help_reply@twilio.com', 'Twilio SendGrid Reply') + + message.subject = Subject('Sending with SendGrid is Fun 2') + + message.content = Content( + MimeType.text, + 'and easy to do anywhere, even with Python') + message.content = Content( + MimeType.html, + 'and easy to do anywhere, even with Python') + message.content = [ + Content('text/calendar', 'Party Time!!'), + Content('text/custom', 'Party Time 2!!') + ] + + message.attachment = Attachment( + FileContent('base64 encoded content 1'), + FileName('balance_001.pdf'), + FileType('application/pdf'), + Disposition('attachment'), + ContentId('Content ID 1')) + message.attachment = [ + Attachment( + FileContent('base64 encoded content 2'), + FileName('banner.png'), + FileType('image/png'), + Disposition('inline'), + ContentId('Content ID 2')), + Attachment( + FileContent('base64 encoded content 3'), + FileName('banner2.png'), + FileType('image/png'), + Disposition('inline'), + ContentId('Content ID 3')) + ] + + message.template_id = TemplateId( + '13b8f94f-bcae-4ec6-b752-70d6cb59f932') + + message.section = Section( + '%section1%', 'Substitution for Section 1 Tag') + message.section = [ + Section('%section2%', 'Substitution for Section 2 Tag'), + Section('%section3%', 'Substitution for Section 3 Tag') + ] + + message.header = Header('X-Test9', 'Test9') + message.header = Header('X-Test10', 'Test10') + message.header = [ + Header('X-Test11', 'Test11'), + Header('X-Test12', 'Test12') + ] + + message.category = Category('Category 1') + message.category = Category('Category 2') + message.category = [ + Category('Category 1'), + Category('Category 2') + ] + + message.custom_arg = CustomArg('marketing5', 'false') + message.custom_arg = CustomArg('transactional5', 'true') + message.custom_arg = [ + CustomArg('marketing6', 'true'), + CustomArg('transactional6', 'false') + ] + + message.send_at = SendAt(1461775053) + + message.batch_id = BatchId("HkJ5yLYULb7Rj8GKSx7u025ouWVlMgAi") + + message.asm = Asm(GroupId(1), GroupsToDisplay([1, 2, 3, 4])) + + message.ip_pool_name = IpPoolName("IP Pool Name") + + mail_settings = MailSettings() + mail_settings.bcc_settings = BccSettings( + False, BccSettingsEmail("bcc@twilio.com")) + mail_settings.bypass_bounce_management = BypassBounceManagement(False) + mail_settings.bypass_list_management = BypassListManagement(False) + mail_settings.bypass_spam_management = BypassSpamManagement(False) + mail_settings.bypass_unsubscribe_management = BypassUnsubscribeManagement(False) + mail_settings.footer_settings = FooterSettings( + True, FooterText("w00t"), FooterHtml("w00t!")) + mail_settings.sandbox_mode = SandBoxMode(True) + mail_settings.spam_check = SpamCheck( + True, SpamThreshold(5), SpamUrl("https://example.com")) + message.mail_settings = mail_settings + + tracking_settings = TrackingSettings() + tracking_settings.click_tracking = ClickTracking(True, False) + tracking_settings.open_tracking = OpenTracking( + True, OpenTrackingSubstitutionTag("open_tracking")) + tracking_settings.subscription_tracking = SubscriptionTracking( + True, + SubscriptionText("Goodbye"), + SubscriptionHtml("Goodbye!"), + SubscriptionSubstitutionTag("unsubscribe")) + tracking_settings.ganalytics = Ganalytics( + True, + UtmSource("utm_source"), + UtmMedium("utm_medium"), + UtmTerm("utm_term"), + UtmContent("utm_content"), + UtmCampaign("utm_campaign")) + message.tracking_settings = tracking_settings + self.assertEqual( + message.get(), + json.loads(r'''{ + "asm": { + "group_id": 1, + "groups_to_display": [ + 1, + 2, + 3, + 4 + ] + }, + "attachments": [ + { + "content": "base64 encoded content 3", + "content_id": "Content ID 3", + "disposition": "inline", + "filename": "banner2.png", + "type": "image/png" + }, + { + "content": "base64 encoded content 2", + "content_id": "Content ID 2", + "disposition": "inline", + "filename": "banner.png", + "type": "image/png" + }, + { + "content": "base64 encoded content 1", + "content_id": "Content ID 1", + "disposition": "attachment", + "filename": "balance_001.pdf", + "type": "application/pdf" + } + ], + "batch_id": "HkJ5yLYULb7Rj8GKSx7u025ouWVlMgAi", + "categories": [ + "Category 2", + "Category 1", + "Category 2", + "Category 1" + ], + "content": [ + { + "type": "text/plain", + "value": "and easy to do anywhere, even with Python" + }, + { + "type": "text/html", + "value": "and easy to do anywhere, even with Python" + }, + { + "type": "text/calendar", + "value": "Party Time!!" + }, + { + "type": "text/custom", + "value": "Party Time 2!!" + } + ], + "custom_args": { + "marketing5": "false", + "marketing6": "true", + "transactional5": "true", + "transactional6": "false" + }, + "from": { + "email": "help@twilio.com", + "name": "Twilio SendGrid" + }, + "headers": { + "X-Test10": "Test10", + "X-Test11": "Test11", + "X-Test12": "Test12", + "X-Test9": "Test9" + }, + "ip_pool_name": "IP Pool Name", + "mail_settings": { + "bcc": { + "email": "bcc@twilio.com", + "enable": false + }, + "bypass_bounce_management": { + "enable": false + }, + "bypass_list_management": { + "enable": false + }, + "bypass_spam_management": { + "enable": false + }, + "bypass_unsubscribe_management": { + "enable": false + }, + "footer": { + "enable": true, + "html": "w00t!", + "text": "w00t" + }, + "sandbox_mode": { + "enable": true + }, + "spam_check": { + "enable": true, + "post_to_url": "https://example.com", + "threshold": 5 + } + }, + "personalizations": [ + { + "bcc": [ + { + "email": "test7@example.com", + "name": "Example User7" + }, + { + "email": "test8@example.com", + "name": "Example User8" + }, + { + "email": "test9@example.com", + "name": "Example User9" + } + ], + "cc": [ + { + "email": "test4@example.com", + "name": "Example User4" + }, + { + "email": "test5@example.com", + "name": "Example User5" + }, + { + "email": "test6@example.com", + "name": "Example User6" + } + ], + "custom_args": { + "marketing1": "true", + "marketing2": "false", + "transactional1": "false", + "transactional2": "true" + }, + "headers": { + "X-Test1": "Test1", + "X-Test2": "Test2", + "X-Test3": "Test3", + "X-Test4": "Test4" + }, + "send_at": 1461775051, + "subject": "Sending with SendGrid is Fun 0", + "substitutions": { + "%city1%": "Example City 1", + "%city2%": "Example City 2", + "%name1%": "Example Name 1", + "%name2%": "Example Name 2" + }, + "to": [ + { + "email": "test1@example.com", + "name": "Example User1" + }, + { + "email": "test2@example.com", + "name": "Example User2" + }, + { + "email": "test3@example.com", + "name": "Example User3" + } + ] + }, + { + "bcc": [ + { + "email": "test16@example.com", + "name": "Example User16" + }, + { + "email": "test17@example.com", + "name": "Example User17" + }, + { + "email": "test18@example.com", + "name": "Example User18" + } + ], + "cc": [ + { + "email": "test13@example.com", + "name": "Example User13" + }, + { + "email": "test14@example.com", + "name": "Example User14" + }, + { + "email": "test15@example.com", + "name": "Example User15" + } + ], + "custom_args": { + "marketing3": "true", + "marketing4": "false", + "transactional3": "false", + "transactional4": "true" + }, + "headers": { + "X-Test5": "Test5", + "X-Test6": "Test6", + "X-Test7": "Test7", + "X-Test8": "Test8" + }, + "send_at": 1461775052, + "subject": "Sending with SendGrid is Fun 1", + "substitutions": { + "%city3%": "Example City 3", + "%city4%": "Example City 4", + "%name3%": "Example Name 3", + "%name4%": "Example Name 4" + }, + "to": [ + { + "email": "test10@example.com", + "name": "Example User10" + }, + { + "email": "test11@example.com", + "name": "Example User11" + }, + { + "email": "test12@example.com", + "name": "Example User12" + } + ] + } + ], + "reply_to": { + "email": "help_reply@twilio.com", + "name": "Twilio SendGrid Reply" + }, + "sections": { + "%section1%": "Substitution for Section 1 Tag", + "%section2%": "Substitution for Section 2 Tag", + "%section3%": "Substitution for Section 3 Tag" + }, + "send_at": 1461775053, + "subject": "Sending with SendGrid is Fun 2", + "template_id": "13b8f94f-bcae-4ec6-b752-70d6cb59f932", + "tracking_settings": { + "click_tracking": { + "enable": true, + "enable_text": false + }, + "ganalytics": { + "enable": true, + "utm_campaign": "utm_campaign", + "utm_content": "utm_content", + "utm_medium": "utm_medium", + "utm_source": "utm_source", + "utm_term": "utm_term" + }, + "open_tracking": { + "enable": true, + "substitution_tag": "open_tracking" + }, + "subscription_tracking": { + "enable": true, + "html": "Goodbye!", + "substitution_tag": "unsubscribe", + "text": "Goodbye" + } + } + }''') + ) + + # Send a Single Email to a Single Recipient with a Dynamic Template + def test_single_email_to_a_single_recipient_with_dynamic_templates(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + message.dynamic_template_data = DynamicTemplateData({ + "total": "$ 239.85", + "items": [ + { + "text": "New Line Sneakers", + "image": "https://marketing-image-production.s3.amazonaws.com/uploads/8dda1131320a6d978b515cc04ed479df259a458d5d45d58b6b381cae0bf9588113e80ef912f69e8c4cc1ef1a0297e8eefdb7b270064cc046b79a44e21b811802.png", + "price": "$ 79.95" + }, + { + "text": "Old Line Sneakers", + "image": "https://marketing-image-production.s3.amazonaws.com/uploads/3629f54390ead663d4eb7c53702e492de63299d7c5f7239efdc693b09b9b28c82c924225dcd8dcb65732d5ca7b7b753c5f17e056405bbd4596e4e63a96ae5018.png", + "price": "$ 79.95" + }, + { + "text": "Blue Line Sneakers", + "image": "https://marketing-image-production.s3.amazonaws.com/uploads/00731ed18eff0ad5da890d876c456c3124a4e44cb48196533e9b95fb2b959b7194c2dc7637b788341d1ff4f88d1dc88e23f7e3704726d313c57f350911dd2bd0.png", + "price": "$ 79.95" + } + ], + "receipt": True, + "name": "Sample Name", + "address01": "1234 Fake St.", + "address02": "Apt. 123", + "city": "Place", + "state": "CO", + "zip": "80202" + }) + self.assertEqual( + message.get(), + json.loads(r'''{ + "content": [ + { + "type": "text/plain", + "value": "and easy to do anywhere, even with Python" + }, + { + "type": "text/html", + "value": "and easy to do anywhere, even with Python" + } + ], + "from": { + "email": "test+from@example.com", + "name": "Example From Name" + }, + "personalizations": [ + { + "dynamic_template_data": { + "address01": "1234 Fake St.", + "address02": "Apt. 123", + "city": "Place", + "items": [ + { + "image": "https://marketing-image-production.s3.amazonaws.com/uploads/8dda1131320a6d978b515cc04ed479df259a458d5d45d58b6b381cae0bf9588113e80ef912f69e8c4cc1ef1a0297e8eefdb7b270064cc046b79a44e21b811802.png", + "price": "$ 79.95", + "text": "New Line Sneakers" + }, + { + "image": "https://marketing-image-production.s3.amazonaws.com/uploads/3629f54390ead663d4eb7c53702e492de63299d7c5f7239efdc693b09b9b28c82c924225dcd8dcb65732d5ca7b7b753c5f17e056405bbd4596e4e63a96ae5018.png", + "price": "$ 79.95", + "text": "Old Line Sneakers" + }, + { + "image": "https://marketing-image-production.s3.amazonaws.com/uploads/00731ed18eff0ad5da890d876c456c3124a4e44cb48196533e9b95fb2b959b7194c2dc7637b788341d1ff4f88d1dc88e23f7e3704726d313c57f350911dd2bd0.png", + "price": "$ 79.95", + "text": "Blue Line Sneakers" + } + ], + "name": "Sample Name", + "receipt": true, + "state": "CO", + "total": "$ 239.85", + "zip": "80202" + }, + "to": [ + { + "email": "test+to@example.com", + "name": "Example To Name" + } + ] + } + ], + "subject": "Sending with SendGrid is Fun" + }''') + ) + + def test_sendgrid_api_key(self): + """Tests if including SendGrid API will throw an Exception""" + + # Minimum required to send an email + self.max_diff = None + mail = Mail() + + mail.from_email = Email("test@example.com") + + mail.subject = "Hello World from the SendGrid Python Library" + + personalization = Personalization() + personalization.add_to(Email("test@example.com")) + mail.add_personalization(personalization) + + # Try to include SendGrid API key + try: + mail.add_content( + Content( + "text/plain", + "some SG.2123b1B.1212lBaC here")) + mail.add_content( + Content( + "text/html", + "some SG.Ba2BlJSDba.232Ln2 here")) + + self.assertEqual( + json.dumps( + mail.get(), + sort_keys=True), + '{"content": [{"type": "text/plain", "value": "some text here"}, ' + '{"type": "text/html", ' + '"value": "some text here"}], ' + '"from": {"email": "test@example.com"}, "personalizations": ' + '[{"to": [{"email": "test@example.com"}]}], ' + '"subject": "Hello World from the SendGrid Python Library"}' + ) + + # Exception should be thrown + except Exception: + pass + + # Exception not thrown + else: + self.fail("Should have failed as SendGrid API key included") + + def test_unicode_values_in_substitutions_helper(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + message.substitution = Substitution('%city%', u'Αθήνα', p=1) + self.assertEqual( + message.get(), + json.loads(r'''{ + "content": [ + { + "type": "text/plain", + "value": "and easy to do anywhere, even with Python" + }, + { + "type": "text/html", + "value": "and easy to do anywhere, even with Python" + } + ], + "from": { + "email": "test+from@example.com", + "name": "Example From Name" + }, + "personalizations": [ + { + "to": [ + { + "email": "test+to@example.com", + "name": "Example To Name" + } + ] + }, + { + "substitutions": { + "%city%": "Αθήνα" + } + } + ], + "subject": "Sending with SendGrid is Fun" + }''') + ) + + def test_asm_display_group_limit(self): + self.assertRaises(ValueError, Asm, 1, list(range(26))) + + def test_disable_tracking(self): + tracking_settings = TrackingSettings() + tracking_settings.click_tracking = ClickTracking(False, False) + + self.assertEqual( + tracking_settings.get(), + {'click_tracking': {'enable': False, 'enable_text': False}} + ) + + def test_bypass_list_management(self): + from sendgrid.helpers.mail import (MailSettings, BypassListManagement) + mail_settings = MailSettings() + mail_settings.bypass_list_management = BypassListManagement(True) + + self.assertEqual( + mail_settings.get(), + { + "bypass_list_management": { + "enable": True + }, + }, + ) + + def test_v3_bypass_filters(self): + from sendgrid.helpers.mail import ( + MailSettings, BypassBounceManagement, + BypassSpamManagement, BypassUnsubscribeManagement + ) + mail_settings = MailSettings() + mail_settings.bypass_bounce_management = BypassBounceManagement(True) + mail_settings.bypass_spam_management = BypassSpamManagement(True) + mail_settings.bypass_unsubscribe_management = BypassUnsubscribeManagement(True) + + self.assertEqual( + mail_settings.get(), + { + "bypass_bounce_management": { + "enable": True + }, + "bypass_spam_management": { + "enable": True + }, + "bypass_unsubscribe_management": { + "enable": True + }, + }, + ) diff --git a/test/test_parse.py b/test/unit/test_parse.py similarity index 84% rename from test/test_parse.py rename to test/unit/test_parse.py index 897b67655..1c899bbbb 100644 --- a/test/test_parse.py +++ b/test/unit/test_parse.py @@ -1,11 +1,8 @@ +import unittest + from sendgrid.helpers.inbound.config import Config from sendgrid.helpers.inbound.app import app -try: - import unittest2 as unittest -except ImportError: - import unittest - class UnitTests(unittest.TestCase): diff --git a/test/unit/test_project.py b/test/unit/test_project.py new file mode 100644 index 000000000..40282bdb7 --- /dev/null +++ b/test/unit/test_project.py @@ -0,0 +1,52 @@ +import os +import unittest + + +class ProjectTests(unittest.TestCase): + # ./.env_sample + def test_env(self): + self.assertTrue(os.path.isfile('./.env_sample')) + + # ./.gitignore + def test_gitignore(self): + self.assertTrue(os.path.isfile('./.gitignore')) + + # ./CHANGELOG.md + def test_changelog(self): + self.assertTrue(os.path.isfile('./CHANGELOG.md')) + + # ./CODE_OF_CONDUCT.md + def test_code_of_conduct(self): + self.assertTrue(os.path.isfile('./CODE_OF_CONDUCT.md')) + + # ./CONTRIBUTING.md + def test_contributing(self): + self.assertTrue(os.path.isfile('./CONTRIBUTING.md')) + + # ./LICENSE + def test_license(self): + self.assertTrue(os.path.isfile('./LICENSE')) + + # ./PULL_REQUEST_TEMPLATE.md + def test_pr_template(self): + self.assertTrue(os.path.isfile('./PULL_REQUEST_TEMPLATE.md')) + + # ./README.rst + def test_readme(self): + self.assertTrue(os.path.isfile('./README.rst')) + + # ./TROUBLESHOOTING.md + def test_troubleshooting(self): + self.assertTrue(os.path.isfile('./TROUBLESHOOTING.md')) + + # ./USAGE.md + def test_usage(self): + self.assertTrue(os.path.isfile('./USAGE.md')) + + # ./use-cases/README.md + def test_use_cases(self): + self.assertTrue(os.path.isfile('./use_cases/README.md')) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/unit/test_sendgrid.py b/test/unit/test_sendgrid.py new file mode 100644 index 000000000..328d978ab --- /dev/null +++ b/test/unit/test_sendgrid.py @@ -0,0 +1,27 @@ +import unittest +import sendgrid + +class UnitTests(unittest.TestCase): + def test_host_with_no_region(self): + sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY') + self.assertEqual("https://api.sendgrid.com",sg.client.host) + + def test_host_with_eu_region(self): + sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY') + sg.set_sendgrid_data_residency("eu") + self.assertEqual("https://api.eu.sendgrid.com",sg.client.host) + + def test_host_with_global_region(self): + sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY') + sg.set_sendgrid_data_residency("global") + self.assertEqual("https://api.sendgrid.com",sg.client.host) + + def test_with_region_is_none(self): + sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY') + with self.assertRaises(ValueError): + sg.set_sendgrid_data_residency(None) + + def test_with_region_is_invalid(self): + sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY') + with self.assertRaises(ValueError): + sg.set_sendgrid_data_residency("abc") \ No newline at end of file diff --git a/test/unit/test_spam_check.py b/test/unit/test_spam_check.py new file mode 100644 index 000000000..e532573b8 --- /dev/null +++ b/test/unit/test_spam_check.py @@ -0,0 +1,38 @@ +from sendgrid.helpers.mail.spam_check import SpamCheck + +try: + import unittest2 as unittest +except ImportError: + import unittest + + +class UnitTests(unittest.TestCase): + + def test_spam_all_values(self): + expected = {'enable': True, 'threshold': 5, 'post_to_url': 'https://www.test.com'} + spam_check = SpamCheck(enable=True, threshold=5, post_to_url='https://www.test.com') + self.assertEqual(spam_check.get(), expected) + + def test_spam_no_url(self): + expected = {'enable': True, 'threshold': 10} + spam_check = SpamCheck(enable=True, threshold=10) + self.assertEqual(spam_check.get(), expected) + + def test_spam_no_threshold(self): + expected = {'enable': True} + spam_check = SpamCheck(enable=True) + self.assertEqual(spam_check.get(), expected) + + def test_has_values_but_not_enabled(self): + expected = {'enable': False, 'threshold': 1, 'post_to_url': 'https://www.test.com'} + spam_check = SpamCheck(enable=False, threshold=1, post_to_url='https://www.test.com') + self.assertEqual(spam_check.get(), expected) + + def test_spam_change_properties(self): + """Tests changing the properties of the spam check class""" + expected = {'enable': False, 'threshold': 10, 'post_to_url': 'https://www.testing.com'} + spam_check = SpamCheck(enable=True, threshold=5, post_to_url='https://www.test.com') + spam_check.enable = False + spam_check.threshold = 10 + spam_check.post_to_url = 'https://www.testing.com' + self.assertEqual(spam_check.get(), expected) diff --git a/test/unit/test_stats.py b/test/unit/test_stats.py new file mode 100644 index 000000000..c71117397 --- /dev/null +++ b/test/unit/test_stats.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +import json +from sendgrid.helpers.stats import * + +try: + import unittest2 as unittest +except ImportError: + import unittest + + +class UnitTests(unittest.TestCase): + + def test_basicStats(self): + + """Minimum required for stats""" + global_stats = Stats(start_date='12-09-2017') + + self.assertEqual( + json.dumps( + global_stats.get(), + sort_keys=True), + '{"start_date": "12-09-2017"}' + ) + + self.assertTrue(isinstance(str(global_stats), str)) + + def test_Stats(self): + + all_stats = Stats(start_date='12-09-2017') + all_stats.end_date = '12-10-2017' + all_stats.aggregated_by = 'day' + all_stats._sort_by_direction = 'asc' + all_stats.sort_by_metric = 'clicks' + all_stats._limit = 100 + all_stats._offset = 2 + + self.assertEqual( + json.dumps( + all_stats.get(), + sort_keys=True), + '{"aggregated_by": "day", "end_date": "12-10-2017", ' + '"limit": 100, "offset": 2, "sort_by_direction": "asc", ' + '"sort_by_metric": "clicks", "start_date": "12-09-2017"}' + ) + + def test_categoryStats(self): + + category_stats = CategoryStats(start_date='12-09-2017', categories=['foo', 'bar']) + category_stats.add_category(Category('woo')) + category_stats.end_date = '12-10-2017' + category_stats.aggregated_by = 'day' + category_stats._sort_by_direction = 'asc' + category_stats.sort_by_metric = 'clicks' + category_stats._limit = 100 + category_stats._offset = 2 + + self.assertEqual( + json.dumps( + category_stats.get(), + sort_keys=True), + '{"aggregated_by": "day", "categories": ["foo", "bar", "woo"], ' + '"end_date": "12-10-2017", "limit": 100, "offset": 2, ' + '"sort_by_direction": "asc", "sort_by_metric": "clicks", ' + '"start_date": "12-09-2017"}' + ) + + def test_subuserStats(self): + + subuser_stats = SubuserStats(start_date = '12-09-2017', subusers=['foo', 'bar']) + subuser_stats.add_subuser(Subuser('blah')) + subuser_stats.end_date = '12-10-2017' + subuser_stats.aggregated_by = 'day' + subuser_stats._sort_by_direction = 'asc' + subuser_stats.sort_by_metric = 'clicks' + subuser_stats._limit = 100 + subuser_stats._offset = 2 + + self.assertEqual( + json.dumps( + subuser_stats.get(), + sort_keys=True), + '{"aggregated_by": "day", "end_date": "12-10-2017", ' + '"limit": 100, "offset": 2, "sort_by_direction": "asc", ' + '"sort_by_metric": "clicks", "start_date": "12-09-2017", ' + '"subusers": ["foo", "bar", "blah"]}' + ) diff --git a/test/unit/test_twilio_email.py b/test/unit/test_twilio_email.py new file mode 100644 index 000000000..92269acff --- /dev/null +++ b/test/unit/test_twilio_email.py @@ -0,0 +1,37 @@ +import os +import unittest + +from sendgrid import TwilioEmailAPIClient + + +class UnitTests(unittest.TestCase): + + @classmethod + def setUpClass(cls): + os.environ['TWILIO_API_KEY'] = 'api-key' + os.environ['TWILIO_API_SECRET'] = 'api-secret' + os.environ['TWILIO_ACCOUNT_SID'] = 'account-sid' + os.environ['TWILIO_AUTH_TOKEN'] = 'auth-token' + + def test_init_key_over_token(self): + mail_client = TwilioEmailAPIClient() + + self.assertEqual(mail_client.username, 'api-key') + self.assertEqual(mail_client.password, 'api-secret') + self.assertEqual(mail_client.host, 'https://email.twilio.com') + + def test_init_token(self): + del os.environ['TWILIO_API_KEY'] + del os.environ['TWILIO_API_SECRET'] + + mail_client = TwilioEmailAPIClient() + + self.assertEqual(mail_client.username, 'account-sid') + self.assertEqual(mail_client.password, 'auth-token') + + def test_init_args(self): + mail_client = TwilioEmailAPIClient('username', 'password') + + self.assertEqual(mail_client.username, 'username') + self.assertEqual(mail_client.password, 'password') + self.assertEqual(mail_client.auth, 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=') diff --git a/test/unit/test_unassigned.py b/test/unit/test_unassigned.py new file mode 100644 index 000000000..08ab943bb --- /dev/null +++ b/test/unit/test_unassigned.py @@ -0,0 +1,93 @@ +import json + +from sendgrid.helpers.endpoints.ip.unassigned import unassigned + +ret_json = '''[ { + "ip": "167.89.21.3", + "pools": [ + "pool1", + "pool2" + ], + "whitelabeled": false, + "start_date": 1409616000, + "subusers": [ + "tim@sendgrid.net" + ], + "warmup": false, + "assigned_at": 1482883200 + }, + { + "ip": "192.168.1.1", + "pools": [ + "pool1", + "pool2" + ], + "whitelabeled": false, + "start_date": 1409616000, + "subusers": [ + "tim@sendgrid.net" + ], + "warmup": false, + "assigned_at": 1482883200 + }, + { + "ip": "208.115.214.22", + "pools": [], + "whitelabeled": true, + "rdns": "o1.email.burgermail.com", + "start_date": 1409616000, + "subusers": [], + "warmup": false, + "assigned_at": 1482883200 + }, + { + "ip": "208.115.214.23", + "pools": [], + "whitelabeled": true, + "rdns": "o1.email.burgermail.com", + "start_date": 1409616000, + "subusers": [], + "warmup": false, + "assigned_at": 1482883200 + + } ] + ''' + + +def get_all_ip(): + ret_val = json.loads(ret_json) + return ret_val + + +def make_data(): + data = set() + data.add("208.115.214.23") + data.add("208.115.214.22") + return data + + +def test_unassigned_ip_json(): + data = make_data() + + as_json = True + calculated = unassigned(get_all_ip(), as_json=as_json) + calculated = json.loads(calculated) + + for item in calculated: + assert item["ip"] in data + + +def test_unassigned_ip_obj(): + data = make_data() + + as_json = False + calculated = unassigned(get_all_ip(), as_json=as_json) + + for item in calculated: + assert item["ip"] in data + + +def test_unassigned_baddata(): + as_json = False + calculated = unassigned(dict(), as_json=as_json) + assert calculated == [] diff --git a/tox.ini b/tox.ini index 9336a97b8..8f4f2db9a 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py26, py27, py34, py35, py36 +envlist = py27, py34, py35, py36, py37, py38, py39, py310, py311, py312, py313 [testenv] commands = coverage erase @@ -14,15 +14,6 @@ deps = -rrequirements.txt coverage -[testenv:py26] -commands = coverage erase - coverage run {envbindir}/unit2 discover -v [] - coverage report -deps = unittest2 - mock - {[testenv]deps} -basepython = python2.6 - [testenv:py27] commands = {[testenv]commands} deps = {[testenv]deps} @@ -42,4 +33,39 @@ basepython = python3.5 [testenv:py36] commands = {[testenv]commands} deps = {[testenv]deps} -basepython = python3.6 \ No newline at end of file +basepython = python3.6 + +[testenv:py37] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.7 + +[testenv:py38] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.8 + +[testenv:py39] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.9 + +[testenv:py310] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.10 + +[testenv:py311] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.11 + +[testenv:py312] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.12 + +[testenv:py313] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.13 diff --git a/twilio_sendgrid_logo.png b/twilio_sendgrid_logo.png new file mode 100644 index 000000000..a4c22239a Binary files /dev/null and b/twilio_sendgrid_logo.png differ diff --git a/twilio_sendgrid_logo_small.png b/twilio_sendgrid_logo_small.png new file mode 100644 index 000000000..f6f5b84c7 Binary files /dev/null and b/twilio_sendgrid_logo_small.png differ diff --git a/use_cases/README.md b/use_cases/README.md new file mode 100644 index 000000000..f9fe2470e --- /dev/null +++ b/use_cases/README.md @@ -0,0 +1,35 @@ +# Use Cases + +This directory provides examples for specific use cases of this library. Please [open an issue](https://github.com/sendgrid/sendgrid-python/issues) or make a pull request for any use cases you would like us to document here. Thank you! + +## Table of Contents + +### Common Use Cases +* [Send a Single Email to a Single Recipient](send_a_single_email_to_a_single_recipient.md) +* [Send a Single Email to Multiple Recipients](send_a_single_email_to_multiple_recipients.md) +* [Send Multiple Emails to Multiple Recipients](send_multiple_emails_to_multiple_recipients.md) +* [Send Multiple Emails with Personalizations](send_multiple_emails_personalizations.md) +* [Kitchen Sink - an example with all settings used](kitchen_sink.md) +* [Transactional Templates](transactional_templates.md) +* [Attachments](attachment.md) + +### Working with Email +* [Asynchronous Mail Send](asynchronous_mail_send.md) +* [Sending HTML-Only Content](sending_html_content.md) +* [Integrate with Slack Events API](slack_event_api_integration.md) +* [Legacy Templates](legacy_templates.md) + +# Twilio Use Cases +* [Twilio Setup](twilio-setup.md) +* [Send an Email With Twilio Email (Pilot)](twilio-email.md) +* [Send an SMS Message](sms.md) + +### Troubleshooting +* [Error Handling](error_handling.md) + +### How-Tos +* [How to Create a Django app, Deployed on Heroku, to Send Email with Twilio SendGrid](django.md) +* [How to Deploy A Simple Hello Email App on AWS](aws.md) +* [How to Deploy a simple Flask app, to send Email with Twilio SendGrid, on Heroku](flask_heroku.md) +* [How to Setup a Domain Authentication](domain_authentication.md) +* [How to View Email Statistics](email_stats.md) diff --git a/use_cases/asynchronous_mail_send.md b/use_cases/asynchronous_mail_send.md new file mode 100644 index 000000000..de38dc751 --- /dev/null +++ b/use_cases/asynchronous_mail_send.md @@ -0,0 +1,83 @@ +# Asynchronous Mail Send + +## Using `asyncio` (3.5+) + +The built-in `asyncio` library can be used to send email in a non-blocking manner. `asyncio` helps us execute mail sending in a separate context, allowing us to continue the execution of business logic without waiting for all our emails to send first. + +```python +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Content, Mail, From, To, Mail +import os +import asyncio + + +sendgrid_client = SendGridAPIClient( + api_key=os.environ.get('SENDGRID_API_KEY')) + +from_email = From("test@example.com") +to_email = To("test1@example.com") + +plain_text_content = Content("text/plain", "This is asynchronous sending test.") +html_content = Content("text/html", "This is asynchronous sending test.") + +# instantiate `sendgrid.helpers.mail.Mail` objects +em1 = Mail(from_email, to_email, "Message #1", content) +em2 = Mail(from_email, to_email, "Message #2", content) +em3 = Mail(from_email, to_email, "Message #3", content) +em4 = Mail(from_email, to_email, "Message #4", content) +em5 = Mail(from_email, to_email, "Message #5", content) +em6 = Mail(from_email, to_email, "Message #6", content) +em7 = Mail(from_email, to_email, "Message #7", content) +em8 = Mail(from_email, to_email, "Message #8", content) +em9 = Mail(from_email, to_email, "Message #9", content) +em10 = Mail(from_email, to_email, "Message #10", content) + + +ems = [em1, em2, em3, em4, em5, em6, em7, em8, em9, em10] + + +async def send_email(n, email): + ''' + send_mail wraps Twilio SendGrid's API client, and makes a POST request to + the api/v3/mail/send endpoint with `email`. + Args: + email: single mail object. + ''' + try: + response = sendgrid_client.send(request_body=email) + if response.status_code < 300: + print("Email #{} processed".format(n), response.body, response.status_code) + except urllib.error.HTTPError as e: + e.read() + + +@asyncio.coroutine +def send_many(emails, cb): + ''' + send_many creates a number of non-blocking tasks (to send email) + that will run on the existing event loop. Due to non-blocking nature, + you can include a callback that will run after all tasks have been queued. + + Args: + emails: contains any # of `sendgrid.helpers.mail.Mail`. + cb: a function that will execute immediately. + ''' + print("START - sending emails ...") + for n, em in enumerate(emails): + asyncio.async(send_email(n, em)) + print("END - returning control...") + cb() + + +def sample_cb(): + print("Executing callback now...") + for i in range(0, 100): + print(i) + return + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + task = asyncio.async(send_many(ems, sample_cb)) + loop.run_until_complete(task) +``` \ No newline at end of file diff --git a/use_cases/attachment.md b/use_cases/attachment.md new file mode 100644 index 000000000..8ec7565dc --- /dev/null +++ b/use_cases/attachment.md @@ -0,0 +1,37 @@ +# Attachment + +```python +import base64 +import os +from sendgrid.helpers.mail import ( + Mail, Attachment, FileContent, FileName, + FileType, Disposition, ContentId) +from sendgrid import SendGridAPIClient + +message = Mail( + from_email='from_email@example.com', + to_emails='to@example.com', + subject='Sending with Twilio SendGrid is Fun', + html_content='and easy to do anywhere, even with Python') +file_path = 'example.pdf' +with open(file_path, 'rb') as f: + data = f.read() + f.close() +encoded = base64.b64encode(data).decode() +attachment = Attachment() +attachment.file_content = FileContent(encoded) +attachment.file_type = FileType('application/pdf') +attachment.file_name = FileName('test_filename.pdf') +attachment.disposition = Disposition('attachment') +attachment.content_id = ContentId('Example Content ID') +message.attachment = attachment +try: + sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message) + print(response.status_code) + print(response.body) + print(response.headers) +except Exception as e: + print(e.message) + +``` diff --git a/use_cases/aws.md b/use_cases/aws.md new file mode 100644 index 000000000..2e63bd1f5 --- /dev/null +++ b/use_cases/aws.md @@ -0,0 +1,173 @@ +# Deploy a simple Hello Email app on AWS + +This tutorial explains how to set up a simple "Hello Email" app on AWS, using the AWS CodeStar service. + +We'll be creating a basic web service to send email via Twilio SendGrid. The application will run on AWS Lambda, and the "endpoint" will be via AWS API Gateway. + +The neat thing is that CodeStar provides all of this in a pre-configured package. We just have to make some config changes and push our code. + +Once this tutorial is complete, you'll have a basic web service for sending email that can be invoked via a link to your newly created API endpoint. + +### Prerequisites +Python 2.7 and 3.4 or 3.5 are supported by the sendgrid Python library, however, I was able to utilize 3.6 with no issue. + +Before starting this tutorial, you will need to have access to an AWS account in which you are allowed to provision resources. This tutorial also assumes you've already created a Twilio SendGrid account with free-tier access. Finally, it is highly recommended you utilize [virtualenv](https://virtualenv.pypa.io/en/stable/). + +*DISCLAIMER*: Any resources provisioned here may result in charges being incurred to your account. Twilio SendGrid is in no way responsible for any billing charges. + + +## Getting Started + +### Create AWS CodeStar Project +Log in to your AWS account and go to the AWS CodeStar service. Click "Start a project". For this tutorial we're going to choose a Python Web service, utilizing AWS Lambda. You can use the filters on the left hand side of the UI to narrow down the available choices. + +After you've selected the template, you're asked to provide a name for your project. Go ahead and name it "hello-email". Once you've entered a name, click "Create Project" in the lower right hand corner. You can then choose which tools you want to use to interact with the project. For this tutorial, we'll be choosing "Command Line". + +Once that is completed, you'll be given some basic steps to get Git installed and setup, and instructions for connecting to the AWS CodeCommit(Git) repository. You can either use HTTPS, or SSH. Instructions for setting up either are provided. + +Go ahead and clone the Git repository link after it is created. You may need to click "Skip" in the lower right-hand corner to proceed. + +Once that's done, you've successfully created a CodeStar project! You should be at the dashboard, with a view of the wiki, change log, build pipeline, and application endpoint. + +### Create Twilio SendGrid API Key +Log in to your Twilio SendGrid account. Click on your username on the left-hand side of the UI and choose "Setup Guide" from the drop-down menu. On the "Welcome" menu, choose "Send Your First Email", and then "Integrate using our Web API or SMTP relay." Choose "Web API" as the recommended option on the next screen, as we'll be using that for this tutorial. For more information about creating API keys, see https://sendgrid.com/docs/Classroom/Send/How_Emails_Are_Sent/api_keys.html + +On the next menu, you have the option to choose what programming language you'll be using. The obvious choice for this tutorial will be Python. + +Follow the steps on the next screen. Choose a name for your API key, such as "hello-email". Follow the remaining steps to create an environment variable, install the sendgrid module, and copy the test code. Once that is complete, check the "I've integrated the code above" box, and click the "Next: Verify Integration" button. + +Assuming all the steps were completed correctly, you should be greeted with a success message. If not, go back and verify that everything is correct, including your API key environment variable, and Python code. + +## Deploy hello-world app using CodeStar + +For the rest of the tutorial, we'll be working out of the Git repository we cloned from AWS earlier: +``` +$ cd hello-email +``` +note: this assumes you cloned the Git repo inside your current directory. My directory is: + +``` +~/projects/hello-email +``` + +The directory contents should be as follows: + + ├──buildspec.yml + ├──index.py + ├──template.yml + ├──README.md + +The `buildspec.yml` file is a YAML definition for the AWS CodeBuild service, and will not need to be modified for this tutorial. The `index.py` is where the application logic will be placed, and the `template.yml` is a YAML definition file for the AWS Lambda function. + +We'll start by modifying the `template.yml` file. Copy and paste from the example below, or edit your existing copy to match: + +```yaml +AWSTemplateFormatVersion: 2010-09-09 +Transform: +- AWS::Serverless-2016-10-31 +- AWS::CodeStar + +Parameters: + ProjectId: + Type: String + Description: CodeStar projectId used to associate new resources to team members + +Resources: + HelloEmail: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: python3.6 + Role: + Fn::ImportValue: + !Join ['-', [!Ref 'ProjectId', !Ref 'AWS::Region', 'LambdaTrustRole']] + Events: + GetEvent: + Type: Api + Properties: + Path: / + Method: get + PostEvent: + Type: Api + Properties: + Path: / + Method: post +``` + +In the root project directory, run the following commands: +``` +virtualenv venv +source ./venv/bin/activate +``` + +Prior to being able to deploy our Python code, we'll need to install the sendgrid Python module *locally*. One of the idiosyncracies of AWS Lambda is that all library and module dependencies that aren't part of the standard library have to be included with the code/build artifact. Virtual environments do not translate to the Lambda runtime environment. + +In the root project directory, run the following command: +``` +$ pip install sendgrid -t . +``` +This will install the module locally to the project dir, where it can be built into the Lambda deployment. + +Now go ahead and modify the `index.py` file to match below: + +```python +import json +import datetime +from sendgrid import SendGridAPIClient +import os +from sendgrid.helpers.mail import (From, To, PlainTextContent, HtmlContent, Mail) + +def handler(event, context): + sendgrid_client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + from_email = From("test@example.com") + to_email = To("test@example.com") + subject = "Sending with Twilio SendGrid is Fun" + plain_text_content = PlainTextContent("and easy to do anywhere, even with Python") + html_content = HtmlContent("and easy to do anywhere, even with Python") + message = Mail(from_email, to_email, subject, plain_text_content, html_content) + response = sendgrid_client.send(message=message) + status = b"{}".decode('utf-8').format(response.status_code) + body = b"{}".decode('utf-8').format(response.body) + headers = b"{}".decode('utf-8').format(response.headers) + data = { + 'status': status, + 'body': body, + 'headers': headers.splitlines(), + 'timestamp': datetime.datetime.utcnow().isoformat() + } + return {'statusCode': 200, + 'body': json.dumps(data), + 'headers': {'Content-Type': 'application/json'}} +``` + +Note that for the most part, we've simply copied the initial code from the API verification with Twilio SendGrid. Some slight modifications were needed to allow it to run as a lambda function, and for the output to be passed cleanly from the API endpoint. + +Change the `test@example.com` emails appropriately so that you may receive the test email. + +Go ahead and commit/push your code: + +``` +$ git add . +``` + +``` +$ git commit -m 'hello-email app' +``` + +``` +$ git push +``` + +Once the code is successfully pushed, head back to the AWS CodeStar dashboard for your project. After your commit successfully registers, an automated build and deployment process should kick off. + +One more step left before our application will work correctly. After your code has bee deployed, head to the AWS Lambda console. Click on your function name, which should start with `awscodestar-hello-email-lambda-`, or similar. + +Scroll down to the "Environment Variables" section. Here we need to populate our Twilio SendGrid API key. Copy the value from the `.env` file you created earlier, ensuring to capture the entire value. Make sure the key is titled: + +``` +SENDGRID_API_KEY +``` + +Now, go back to your project dashboard in CodeStar. Click on the link under "Application endpoints". After a moment, you should be greeted with JSON output indicating an email was successfully sent. + +Congratulations, you've just used serverless technology to create an email sending app in AWS! diff --git a/use_cases/django.md b/use_cases/django.md new file mode 100644 index 000000000..809b491a6 --- /dev/null +++ b/use_cases/django.md @@ -0,0 +1,202 @@ +# Create a Django app to send email with Twilio SendGrid + +This tutorial explains how we set up a simple Django app to send an email with the Twilio SendGrid Python SDK and how we deploy our app to Heroku. + +## Create a Django project + +We first create a project folder. + +```bash +$ mkdir hello-sendgrid +$ cd hello-sendgrid +``` + +We assume you have created and activated a [virtual environment](https://virtualenv.pypa.io/) (See [venv](https://docs.python.org/3/tutorial/venv.html) for Python 3+) for isolated Python environments. + +Run the command below to install Django, Gunicorn (a Python WSGI HTTP server), and Twilio SendGrid Python SDK. + +```bash +$ pip install django gunicorn sendgrid +``` + +It's a good practice for Python dependency management. We'll pin the requirements with a file `requirements.txt`. + +```bash +$ pip freeze > requirements.txt +``` + +Run the command below to initialize a Django project. + +```bash +$ django-admin startproject hello_sendgrid +``` + +The folder structure should look like this: + +``` +hello-sendgrid +├── hello_sendgrid +│   ├── hello_sendgrid +│   │   ├── __init__.py +│   │   ├── settings.py +│   │   ├── urls.py +│   │   └── wsgi.py +│   └── manage.py +└── requirements.txt +``` + +Let's create a page to generate and send an email to a user when you hit the page. + +We first create a file `views.py` and put it under the folder `hello_sendgrid/hello_sendgrid`. Add the minimum needed code below. + +```python +import os + +from django.http import HttpResponse + +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import (From, To, PlainTextContent, HtmlContent, Mail) + + +def index(request): + sendgrid_client = SendGridAPIClient( + api_key=os.environ.get('SENDGRID_API_KEY')) + from_email = From('test@example.com') + to_email = To('test@example.com') + subject = 'Sending with Twilio SendGrid is Fun' + plain_text_content = PlainTextContent( + 'and easy to do anywhere, even with Python' + ) + html_content = HtmlContent( + 'and easy to do anywhere, even with Python' + ) + message = Mail(from_email, to_email, subject, plain_text_content, html_content) + response = sendgrid_client.send(message=message) + + return HttpResponse('Email Sent!') +``` + +**Note:** It would be best to change your to email from `test@example.com` to your own email, so that you can see the email you receive. + +Now the folder structure should look like this: + +``` +hello-sendgrid +├── hello_sendgrid +│   ├── hello_sendgrid +│   │   ├── __init__.py +│   │   ├── settings.py +│   │   ├── urls.py +│   │   ├── views.py +│   │   └── wsgi.py +│   └── manage.py +└── requirements.txt +``` + +Next we open the file `urls.py` in order to add the view we have just created to the Django URL dispatcher. + +```python +from django.conf.urls import url +from django.contrib import admin + +from .views import index + + +urlpatterns = [ + url(r'^admin/', admin.site.urls), + url(r'^sendgrid/', index, name='sendgrid'), +] +``` + +These paths allow the URL `/sendgrid/` to send the email. + +We also assume that you have set up your development environment with your `SENDGRID_API_KEY`. If you have not done it yet, please do so. See the section [Setup Environment Variables](https://github.com/sendgrid/sendgrid-python#setup-environment-variables). + +Now we should be able to send an email. Let's run our Django development server to test it. + +``` +$ cd hello_sengrid +$ python manage.py migrate +$ python manage.py runserver +``` + +By default, it starts the development server at `http://127.0.0.1:8000/`. To test if we can send email or not, go to `http://127.0.0.1:8000/sendgrid/`. If it works, we should see the page says "Email Sent!". + +**Note:** If you use `test@example.com` as your from email, it's likely to go to your spam folder. To have the emails show up in your inbox, try using an email address at the domain you registered your Twilio SendGrid account. + +## Deploy to Heroku + +There are different deployment methods we can choose. In this tutorial, we choose to deploy our app using the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli). Therefore, let's install it before we go further. + +Once you have the Heroku CLI installed, run the command below to log in to your Heroku account if you haven't already. + +``` +$ heroku login +``` + +Before we start the deployment, let's create a Heroku app by running the command below. This tutorial names the Heroku app `hello-sendgrid`. + +```bash +$ heroku create hello-sendgrid +``` + +**Note:** If you see Heroku reply with "Name is already taken", please add a random string to the end of the name. + +We also need to do a couple things: + +1. Add `'*'` or your Heroku app domain to `ALLOWED_HOSTS` in the file `settings.py`. It will look like this: +```python +ALLOWED_HOSTS = ['*'] +``` + +2. Add `Procfile` with the code below to declare what commands are run by your application's dynos on the Heroku platform. +``` +web: cd hello_sendgrid && gunicorn hello_sendgrid.wsgi --log-file - +``` + +The final folder structure looks like this: + +``` +hello-sendgrid +├── hello_sendgrid +│   ├── hello_sendgrid +│   │   ├── __init__.py +│   │   ├── settings.py +│   │   ├── urls.py +│   │   ├── views.py +│   │   └── wsgi.py +│   └── manage.py +├── Procfile +└── requirements.txt +``` + +Go to the root folder then initialize a Git repository. + +``` +$ git init +$ heroku git:remote -a hello-sendgrid +``` + +**Note:** Change `hello-sendgrid` to your new Heroku app name you created earlier. + +Add your `SENDGRID_API_KEY` as one of the Heroku environment variables. + +``` +$ heroku config:set SENDGRID_API_KEY= +``` + +Since we do not use any static files, we will disable `collectstatic` for this project. + +``` +$ heroku config:set DISABLE_COLLECTSTATIC=1 +``` + +Commit the code to the repository and deploy it to Heroku using Git. + +``` +$ git add . +$ git commit -am "Create simple Hello Email Django app using Twilio SendGrid" +$ git push heroku main +``` + +After that, let's verify if our app is working or not by accessing the root domain of your Heroku app. You should see the page says "Email Sent!" and on the Activity Feed page in the Twilio SendGrid dashboard, you should see a new feed with the email you set in the code. diff --git a/use_cases/domain_authentication.md b/use_cases/domain_authentication.md new file mode 100644 index 000000000..4740f92f9 --- /dev/null +++ b/use_cases/domain_authentication.md @@ -0,0 +1,5 @@ +# How to Setup a Domain Authentication + +You can find documentation for how to setup a domain authentication via the UI [here](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication) and via API [here](../USAGE.md#sender-authentication). + +Find more information about all of Twilio SendGrid's domain authentication related documentation [here](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication). diff --git a/use_cases/email_stats.md b/use_cases/email_stats.md new file mode 100644 index 000000000..10e265721 --- /dev/null +++ b/use_cases/email_stats.md @@ -0,0 +1,5 @@ +# How to View Email Statistics + +You can find documentation for how to view your email statistics via the UI [here](https://app.sendgrid.com/statistics) and via API [here](../USAGE.md#stats). + +Alternatively, we can post events to a URL of your choice via our [Event Webhook](https://docs.sendgrid.com/for-developers/tracking-events/event) about events that occur as Twilio SendGrid processes your email. diff --git a/use_cases/error_handling.md b/use_cases/error_handling.md new file mode 100644 index 000000000..d0bdf0945 --- /dev/null +++ b/use_cases/error_handling.md @@ -0,0 +1,29 @@ +# Error Handling +[Custom exceptions](https://github.com/sendgrid/python-http-client/blob/HEAD/python_http_client/exceptions.py) for `python_http_client` are now supported. + +Please see [here](https://github.com/sendgrid/python-http-client/blob/HEAD/python_http_client/exceptions.py) for a list of supported exceptions. + +There are also email specific exceptions located [here](../sendgrid/helpers/mail/exceptions.py) + +```python +import os +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import (From, To, Subject, PlainTextContent, HtmlContent, Mail) +from python_http_client import exceptions + +sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) +from_email = From("help@twilio.com") +to_email = To("ethomas@twilio.com") +subject = Subject("Sending with Twilio SendGrid is Fun") +plain_text_content = PlainTextContent("and easy to do anywhere, even with Python") +html_content = HtmlContent("and easy to do anywhere, even with Python") +message = Mail(from_email, to_email, subject, plain_text_content, html_content) +try: + response = sendgrid_client.send(message) + print(response.status_code) + print(response.body) + print(response.headers) +except exceptions.BadRequestsError as e: + print(e.body) + exit() +``` diff --git a/use_cases/flask_heroku.md b/use_cases/flask_heroku.md new file mode 100644 index 000000000..be2581978 --- /dev/null +++ b/use_cases/flask_heroku.md @@ -0,0 +1,9 @@ +# Create a Flask app to send email with Twilio SendGrid + +This tutorial explains how to deploy a simple Flask app, to send an email using the Twilio SendGrid Python SDK, on Heroku. + +1. Create a Twilio SendGrid API key at https://app.sendgrid.com/settings/api_keys +1. Go to https://github.com/swapagarwal/sendgrid-flask-heroku +1. Click on `Deploy to Heroku` button, and follow the instructions. + +That's all. You'll be sending your first email within seconds! diff --git a/use_cases/kitchen_sink.md b/use_cases/kitchen_sink.md new file mode 100644 index 000000000..c0a301117 --- /dev/null +++ b/use_cases/kitchen_sink.md @@ -0,0 +1,230 @@ +```python +import os +import json +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import ( + Mail, From, To, Cc, Bcc, Subject, Substitution, Header, + CustomArg, SendAt, Content, MimeType, Attachment, FileName, + FileContent, FileType, Disposition, ContentId, TemplateId, + Section, ReplyTo, Category, BatchId, Asm, GroupId, GroupsToDisplay, + IpPoolName, MailSettings, BccSettings, BccSettingsEmail, + BypassBounceManagement, BypassListManagement, BypassSpamManagement, + BypassUnsubscribeManagement, FooterSettings, FooterText, + FooterHtml, SandBoxMode, SpamCheck, SpamThreshold, SpamUrl, + TrackingSettings, ClickTracking, SubscriptionTracking, + SubscriptionText, SubscriptionHtml, SubscriptionSubstitutionTag, + OpenTracking, OpenTrackingSubstitutionTag, Ganalytics, + UtmSource, UtmMedium, UtmTerm, UtmContent, UtmCampaign) + +message = Mail() + +# Define Personalizations + +message.to = To('test1@example.com', 'Example User1', p=0) +message.to = [ + To('test2@example.com', 'Example User2', p=0), + To('test3@example.com', 'Example User3', p=0) +] + +message.cc = Cc('test4@example.com', 'Example User4', p=0) +message.cc = [ + Cc('test5@example.com', 'Example User5', p=0), + Cc('test6@example.com', 'Example User6', p=0) +] + +message.bcc = Bcc('test7@example.com', 'Example User7', p=0) +message.bcc = [ + Bcc('test8@example.com', 'Example User8', p=0), + Bcc('test9@example.com', 'Example User9', p=0) +] + +message.subject = Subject('Sending with Twilio SendGrid is Fun 0', p=0) + +message.header = Header('X-Test1', 'Test1', p=0) +message.header = Header('X-Test2', 'Test2', p=0) +message.header = [ + Header('X-Test3', 'Test3', p=0), + Header('X-Test4', 'Test4', p=0) +] + +message.substitution = Substitution('%name1%', 'Example Name 1', p=0) +message.substitution = Substitution('%city1%', 'Example City 1', p=0) +message.substitution = [ + Substitution('%name2%', 'Example Name 2', p=0), + Substitution('%city2%', 'Example City 2', p=0) +] + +message.custom_arg = CustomArg('marketing1', 'true', p=0) +message.custom_arg = CustomArg('transactional1', 'false', p=0) +message.custom_arg = [ + CustomArg('marketing2', 'false', p=0), + CustomArg('transactional2', 'true', p=0) +] + +message.send_at = SendAt(1461775051, p=0) + +message.to = To('test10@example.com', 'Example User10', p=1) +message.to = [ + To('test11@example.com', 'Example User11', p=1), + To('test12@example.com', 'Example User12', p=1) +] + +message.cc = Cc('test13@example.com', 'Example User13', p=1) +message.cc = [ + Cc('test14@example.com', 'Example User14', p=1), + Cc('test15@example.com', 'Example User15', p=1) +] + +message.bcc = Bcc('test16@example.com', 'Example User16', p=1) +message.bcc = [ + Bcc('test17@example.com', 'Example User17', p=1), + Bcc('test18@example.com', 'Example User18', p=1) +] + +message.header = Header('X-Test5', 'Test5', p=1) +message.header = Header('X-Test6', 'Test6', p=1) +message.header = [ + Header('X-Test7', 'Test7', p=1), + Header('X-Test8', 'Test8', p=1) +] + +message.substitution = Substitution('%name3%', 'Example Name 3', p=1) +message.substitution = Substitution('%city3%', 'Example City 3', p=1) +message.substitution = [ + Substitution('%name4%', 'Example Name 4', p=1), + Substitution('%city4%', 'Example City 4', p=1) +] + +message.custom_arg = CustomArg('marketing3', 'true', p=1) +message.custom_arg = CustomArg('transactional3', 'false', p=1) +message.custom_arg = [ + CustomArg('marketing4', 'false', p=1), + CustomArg('transactional4', 'true', p=1) +] + +message.send_at = SendAt(1461775052, p=1) + +message.subject = Subject('Sending with Twilio SendGrid is Fun 1', p=1) + +# The values below this comment are global to entire message + +message.from_email = From('help@twilio.com', 'Twilio SendGrid') + +message.reply_to = ReplyTo('help_reply@twilio.com', 'Twilio SendGrid Reply') + +message.subject = Subject('Sending with Twilio SendGrid is Fun 2') + +message.content = Content( + MimeType.text, + 'and easy to do anywhere, even with Python') +message.content = Content( + MimeType.html, + 'and easy to do anywhere, even with Python') +message.content = [ + Content('text/calendar', 'Party Time!!'), + Content('text/custom', 'Party Time 2!!') +] + +message.attachment = Attachment(FileContent('base64 encoded content 1'), + FileName('balance_001.pdf'), + FileType('application/pdf'), + Disposition('attachment'), + ContentId('Content ID 1')) +message.attachment = [ + Attachment( + FileContent('base64 encoded content 2'), + FileName('banner.png'), + FileType('image/png'), + Disposition('inline'), + ContentId('Content ID 2')), + Attachment( + FileContent('base64 encoded content 3'), + FileName('banner2.png'), + FileType('image/png'), + Disposition('inline'), + ContentId('Content ID 3')) +] + +message.template_id = TemplateId('13b8f94f-bcae-4ec6-b752-70d6cb59f932') + +message.section = Section('%section1%', 'Substitution for Section 1 Tag') +message.section = [ + Section('%section2%', 'Substitution for Section 2 Tag'), + Section('%section3%', 'Substitution for Section 3 Tag') +] + +message.header = Header('X-Test9', 'Test9') +message.header = Header('X-Test10', 'Test10') +message.header = [ + Header('X-Test11', 'Test11'), + Header('X-Test12', 'Test12') +] + +message.category = Category('Category 1') +message.category = Category('Category 2') +message.category = [ + Category('Category 1'), + Category('Category 2') +] + +message.custom_arg = CustomArg('marketing5', 'false') +message.custom_arg = CustomArg('transactional5', 'true') +message.custom_arg = [ + CustomArg('marketing6', 'true'), + CustomArg('transactional6', 'false') +] + +message.send_at = SendAt(1461775053) + +message.batch_id = BatchId("HkJ5yLYULb7Rj8GKSx7u025ouWVlMgAi") + +message.asm = Asm(GroupId(1), GroupsToDisplay([1, 2, 3, 4])) + +message.ip_pool_name = IpPoolName("IP Pool Name") + +mail_settings = MailSettings() +mail_settings.bcc_settings = BccSettings( + False, + BccSettingsEmail("bcc@twilio.com")) +mail_settings.bypass_bounce_management = BypassBounceManagement(False) +mail_settings.bypass_list_management = BypassListManagement(False) +mail_settings.bypass_spam_management = BypassSpamManagement(False) +mail_settings.bypass_unsubscribe_management = BypassUnsubscribeManagement(False) +mail_settings.footer_settings = FooterSettings( + True, + FooterText("w00t"), + FooterHtml("w00t!")) +mail_settings.sandbox_mode = SandBoxMode(True) +mail_settings.spam_check = SpamCheck( + True, + SpamThreshold(5), + SpamUrl("https://example.com")) +message.mail_settings = mail_settings + +tracking_settings = TrackingSettings() +tracking_settings.click_tracking = ClickTracking(True, False) +tracking_settings.open_tracking = OpenTracking( + True, + OpenTrackingSubstitutionTag("open_tracking")) +tracking_settings.subscription_tracking = SubscriptionTracking( + True, + SubscriptionText("Goodbye"), + SubscriptionHtml("Goodbye!"), + SubscriptionSubstitutionTag("unsubscribe")) +tracking_settings.ganalytics = Ganalytics( + True, + UtmSource("utm_source"), + UtmMedium("utm_medium"), + UtmTerm("utm_term"), + UtmContent("utm_content"), + UtmCampaign("utm_campaign")) +message.tracking_settings = tracking_settings +try: + sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message) + print(response.status_code) + print(response.body) + print(response.headers) +except Exception as e: + print(e.message) +``` diff --git a/use_cases/legacy_templates.md b/use_cases/legacy_templates.md new file mode 100644 index 000000000..c8188a257 --- /dev/null +++ b/use_cases/legacy_templates.md @@ -0,0 +1,116 @@ +# Legacy Templates + +For this example, we assume you have created a [legacy transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html) in the UI or via the API. Following is the template content we used for testing. + +Template ID (replace with your own): + +```text +13b8f94f-bcae-4ec6-b752-70d6cb59f932 +``` + +Email Subject: + +```text +<%subject%> +``` + +Template Body: + +```html + + + + + +Hello -name-, +

+I'm glad you are trying out the template feature! +

+<%body%> +

+I hope you are having a great day in -city- :) +

+ + +``` + +## With Mail Helper Class + +```python +import sendgrid +import os +from sendgrid.helpers.mail import Email, Content, Substitution, Mail +try: + # Python 3 + import urllib.request as urllib +except ImportError: + # Python 2 + import urllib2 as urllib + +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) +from_email = Email("test@example.com") +subject = "I'm replacing the subject tag" +to_email = Email("test@example.com") +content = Content("text/html", "I'm replacing the body tag") +mail = Mail(from_email, subject, to_email, content) +mail.personalizations[0].add_substitution(Substitution("-name-", "Example User")) +mail.personalizations[0].add_substitution(Substitution("-city-", "Denver")) +mail.template_id = "13b8f94f-bcae-4ec6-b752-70d6cb59f932" +try: + response = sg.client.mail.send.post(request_body=mail.get()) +except urllib.HTTPError as e: + print (e.read()) + exit() +print(response.status_code) +print(response.body) +print(response.headers) +``` + +## Without Mail Helper Class + +```python +import sendgrid +import os +try: + # Python 3 + import urllib.request as urllib +except ImportError: + # Python 2 + import urllib2 as urllib + +sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) +data = { + "personalizations": [ + { + "to": [ + { + "email": "test@example.com" + } + ], + "substitutions": { + "-name-": "Example User", + "-city-": "Denver" + }, + "subject": "I'm replacing the subject tag" + }, + ], + "from": { + "email": "test@example.com" + }, + "content": [ + { + "type": "text/html", + "value": "I'm replacing the body tag" + } + ], + "template_id": "13b8f94f-bcae-4ec6-b752-70d6cb59f932" +} +try: + response = sg.client.mail.send.post(request_body=data) +except urllib.HTTPError as e: + print (e.read()) + exit() +print(response.status_code) +print(response.body) +print(response.headers) +``` diff --git a/use_cases/send_a_single_email_to_a_single_recipient.md b/use_cases/send_a_single_email_to_a_single_recipient.md new file mode 100644 index 000000000..8a2364285 --- /dev/null +++ b/use_cases/send_a_single_email_to_a_single_recipient.md @@ -0,0 +1,19 @@ +```python +import os +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail + +message = Mail( + from_email='from_email@example.com', + to_emails='to@example.com', + subject='Sending with Twilio SendGrid is Fun', + html_content='and easy to do anywhere, even with Python') +try: + sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message) + print(response.status_code) + print(response.body) + print(response.headers) +except Exception as e: + print(e) +``` diff --git a/use_cases/send_a_single_email_to_multiple_recipients.md b/use_cases/send_a_single_email_to_multiple_recipients.md new file mode 100644 index 000000000..0cde3b56e --- /dev/null +++ b/use_cases/send_a_single_email_to_multiple_recipients.md @@ -0,0 +1,24 @@ +```python +import os +import json +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail + +to_emails = [ + ('test0@example.com', 'Example Name 0'), + ('test1@example.com', 'Example Name 1') +] +message = Mail( + from_email=('from@example.com', 'Example From Name'), + to_emails=to_emails, + subject='Sending with Twilio SendGrid is Fun', + html_content='and easy to do anywhere, even with Python') +try: + sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message) + print(response.status_code) + print(response.body) + print(response.headers) +except Exception as e: + print(e.message) +``` \ No newline at end of file diff --git a/use_cases/send_a_single_email_with_multiple_reply_to_addresses.md b/use_cases/send_a_single_email_with_multiple_reply_to_addresses.md new file mode 100644 index 000000000..55d6adf18 --- /dev/null +++ b/use_cases/send_a_single_email_with_multiple_reply_to_addresses.md @@ -0,0 +1,29 @@ +```python +import os +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail + +message = Mail( + from_email='from_email@example.com', + to_emails='to@example.com', + subject='Sending with Twilio SendGrid is Fun', + html_content='and easy to do anywhere, even with Python') +message.reply_to_list = [ + ReplyTo( + email='reply-to-1@example.com', + name="Reply To Name 1", + ), + ReplyTo( + email='reply-to-2@example.com', + name="Reply To Name 2", + ) +] +try: + sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message) + print(response.status_code) + print(response.body) + print(response.headers) +except Exception as e: + print(e) +``` diff --git a/use_cases/send_multiple_emails_personalizations.md b/use_cases/send_multiple_emails_personalizations.md new file mode 100644 index 000000000..e8a7e2eec --- /dev/null +++ b/use_cases/send_multiple_emails_personalizations.md @@ -0,0 +1,32 @@ +```python +import os +import json +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail, Personalization, From, To, Cc, Bcc + +# Note that the domain for all From email addresses must match +message = Mail( + from_email=('from@example.com', 'Example From Name'), + subject='Sending with Twilio SendGrid is Fun', + html_content='and easy to do anywhere, even with Python') + +personalization1 = Personalization() +personalization1.add_email(To('test0@example.com', 'Example Name 0')) +personalization1.add_email(Cc('test1@example.com', 'Example Name 1')) +message.add_personalization(personalization1) + +personalization2 = Personalization() +personalization2.add_email(To('test2@example.com', 'Example Name 2')) +personalization2.add_email(Bcc('test3@example.com', 'Example Name 3')) +personalization2.add_email(From('from2@example.com', 'Example From Name 2')) +message.add_personalization(personalization2) + +try: + sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message) + print(response.status_code) + print(response.body) + print(response.headers) +except Exception as e: + print(e.message) +``` \ No newline at end of file diff --git a/use_cases/send_multiple_emails_to_multiple_recipients.md b/use_cases/send_multiple_emails_to_multiple_recipients.md new file mode 100644 index 000000000..e3085469d --- /dev/null +++ b/use_cases/send_multiple_emails_to_multiple_recipients.md @@ -0,0 +1,36 @@ +```python +import os +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail, To + +to_emails = [ + To(email='test+to0@example.com', + name='Example Name 0', + dynamic_template_data={ + 'name': 'Dynamic Name 0', + 'url': 'https://example.com/test0', + }, + subject='Override Global Subject'), + To(email='test+to1@example.com', + name='Example Name 1', + dynamic_template_data={ + 'name': 'Dynamic Name 1', + 'url': 'https://example.com/test1', + }), +] +message = Mail( + from_email=('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject='Global subject', + is_multiple=True) +message.template_id = 'd-12345678901234567890123456789012' + +try: + sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message) + print(response.status_code) + print(response.body) + print(response.headers) +except Exception as e: + print(e.message) +``` diff --git a/use_cases/sending_amp_html_content.md b/use_cases/sending_amp_html_content.md new file mode 100644 index 000000000..616b52039 --- /dev/null +++ b/use_cases/sending_amp_html_content.md @@ -0,0 +1,102 @@ +# Sending AMP-HTML Email + +Following is an example on how to send an AMP HTML Email. +Currently, we require AMP HTML and any one of HTML or Plain Text content (preferrably both) for improved deliverability or fallback for AMP HTML Email for supporting older clients and showing alternate content after 30 days. + +For more information on AMP emails pls check the [official AMP email page](https://amp.dev/about/email/) + +```python +import os +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail + +# The below amp html email is taken from [Google AMP Hello World Email](https://amp.dev/documentation/examples/introduction/hello_world_email/) +amp_html_content = ''' + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Hello!

+ + + + + + + + +
+ + +''' + +message = Mail( + from_email='example@example.com', + to_emails='example@example.com', + subject='Sending with Twilio SendGrid is Fun', + html_content='and easy to do anywhere, even with Python', + amp_html_content=amp_html_content) +try: + sg = SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) + response = sg.send(message) + print(response.status_code) + print(response.body) + print(response.headers) +except Exception as e: + print(e.message) +``` \ No newline at end of file diff --git a/use_cases/sending_html_content.md b/use_cases/sending_html_content.md new file mode 100644 index 000000000..4a828e737 --- /dev/null +++ b/use_cases/sending_html_content.md @@ -0,0 +1,56 @@ +# Sending HTML-only Content + + +Currently, we require both HTML and Plain Text content for improved deliverability. In some cases, only HTML may be available. The below example shows how to obtain the Plain Text equivalent of the HTML content. + +## Using `beautifulsoup4` + +```python +import os +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import From, To, Subject, PlainTextContent, HtmlContent, Mail +try: + # Python 3 + import urllib.request as urllib +except ImportError: + # Python 2 + import urllib2 as urllib +from bs4 import BeautifulSoup + +html_text = """ + + +

+ Some + + bad + + HTML + + +

+ + +""" + +sendgrid_client = SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) +from_email = From("from_email@exmaple.com") +to_email = To("to_email@example.com") +subject = Subject("Test Subject") +html_content = HtmlContent(html_text) + +soup = BeautifulSoup(html_text) +plain_text = soup.get_text() +plain_text_content = Content("text/plain", plain_text) + +message = Mail(from_email, to_email, subject, plain_text_content, html_content) + +try: + response = sendgrid_client.send(message=message) + print(response.status_code) + print(response.body) + print(response.headers) +except urllib.HTTPError as e: + print(e.read()) + exit() +``` diff --git a/use_cases/slack_event_api_integration.md b/use_cases/slack_event_api_integration.md new file mode 100644 index 000000000..8ea7bc7c4 --- /dev/null +++ b/use_cases/slack_event_api_integration.md @@ -0,0 +1,48 @@ +# Integrate with Slack Events API + +It's fairly straightforward to integrate Twilio SendGrid with Slack, to allow emails to be triggered by events happening on Slack. + +For this, we make use of the [Official Slack Events API](https://github.com/slackapi/python-slack-events-api), which can be installed using pip. + +To allow our application to get notifications of slack events, we first create a Slack App with Event Subscriptions as described [here](https://github.com/slackapi/python-slack-events-api#--development-workflow) + +Then, we set `SENDGRID_API_KEY` _(which you can create on the Twilio SendGrid dashboard)_ and `SLACK_VERIFICATION_TOKEN` _(which you can get in the App Credentials section of the Slack App)_ as environment variables. + +Once this is done, we can subscribe to [events on Slack](https://api.slack.com/events) and trigger emails when an event occurs. In the example below, we trigger an email to `test@example.com` whenever someone posts a message on Slack that has the word "_help_" in it. + +```python +from slackeventsapi import SlackEventAdapter +from slackclient import SlackClient +import os +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import From, To, Subject, PlainTextContent, HtmlContent, Mail + +sendgrid_client = SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) + + +SLACK_VERIFICATION_TOKEN = os.environ["SLACK_VERIFICATION_TOKEN"] +slack_events_adapter = SlackEventAdapter(SLACK_VERIFICATION_TOKEN, "/slack/events") + +@slack_events_adapter.on("message") +def handle_message(event_data): + message = event_data["event"] + # If the incoming message contains "help", then send an email using Twilio SendGrid + if message.get("subtype") is None and "help" in message.get('text').lower(): + message = "Someone needs your help: \n\n %s" % message["text"] + r = send_email(message) + print(r) + + +def send_email(message): + from_email = From("slack_integration@example.com") + to_email = To("test@example.com") + subject = Subject("Psst... Someone needs help!") + plain_text_content = PlainTextContent(message) + html_content = HtmlContent('{0}message{0}'.format('','')) + message = Mail(from_email, to_email, subject, plain_text_content, html_content) + response = sendgrid_client.send(message=message) + return response.status_code + +# Start the slack event listener server on port 3000 +slack_events_adapter.start(port=3000) +``` diff --git a/use_cases/sms.md b/use_cases/sms.md new file mode 100644 index 000000000..1e51f0a25 --- /dev/null +++ b/use_cases/sms.md @@ -0,0 +1,31 @@ +First, follow the [Twilio Setup](twilio-setup.md) guide for creating a Twilio account and setting up environment variables with the proper credentials. + +Then, install the Twilio Helper Library. + +```bash +pip install twilio +``` + +Finally, send a message. + +```python +import os +from twilio.rest import Client + +account_sid = os.environ.get('TWILIO_ACCOUNT_SID') +auth_token = os.environ.get('TWILIO_AUTH_TOKEN') +from_number = '+15017122661' +to_number ='+15558675310' +body = "Join Earth's mightiest heroes. Like Kevin Bacon." +client = Client(account_sid, auth_token) + +message = client.messages.create( + body=body, + from_=from_number, + to=to_number +) + +print(message.sid) +``` + +For more information, please visit the [Twilio SMS Python documentation](https://www.twilio.com/docs/sms/quickstart/python). diff --git a/use_cases/transactional_templates.md b/use_cases/transactional_templates.md new file mode 100644 index 000000000..460fd65ee --- /dev/null +++ b/use_cases/transactional_templates.md @@ -0,0 +1,110 @@ +# Transactional Templates + +For this example, we assume you have created a [dynamic transactional template](https://sendgrid.com/docs/ui/sending-email/how-to-send-an-email-with-dynamic-transactional-templates/) in the UI or via the API. Following is the template content we used for testing. + +Email Subject: + +```text +{{ subject }} +``` + +Template Body: + +```html + + + + + +Hello {{ name }}, +

+I'm glad you are trying out the template feature! +

+I hope you are having a great day in {{ city }} :) +

+ + +``` + +```python +import os +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail + +message = Mail( + from_email='from_email@example.com', + to_emails='to@example.com', + html_content='and easy to do anywhere, even with Python') +message.dynamic_template_data = { + 'subject': 'Testing Templates', + 'name': 'Some One', + 'city': 'Denver' +} +message.template_id = 'd-f43daeeaef504760851f727007e0b5d0' +try: + sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message) + print(response.status_code) + print(response.body) + print(response.headers) +except Exception as e: + print(e.message) +``` + +## Prevent Escaping Characters + +Per Handlebars' documentation: If you don't want Handlebars to escape a value, use the "triple-stash", {{{ + +> If you include the characters ', " or & in a subject line replacement be sure to use three brackets. + +Email Subject: + +```text +{{{ subject }}} +``` + +Template Body: + +```html + + + + + +Hello {{{ name }}}, +

+I'm glad you are trying out the template feature! +

+<%body%> +

+I hope you are having a great day in {{{ city }}} :) +

+ + +``` + +```python +import os +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail + +message = Mail( + from_email='from_email@example.com', + to_emails='to@example.com', + subject='Sending with SendGrid is Fun', + html_content='and easy to do anywhere, even with Python') +message.dynamic_template_data = { + 'subject': 'Testing Templates & Stuff', + 'name': 'Some "Testing" One', + 'city': 'Denver', +} +message.template_id = 'd-f43daeeaef504760851f727007e0b5d0' +try: + sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message) + print(response.status_code) + print(response.body) + print(response.headers) +except Exception as e: + print(e.message) +``` diff --git a/use_cases/twilio-email.md b/use_cases/twilio-email.md new file mode 100644 index 000000000..c8f0373d8 --- /dev/null +++ b/use_cases/twilio-email.md @@ -0,0 +1,16 @@ +First, follow the [Twilio Setup](twilio-setup.md) guide for creating a Twilio account and setting up environment variables with the proper credentials. + +Then, initialize the Twilio Email Client. + +```python +import sendgrid +import os + +mail_client = sendgrid.TwilioEmailAPIClient(os.environ.get('TWILIO_API_KEY'), os.environ.get('TWILIO_API_SECRET')) + +# or + +mail_client = sendgrid.TwilioEmailAPIClient(os.environ.get('TWILIO_ACCOUNT_SID'), os.environ.get('TWILIO_AUTH_TOKEN')) +``` + +This client has the same interface as the `SendGridAPIClient` client. diff --git a/use_cases/twilio-setup.md b/use_cases/twilio-setup.md new file mode 100644 index 000000000..315cd3b32 --- /dev/null +++ b/use_cases/twilio-setup.md @@ -0,0 +1,54 @@ +## 1. Obtain a Free Twilio Account + +Sign up for a free Twilio account [here](https://www.twilio.com/try-twilio?source=sendgrid-nodejs). + +## 2. Set Up Your Environment Variables + +The Twilio API allows for authentication using with either an API key/secret or your Account SID/Auth Token. You can create an API key [here](https://twil.io/get-api-key) or obtain your Account SID and Auth Token [here](https://twil.io/console). + +Once you have those, follow the steps below based on your operating system. + +### Linux/Mac + +```bash +echo "export TWILIO_API_KEY='YOUR_TWILIO_API_KEY'" > twilio.env +echo "export TWILIO_API_SECRET='YOUR_TWILIO_API_SECRET'" >> twilio.env + +# or + +echo "export TWILIO_ACCOUNT_SID='YOUR_TWILIO_ACCOUNT_SID'" > twilio.env +echo "export TWILIO_AUTH_TOKEN='YOUR_TWILIO_AUTH_TOKEN'" >> twilio.env +``` + +Then: + +```bash +echo "twilio.env" >> .gitignore +source ./twilio.env +``` + +### Windows + +Temporarily set the environment variable (accessible only during the current CLI session): + +```bash +set TWILIO_API_KEY=YOUR_TWILIO_API_KEY +set TWILIO_API_SECRET=YOUR_TWILIO_API_SECRET + +: or + +set TWILIO_ACCOUNT_SID=YOUR_TWILIO_ACCOUNT_SID +set TWILIO_AUTH_TOKEN=YOUR_TWILIO_AUTH_TOKEN +``` + +Or permanently set the environment variable (accessible in all subsequent CLI sessions): + +```bash +setx TWILIO_API_KEY "YOUR_TWILIO_API_KEY" +setx TWILIO_API_SECRET "YOUR_TWILIO_API_SECRET" + +: or + +setx TWILIO_ACCOUNT_SID "YOUR_TWILIO_ACCOUNT_SID" +setx TWILIO_AUTH_TOKEN "YOUR_TWILIO_AUTH_TOKEN" +```