diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE index 7ad590b42..2444957f4 100644 --- a/.github/PULL_REQUEST_TEMPLATE +++ b/.github/PULL_REQUEST_TEMPLATE @@ -1,5 +1,5 @@ -# Fixes # +# Fixes #X ### Checklist - [ ] I have made a material change to the repo (functionality, testing, spelling, grammar) @@ -19,6 +19,5 @@ Closes #2 ### 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. +If you have questions, please send an email to [Twilio SendGrid](mailto:dx@sendgrid.com), or file a GitHub Issue in this repository. diff --git a/.gitignore b/.gitignore index 96232a5bf..e703eeda6 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,8 @@ README.txt .coverage coverage.xml htmlcov -temp*.py -sendgrid.env +temp.py .vscode - +live_test.py +__pycache__ +example.pdf diff --git a/.travis.yml b/.travis.yml index 5ad33bccf..cb4a7a0a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,21 +2,24 @@ language: python sudo: false cache: pip python: -- '2.6' - '2.7' - '3.4' - '3.5' - '3.6' +# Enable 3.7 without globally enabling sudo and dist: xenial for other build jobs +matrix: + include: + - python: 3.7 + dist: xenial + sudo: true env: global: - CC_TEST_REPORTER_ID=$TRAVIS_CODE_CLIMATE_TOKEN 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 @@ -30,12 +33,10 @@ before_script: - chmod +x ./cc-test-reporter - ./cc-test-reporter before-build script: -- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then coverage run -m unittest2 discover; else coverage run -m unittest discover; fi +- coverage run -m unittest discover after_script: - codecov - ./cc-test-reporter after-build --exit-code $? -before_deploy: -- python ./register.py deploy: provider: pypi user: thinkingserious diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c1869666..1a206dfdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ # Change Log All notable changes to this project will be documented in this file. +## [6.0.0] - TBD + +- https://github.com/sendgrid/sendgrid-python/pull/486 +- https://github.com/sendgrid/sendgrid-python/pull/488 +- https://github.com/sendgrid/sendgrid-python/pull/493 +- https://github.com/sendgrid/sendgrid-python/pull/496 +- https://github.com/sendgrid/sendgrid-python/pull/509 +- https://github.com/sendgrid/sendgrid-python/pull/510 +- https://github.com/sendgrid/sendgrid-python/pull/512 +- https://github.com/sendgrid/sendgrid-python/pull/524 + ## [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! @@ -30,7 +41,7 @@ All notable changes to this project will be documented in this file. - [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 unittesting support to .codeclimate.yml. Big thanks to [Prashu Chaudhary](https://github.com/prashuchaudhary) 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! @@ -40,13 +51,13 @@ All notable changes to this project will be documented in this file. - [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 shell command. Big thanks to [Aditya Narayan](https://github.com/aditnryn) 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 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 #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! @@ -54,7 +65,7 @@ Removed the trailing white spaces. Big thanks to [Siddaram Halli](https://github - [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 version in pyenv install instruction. Big thanks to [Krista LaFentres](https://github.com/lafentres) 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! diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 39ed18bf7..31d66b27b 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,6 +1,6 @@ # 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. + 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. @@ -17,7 +17,7 @@ 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. + 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. @@ -30,12 +30,12 @@ **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). + 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) +* [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) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b38f1c1c4..d1df93c3a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,14 +1,27 @@ 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. -- [CLAs and CCLAs](#cla) -- [Roadmap & Milestones](#roadmap) +- [CLAs and CCLAs](#clas-and-cclas) - [Feature Request](#feature-request) - [Submit a Bug Report](#submit-a-bug-report) + - [Please use our Bug Report Template](#please-use-our-bug-report-template) - [Improvements to the Codebase](#improvements-to-the-codebase) -- [Understanding the Code Base](#understanding-the-codebase) + - [Development Environment](#development-environment) + - [There are two ways to get set up:](#there-are-two-ways-to-get-set-up) + - [1. Using Docker](#1-using-docker) + - [- OR -](#or) + - [2. Install and Run Locally](#2-install-and-run-locally) + - [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) -- [Creating a Pull Request](#creating-a-pull-request) + - [Testing Multiple Versions of Python](#testing-multiple-versions-of-python) + - [Prerequisites:](#prerequisites) + - [Initial setup:](#initial-setup-1) + - [Execute:](#execute-1) +- [Style Guidelines & Naming Conventions](#style-guidelines--naming-conventions) +- [Creating a Pull Request](#creating-a-pull-requesta-name%22creating-a-pull-request%22a) - [Code Reviews](#code-reviews) @@ -46,8 +59,8 @@ A software bug is a demonstrable issue in the code base. In order for us to diag 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 +1. Check the GitHub issues tab if the identified issue has already been reported, if so, please add a +1 to the existing post. +2. Update to the latest version of this code and check if the issue has already been fixed 3. Copy and fill in the Bug Report Template we have provided below ### Please use our Bug Report Template @@ -62,7 +75,7 @@ We welcome direct contributions to the sendgrid-python code base. Thank you! ### Development Environment ### #### There are two ways to get set up: #### #### 1. Using Docker #### -This is usually the easiest and fastest way to get set up. +This is usually the easiest and fastest way to get set up. 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). #### - OR - #### @@ -70,7 +83,7 @@ You can use our Docker image to avoid setting up the development environment you ##### Prerequisites ##### -- Python 2.6 through 3.6 +- Python 2.7 and 3.4+ - [python_http_client](https://github.com/sendgrid/python-http-client) ##### Initial setup: ##### @@ -87,9 +100,14 @@ First, get your free SendGrid account [here](https://sendgrid.com/free?source=se 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 +``` + +Then edit `.env` and insert your API key. + +```bash +# You do not need to do this when using Docker Compose +source .env ``` ##### Execute: ##### @@ -107,7 +125,7 @@ Working examples that demonstrate usage. **/tests** -Currently we have unit and profiling tests. +Currently, we have unit and profiling tests. **/sendgrid** @@ -122,12 +140,6 @@ All test files are in the [`test`](https://github.com/sendgrid/sendgrid-python/t 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 @@ -149,7 +161,6 @@ You can install it by yourself in user dir by calling `source test/prism.sh`. 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 @@ -159,7 +170,7 @@ Make sure to change the current working directory to your local version of the r python setup.py install ``` ``` -pyenv local 3.5.0 3.4.3 2.7.11 2.6.9 +pyenv local 3.5.0 3.4.3 2.7.11 pyenv rehash ``` @@ -191,8 +202,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 ``` @@ -240,4 +253,4 @@ If you have any additional questions, please feel free to [email](mailto:dx@send ## 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/FIRST_TIMERS.md b/FIRST_TIMERS.md new file mode 100644 index 000000000..ee7fa3327 --- /dev/null +++ b/FIRST_TIMERS.md @@ -0,0 +1,75 @@ +## Welcome to the SendGrid Open Source Community + If you are new to Open Source, you are at the right place to start with. Contributions are always encouraged & appreciated. Just follow the organisation's Contribution Policies & you are good to go. + ## How to get Started? + - [Explore SendGrid](#explore) + - [Raise Issues(If Found Any)](#issues) + - [Setting up the Development Environment](#setup) + - [Proposing Change through a Pull Request](#pr) + - [Be Patient & Wait for reviews](#reviews) + + + ### Explore SendGrid +Step 1: Get yourself Access to SendGrid API Service absolutely free from [here](https://sendgrid.com/free/?source=sendgrid-python)\ +Step 2: Get familiar with SendGrid Service + - Prerequisites are Python version 2.6, 2.7, 3.4, 3.5 or 3.6 + - Set up your [SendGrid API Key](https://app.sendgrid.com/settings/api_keys) to your local workspace [using](https://github.com/sendgrid/sendgrid-python#setup-environment-variables) + - Install SendGrid to your workspace using `pip install sendgrid` + - Copy & Run few sample programs from [here](https://github.com/sendgrid/sendgrid-python#hello-email) + + + + ### Raise Issues + SendGrid uses GitHub as the content management service so, all the issues related to the project be it some feature request or a bug report, all are reported at the [GitHub Issue Tracker](https://github.com/sendgrid/sendgrid-python/issues)\ + Kindly make sure, to check for any duplicate issues raised by fellow contributors before opening a new issue. Be humble & polite while commenting on issues + - Feature Request\ + In case you feel like something is missing or lacking in the API Service, feel free to share your views & opinions with the community + - Bug Report\ + If you encounter any sort of bug or abnormal behavior, feel free to inform the community after performing the following checks: + - Update to the latest version & check if the bug persists + - Check the Issue Tracker for any similar bug report + + Finally fill up the Bug Report Template & Open the Issue highlighting your encountered bug & detailed steps to regenerate the bug. + + + ### Setting up the Development Environment + - **Using Docker**\ + 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) + + - **Setting up Locally**\ + Step 1: Install the Prerequistes: Any Version of Python(2.6 through 3.6) & [python_http_client](https://github.com/sendgrid/python-http-client)\ + Step 2: Get a local copy of repository using `git clone https://github.com/sendgrid/sendgrid-python.git`\ + Step 3: Set your [SendGrid API Key](https://app.sendgrid.com/settings/api_keys) to your local workspace using\ + `echo "export SENDGRID_API_KEY='YOUR_API_KEY'" > sendgrid.env`\ + `echo "sendgrid.env" >> .gitignore`\ + `source ./sendgrid.env`\ + Step 4: The entire codebase consist of 3 major divisions + - **/examples** contains *Working examples that demonstrate usage* + - **/tests** contains *the unit and profiling tests* + - **/sendgrid** contains *the Web API v3 client ie sendgrid.py and other files*. + + + + ## Proposing Change through a Pull Request + **Step 1:** Fork the project & Clone your fork using `git clone https://github.com//sendgrid-python.git` + + **Step 2:** Reconfigure the remotes using `cd sendgrid-python` and `git remote add upstream https://github.com/sendgrid/sendgrid-python.git` + + **Step 3:** Create a new branch for your modifications using `git checkout -b ` + + **Step 4:** Commit the changes in logical chunks & add commit messages strictly following [this](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) + + **Step 5:** Run all test locally, [for more info](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md#testing) + + **Step 6:** Locally merge your the upstream development branch into your topic-branch using `git pull [--rebase] upstream master` + + **Step 7:** Push the topic branch up to your fork using `git push origin ` + + **Step 8:** Open a Pull Request with clear title and description against the master branch. + + In case, you have additional questions, feel free to drop a [mail](dx@sendgrid.com) or open an issue. + + + ## Be Patient & Wait for Reviews + Kindly be patient & follow the suggestions as provided by the peer reviewers. Make required ammendments & changes to the PR as asked. + +## [Explore New Issues to work upon](https://github.com/sendgrid/sendgrid-python/labels/difficulty%3A%20easy) \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt index 69511d70c..044ba66a3 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2012-2018 SendGrid, Inc. +Copyright (c) 2012-2019 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 diff --git a/MANIFEST.in b/MANIFEST.in index 94d2153e7..21d9e9a11 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,8 @@ include README.rst include LICENSE.txt +include VERSION.txt include app.json include Procfile include requirements.txt +recursive-include sendgrid *.py *.txt recursive-exclude test * diff --git a/README.md b/README.md deleted file mode 100644 index aafb2952e..000000000 --- a/README.md +++ /dev/null @@ -1,224 +0,0 @@ -![SendGrid Logo](https://uiux.s3.amazonaws.com/2016-logos/email-logo%402x.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) -[![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) -[![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) -[![Open Source Helpers](https://www.codetriage.com/sendgrid/sendgrid-python/badges/users.svg)](https://www.codetriage.com/sendgrid/sendgrid-python) - -**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). - -**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. - -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) -* [Processing Inbound Email](#inbound) -* [Usage](#usage) -* [Use Cases](#use-cases) -* [Announcements](#announcements) -* [Roadmap](#roadmap) -* [How to Contribute](#contribute) -* [Troubleshooting](#troubleshooting) -* [About](#about) -* [License](#license) - - - -# Installation - -## Prerequisites - -- Python version 2.6, 2.7, 3.4, 3.5 or 3.6 -- The SendGrid service, starting at the [free level](https://sendgrid.com/free?source=sendgrid-python) - -## Setup Environment Variables - -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. - -## Install Package - -```bash -pip install sendgrid -``` - -## Dependencies - -- [Python-HTTP-Client](https://github.com/sendgrid/python-http-client) - - - -# 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): - -### With Mail Helper Class - -```python -import sendgrid -import os -from sendgrid.helpers.mail import * - -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()) -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. - -### 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): - -```python -import sendgrid -import os - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) -data = { - "personalizations": [ - { - "to": [ - { - "email": "test@example.com" - } - ], - "subject": "Sending with SendGrid is Fun" - } - ], - "from": { - "email": "test@example.com" - }, - "content": [ - { - "type": "text/plain", - "value": "and easy to do anywhere, even with Python" - } - ] -} -response = sg.client.mail.send.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) -``` - -## General v3 Web API Usage (With [Fluent Interface](https://sendgrid.com/blog/using-python-to-implement-a-fluent-interface-to-any-rest-api/)) - -```python -import sendgrid -import os - -sg = sendgrid.SendGridAPIClient(apikey=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) - -```python -import sendgrid -import os - -sg = sendgrid.SendGridAPIClient(apikey=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](https://github.com/sendgrid/sendgrid-python/tree/master/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) -- [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) - - -# Use Cases - -[Examples of common API use cases](https://github.com/sendgrid/sendgrid-python/blob/master/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. - - -# 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. - -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) - - -# Troubleshooting - -Please see our [troubleshooting guide](https://github.com/sendgrid/sendgrid-python/blob/master/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 SendGrid, Inc. The names and logos for sendgrid-python are trademarks of SendGrid, Inc. - - -# License -[The MIT License (MIT)](LICENSE.txt) diff --git a/README.rst b/README.rst new file mode 100644 index 000000000..7a65eb9f3 --- /dev/null +++ b/README.rst @@ -0,0 +1,324 @@ +.. image:: https://uiux.s3.amazonaws.com/2016-logos/email-logo%402x.png + :target: https://www.sendgrid.com + +|Travis Badge| |codecov| |Python Versions| |PyPI Version| |Docker Badge| |Email Notifications Badge| |MIT licensed| |Twitter Follow| |GitHub contributors| |Open Source Helpers| + +**NEW:** + +- Subscribe to email `notifications`_ for releases and breaking changes. +- Version 6.X release is a BREAKING CHANGE, please see the release notes for details. +- Quickly get started with `Docker`_. + +**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`_ endpoints, including the new `v3 /mail/send`_. + +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`_ 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>`__ +- `Processing Inbound Email <#processing-inbound-email>`__ +- `Usage <#usage>`__ +- `Use Cases <#use-cases>`__ +- `Announcements <#announcements>`__ +- `Roadmap <#roadmap>`__ +- `How to Contribute <#how-to-contribute>`__ +- `Troubleshooting <#troubleshooting>`__ +- `About <#about>`__ +- `License <#license>`__ + +Installation +============ + +Prerequisites +------------- + +- Python version 2.7 and 3.4+ +- The SendGrid service, starting at the `free level`_ + +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 + +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`_ + +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 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(e.message) + +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 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(e.message) + +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 +===== + +- `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. + +Announcements +============= + +Join an experienced and passionate team that focuses on making an impact. +`Opportunities abound`_ to grow the product - and grow your career! + +Please see our announcement regarding `breaking changes`_. +Your support is appreciated! + +All updates to this library are documented in our `CHANGELOG`_ and `releases`_. +You may also subscribe to email `release notifications`_ 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`_ and `pull requests `__. +We would love to hear your feedback. + +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`_ +- `Sign the CLA to Create a Pull Request`_ + +Troubleshooting +=============== + +Please see our `troubleshooting guide`_ for common library issues. + +About +===== + +**sendgrid-python** is guided and supported by the SendGrid Developer Experience Team. + +Email the Developer Experience Team `here `__ in case of any queries. + +**sendgrid-python** is maintained and funded by SendGrid, Inc. +The names and logos for **sendgrid-python** are trademarks of SendGrid, Inc. + +License +======= + +`The MIT License (MIT)`_ + +.. _notifications: https://dx.sendgrid.com/newsletter/python +.. _Docker: https://github.com/sendgrid/sendgrid-python/tree/master/docker +.. _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/master/CONTRIBUTING.md +.. _free level: https://sendgrid.com/free?source=sendgrid-python +.. _SENDGRID_API_KEY: https://app.sendgrid.com/settings/api_keys +.. _Python-HTTP-Client: https://github.com/sendgrid/python-http-client +.. _/mail/send Helper: https://github.com/sendgrid/sendgrid-python/tree/master/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/master/sendgrid/helpers/inbound +.. _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 +.. _`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 +.. _Processing Inbound Email: https://github.com/sendgrid/sendgrid-python/tree/master/sendgrid/helpers/inbound +.. _Examples of common API use cases: https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/README.md +.. _Opportunities abound: https://sendgrid.com/careers +.. _breaking changes: https://github.com/sendgrid/sendgrid-python/issues/217 +.. _CHANGELOG: https://github.com/sendgrid/sendgrid-python/blob/master/CHANGELOG.md +.. _releases: https://github.com/sendgrid/sendgrid-python/releases +.. _release notifications: https://dx.sendgrid.com/newsletter/python +.. _CONTRIBUTING: https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md +.. _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 +.. _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 +.. _Sign the CLA to Create a Pull Request: https://cla.sendgrid.com/sendgrid/sendgrid-python +.. _troubleshooting guide: https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md +.. _Developer Experience Team: mailto:dx@sendgrid.com +.. _The MIT License (MIT): https://github.com/sendgrid/sendgrid-python/blob/master/LICENSE.txt + +.. |Travis Badge| image:: https://travis-ci.org/sendgrid/sendgrid-python.svg?branch=master + :target: https://travis-ci.org/sendgrid/sendgrid-python +.. |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/ +.. |codecov| image:: https://img.shields.io/codecov/c/github/sendgrid/sendgrid-python/master.svg?style=flat-square&label=Codecov+Coverage + :target: https://codecov.io/gh/sendgrid/sendgrid-python +.. |Docker Badge| image:: https://img.shields.io/docker/automated/sendgrid/sendgrid-python.svg + :target: https://hub.docker.com/r/sendgrid/sendgrid-python/ +.. |Email Notifications Badge| image:: https://dx.sendgrid.com/badge/python + :target: https://dx.sendgrid.com/newsletter/python +.. |MIT licensed| image:: https://img.shields.io/badge/license-MIT-blue.svg + :target: ./LICENSE.txt +.. |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 29c0c8e89..abb2ff14a 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -21,13 +21,13 @@ All of our examples assume you are using [environment variables](https://github. If you choose to add your 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 SendGrid API Key. ## Error Messages @@ -38,9 +38,9 @@ To read the error message returned by SendGrid's API in Python 2.X: import urllib2 try: - response = sg.client.mail.send.post(request_body=mail.get()) + response = sendgrid_client.send(request_body=mail.get()) except urllib2.HTTPError as e: - print e.read() + print(e.read()) ``` To read the error message returned by SendGrid's API in Python 3.X: @@ -48,9 +48,9 @@ To read the error message returned by SendGrid's API in Python 3.X: ```python import urllib try: - response = sg.client.mail.send.post(request_body=mail.get()) + response = sendgrid_client.send(request_body=mail.get()) except urllib.error.HTTPError as e: - print e.read() + print(e.read()) ``` @@ -77,7 +77,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 @@ -105,10 +105,10 @@ 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/README.md) for examples of error handling. +Please review [our use_cases](https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/README.md) for examples of error handling. diff --git a/USAGE.md b/USAGE.md index 09b78609a..fe005f82a 100644 --- a/USAGE.md +++ b/USAGE.md @@ -3,11 +3,11 @@ This documentation is based on our [OAI specification](https://github.com/sendgr # INITIALIZATION ```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 @@ -56,9 +56,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 +88,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 +105,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 +131,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 +151,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 +171,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 +198,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 +217,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 +240,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 +260,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 +280,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 +291,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 +313,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 +329,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 +356,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 @@ -383,9 +383,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,15 +399,15 @@ 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). @@ -423,9 +423,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 @@ -450,9 +450,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,9 +470,9 @@ 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. @@ -495,9 +495,9 @@ 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. @@ -515,9 +515,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. @@ -537,9 +537,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 +561,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 +577,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 +602,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 +619,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 +634,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 +655,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 +673,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 +689,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 +705,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 +726,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 +739,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 +770,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 +792,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 +819,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 +839,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 +859,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 +880,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 +901,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 +919,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 +940,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 +960,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 +983,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 +1002,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 +1020,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 +1038,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 +1059,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 +1084,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 @@ -1106,9 +1106,9 @@ 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 @@ -1121,9 +1121,9 @@ The contactdb is a database of your contacts for [SendGrid Marketing Campaigns]( ```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 @@ -1137,9 +1137,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).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 @@ -1153,9 +1153,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 +1171,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 +1186,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 +1207,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 +1228,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 +1245,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 +1262,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 +1284,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 +1301,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 +1318,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 +1336,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 @@ -1362,9 +1362,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 +1393,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,13 +1412,13 @@ 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. @@ -1433,9 +1433,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,9 +1450,9 @@ 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 @@ -1465,9 +1465,9 @@ The contactdb is a database of your contacts for [SendGrid Marketing Campaigns]( ```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,7 +1476,7 @@ 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 @@ -1490,9 +1490,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 +1506,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 +1522,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,9 +1540,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).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 @@ -1555,9 +1555,9 @@ The contactdb is a database of your contacts for [SendGrid Marketing Campaigns]( ```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 +1614,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 +1631,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 +1662,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 +1681,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 +1702,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 +1721,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 +1737,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 +1751,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 +1772,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 +1783,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 whitelabel 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 +1793,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 +1808,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. @@ -1832,9 +1832,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. @@ -1851,9 +1851,9 @@ 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. @@ -1874,9 +1874,9 @@ 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. @@ -1894,9 +1894,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).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. @@ -1914,9 +1914,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 +1935,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,9 +1954,9 @@ 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 @@ -1974,9 +1974,9 @@ 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 @@ -1991,9 +1991,9 @@ 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 @@ -2009,9 +2009,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).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 @@ -2027,9 +2027,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 +2045,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 +2067,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,9 +2087,9 @@ 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 @@ -2246,9 +2246,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 @@ -2265,9 +2265,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 @@ -2289,9 +2289,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 @@ -2306,9 +2306,9 @@ Mail settings allow you to tell SendGrid specific things to do to every email th ```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 @@ -2327,9 +2327,9 @@ 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 @@ -2344,9 +2344,9 @@ Mail settings allow you to tell SendGrid specific things to do to every email th ```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 @@ -2366,9 +2366,9 @@ 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 @@ -2383,9 +2383,9 @@ Mail settings allow you to tell SendGrid specific things to do to every email th ```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 @@ -2405,9 +2405,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 @@ -2422,9 +2422,9 @@ Mail settings allow you to tell SendGrid specific things to do to every email th ```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 @@ -2443,9 +2443,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 @@ -2460,9 +2460,9 @@ Mail settings allow you to tell SendGrid specific things to do to every email th ```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 @@ -2481,9 +2481,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 @@ -2498,9 +2498,9 @@ Mail settings allow you to tell SendGrid specific things to do to every email th ```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 @@ -2518,9 +2518,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 @@ -2535,9 +2535,9 @@ Mail settings allow you to tell SendGrid specific things to do to every email th ```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 @@ -2557,9 +2557,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 @@ -2574,9 +2574,9 @@ Mail settings allow you to tell SendGrid specific things to do to every email th ```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 @@ -2597,9 +2597,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 @@ -2616,9 +2616,9 @@ Mail settings allow you to tell SendGrid specific things to do to every email th ```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 +2637,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 @@ -2656,9 +2656,9 @@ 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. @@ -2678,9 +2678,9 @@ 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. @@ -2695,9 +2695,9 @@ By integrating with New Relic, you can send your SendGrid email statistics to yo ```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 @@ -2713,9 +2713,9 @@ API Keys can be used to authenticate the use of [SendGrids v3 Web API](https://s ```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 +2726,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 whitelabeled it will auto verify on creation. Otherwise, an email will be sent to the `from.email`. ### POST /senders @@ -2750,30 +2750,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 whitelabeled 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 whitelabeled 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 +2800,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 whitelabeled it will auto verify on creation. Otherwise, an email will be sent to the `from.email`. ### GET /senders/{sender_id} @@ -2816,15 +2816,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 whitelabeled it will auto verify on creation. Otherwise, an email will be sent to the `from.email`. ### DELETE /senders/{sender_id} @@ -2832,15 +2832,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 whitelabeled it will auto verify on creation. Otherwise, an email will be sent to the `from.email`. ### POST /senders/{sender_id}/resend_verification @@ -2848,9 +2848,9 @@ 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 @@ -2867,9 +2867,9 @@ Parent accounts will see aggregated stats for their account and all subuser acco ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` # SUBUSERS @@ -2897,9 +2897,9 @@ data = { "username": "John@example.com" } response = sg.client.subusers.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## List all Subusers @@ -2916,9 +2916,9 @@ For more information about Subusers: ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve Subuser Reputations @@ -2932,9 +2932,9 @@ This endpoint allows you to request the reputations for your subusers. ```python params = {'usernames': 'test_string'} response = sg.client.subusers.reputations.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 your subusers. @@ -2952,9 +2952,9 @@ For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/ ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve monthly stats for all subusers @@ -2973,9 +2973,9 @@ For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/ ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve the totals for each email statistic metric for all subusers. @@ -2992,9 +2992,9 @@ For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/ ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Enable/disable a subuser @@ -3014,9 +3014,9 @@ data = { } 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a subuser @@ -3033,9 +3033,9 @@ For more information about Subusers: ```python subuser_name = "test_url_param" response = sg.client.subusers._(subuser_name).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update IPs assigned to a subuser @@ -3055,9 +3055,9 @@ data = [ ] 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update Monitor Settings for a subuser @@ -3073,9 +3073,9 @@ data = { } 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Create monitor settings @@ -3091,9 +3091,9 @@ data = { } 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve monitor settings for a subuser @@ -3105,9 +3105,9 @@ Subuser monitor settings allow you to receive a sample of an outgoing message by ```python subuser_name = "test_url_param" response = sg.client.subusers._(subuser_name).monitor.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete monitor settings @@ -3119,9 +3119,9 @@ Subuser monitor settings allow you to receive a sample of an outgoing message by ```python subuser_name = "test_url_param" response = sg.client.subusers._(subuser_name).monitor.delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve the monthly email statistics for a single subuser @@ -3141,9 +3141,9 @@ For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/ 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` # SUPPRESSION @@ -3162,9 +3162,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/User ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete blocks @@ -3191,9 +3191,9 @@ data = { ] } response = sg.client.suppression.blocks.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 block @@ -3209,9 +3209,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/User ```python email = "test_url_param" response = sg.client.suppression.blocks._(email).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a specific block @@ -3227,9 +3227,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/User ```python email = "test_url_param" response = sg.client.suppression.blocks._(email).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all bounces @@ -3248,9 +3248,9 @@ For more information see: ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete bounces @@ -3278,9 +3278,9 @@ data = { ] } response = sg.client.suppression.bounces.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 Bounce @@ -3300,9 +3300,9 @@ For more information see: ```python email = "test_url_param" response = sg.client.suppression.bounces._(email).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a bounce @@ -3323,9 +3323,9 @@ For more information see: 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all invalid emails @@ -3343,9 +3343,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/User ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete invalid emails @@ -3374,9 +3374,9 @@ data = { ] } response = sg.client.suppression.invalid_emails.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 invalid email @@ -3394,9 +3394,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/User ```python email = "test_url_param" response = sg.client.suppression.invalid_emails._(email).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a specific invalid email @@ -3414,9 +3414,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/User ```python email = "test_url_param" response = sg.client.suppression.invalid_emails._(email).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve a specific spam report @@ -3432,9 +3432,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/User ```python email = "test_url_param" response = sg.client.suppression.spam_report._(email).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a specific spam report @@ -3450,9 +3450,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/User ```python email = "test_url_param" response = sg.client.suppression.spam_report._(email).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all spam reports @@ -3468,9 +3468,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/User ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete spam reports @@ -3497,9 +3497,9 @@ data = { ] } response = sg.client.suppression.spam_reports.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 all global suppressions @@ -3513,9 +3513,9 @@ A global suppression (or global unsubscribe) is an email address of a recipient ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` # TEMPLATES @@ -3536,9 +3536,9 @@ data = { "name": "example_name" } response = sg.client.templates.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 transactional templates. @@ -3553,9 +3553,9 @@ Transactional templates are templates created specifically for transactional ema ```python response = sg.client.templates.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Edit a transactional template. @@ -3575,9 +3575,9 @@ data = { } 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve a single transactional template. @@ -3594,9 +3594,9 @@ Transactional templates are templates created specifically for transactional ema ```python template_id = "test_url_param" response = sg.client.templates._(template_id).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a template. @@ -3613,9 +3613,9 @@ Transactional templates are templates created specifically for transactional ema ```python template_id = "test_url_param" response = sg.client.templates._(template_id).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Create a new transactional template version. @@ -3640,9 +3640,9 @@ data = { } 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Edit a transactional template version. @@ -3672,9 +3672,9 @@ data = { 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve a specific transactional template version. @@ -3697,9 +3697,9 @@ For more information about transactional templates, please see our [User Guide]( 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a transactional template version. @@ -3722,9 +3722,9 @@ For more information about transactional templates, please see our [User Guide]( 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Activate a transactional template version. @@ -3748,9 +3748,9 @@ For more information about transactional templates, please see our [User Guide]( 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` # TRACKING SETTINGS @@ -3769,9 +3769,9 @@ For more information about tracking, please see our [User Guide](https://sendgri ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update Click Tracking Settings @@ -3789,9 +3789,9 @@ data = { "enabled": True } response = sg.client.tracking_settings.click.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 Click Track Settings @@ -3806,9 +3806,9 @@ For more information about tracking, please see our [User Guide](https://sendgri ```python response = sg.client.tracking_settings.click.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update Google Analytics Settings @@ -3835,9 +3835,9 @@ data = { "utm_term": "" } response = sg.client.tracking_settings.google_analytics.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 Google Analytics Settings @@ -3856,9 +3856,9 @@ For more information about tracking, please see our [User Guide](https://sendgri ```python response = sg.client.tracking_settings.google_analytics.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update Open Tracking Settings @@ -3878,9 +3878,9 @@ data = { "enabled": True } response = sg.client.tracking_settings.open.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 Open Tracking Settings @@ -3897,9 +3897,9 @@ For more information about tracking, please see our [User Guide](https://sendgri ```python response = sg.client.tracking_settings.open.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update Subscription Tracking Settings @@ -3924,9 +3924,9 @@ data = { "url": "url" } response = sg.client.tracking_settings.subscription.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 Subscription Tracking Settings @@ -3943,9 +3943,9 @@ For more information about tracking, please see our [User Guide](https://sendgri ```python response = sg.client.tracking_settings.subscription.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` # USER @@ -3967,9 +3967,9 @@ For more information about your user profile: ```python response = sg.client.user.account.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve your credit balance @@ -3982,9 +3982,9 @@ Your monthly credit allotment limits the number of emails you may send before in ```python response = sg.client.user.credits.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update your account email address @@ -4004,9 +4004,9 @@ data = { "email": "example@example.com" } response = sg.client.user.email.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 your account email address @@ -4023,9 +4023,9 @@ For more information about your user profile: ```python response = sg.client.user.email.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update your password @@ -4046,9 +4046,9 @@ data = { "old_password": "old_password" } response = sg.client.user.password.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 a user's profile @@ -4072,9 +4072,9 @@ data = { "last_name": "User" } response = sg.client.user.profile.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 a user's profile @@ -4089,9 +4089,9 @@ For more information about your user profile: ```python response = sg.client.user.profile.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Cancel or pause a scheduled send @@ -4111,9 +4111,9 @@ data = { "status": "pause" } response = sg.client.user.scheduled_sends.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 scheduled sends @@ -4126,9 +4126,9 @@ The Cancel Scheduled Sends feature allows the customer to cancel a scheduled sen ```python response = sg.client.user.scheduled_sends.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update user scheduled send information @@ -4145,9 +4145,9 @@ data = { } 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve scheduled send @@ -4161,9 +4161,9 @@ The Cancel Scheduled Sends feature allows the customer to cancel a scheduled sen ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a cancellation or pause of a scheduled send @@ -4177,9 +4177,9 @@ The Cancel Scheduled Sends feature allows the customer to cancel a scheduled sen ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update Enforced TLS settings @@ -4198,9 +4198,9 @@ data = { "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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve current Enforced TLS settings. @@ -4215,9 +4215,9 @@ The Enforced TLS settings specify whether or not the recipient is required to su ```python response = sg.client.user.settings.enforced_tls.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update your username @@ -4237,9 +4237,9 @@ data = { "username": "test_username" } response = sg.client.user.username.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 your username @@ -4256,9 +4256,9 @@ For more information about your user profile: ```python response = sg.client.user.username.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update Event Notification Settings @@ -4290,9 +4290,9 @@ data = { "url": "url" } response = sg.client.user.webhooks.event.settings.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 Event Webhook settings @@ -4309,9 +4309,9 @@ Common uses of this data are to remove unsubscribes, react to spam reports, dete ```python response = sg.client.user.webhooks.event.settings.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Test Event Notification Settings @@ -4329,9 +4329,9 @@ data = { "url": "url" } response = sg.client.user.webhooks.event.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) ``` ## Create a parse setting @@ -4350,9 +4350,9 @@ data = { "url": "http://email.myhosthame.com" } response = sg.client.user.webhooks.parse.settings.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 parse settings @@ -4365,9 +4365,9 @@ The inbound parse webhook allows you to have incoming emails parsed, extracting ```python response = sg.client.user.webhooks.parse.settings.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update a parse setting @@ -4386,9 +4386,9 @@ data = { } 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve a specific parse setting @@ -4402,9 +4402,9 @@ The inbound parse webhook allows you to have incoming emails parsed, extracting ```python hostname = "test_url_param" response = sg.client.user.webhooks.parse.settings._(hostname).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a parse setting @@ -4418,9 +4418,9 @@ The inbound parse webhook allows you to have incoming emails parsed, extracting ```python hostname = "test_url_param" response = sg.client.user.webhooks.parse.settings._(hostname).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieves Inbound Parse Webhook statistics. @@ -4436,9 +4436,9 @@ There are a number of pre-made integrations for the SendGrid Parse Webhook which ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` # WHITELABEL @@ -4472,9 +4472,9 @@ data = { "username": "john@example.com" } response = sg.client.whitelabel.domains.post(request_body=data) -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## List all domain whitelabels. @@ -4491,9 +4491,9 @@ For more information on whitelabeling, please see our [User Guide](https://sendg ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Get the default domain whitelabel. @@ -4513,9 +4513,9 @@ For more information on whitelabeling, please see our [User Guide](https://sendg ```python response = sg.client.whitelabel.domains.default.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## List the domain whitelabel associated with the given user. @@ -4537,9 +4537,9 @@ For more information on whitelabeling, please see our [User Guide](https://sendg ```python response = sg.client.whitelabel.domains.subuser.get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Disassociate a domain whitelabel from a given user. @@ -4561,9 +4561,9 @@ For more information on whitelabeling, please see our [User Guide](https://sendg ```python response = sg.client.whitelabel.domains.subuser.delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update a domain whitelabel. @@ -4583,9 +4583,9 @@ data = { } 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve a domain whitelabel. @@ -4602,9 +4602,9 @@ For more information on whitelabeling, please see our [User Guide](https://sendg ```python domain_id = "test_url_param" response = sg.client.whitelabel.domains._(domain_id).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a domain whitelabel. @@ -4620,9 +4620,9 @@ For more information on whitelabeling, please see our [User Guide](https://sendg ```python domain_id = "test_url_param" response = sg.client.whitelabel.domains._(domain_id).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Associate a domain whitelabel with a given user. @@ -4648,9 +4648,9 @@ data = { } 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Add an IP to a domain whitelabel. @@ -4674,9 +4674,9 @@ 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Remove an IP from a domain whitelabel. @@ -4699,9 +4699,9 @@ For more information on whitelabeling, please see our [User Guide](https://sendg 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Validate a domain whitelabel. @@ -4722,9 +4722,9 @@ For more information on whitelabeling, please see our [User Guide](https://sendg ```python id = "test_url_param" response = sg.client.whitelabel.domains._(id).validate.post() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Create an IP whitelabel @@ -4746,9 +4746,9 @@ data = { "subdomain": "email" } response = sg.client.whitelabel.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) ``` ## Retrieve all IP whitelabels @@ -4766,9 +4766,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/API_ ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve an IP whitelabel @@ -4784,9 +4784,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/API_ ```python id = "test_url_param" response = sg.client.whitelabel.ips._(id).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete an IP whitelabel @@ -4802,9 +4802,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/API_ ```python id = "test_url_param" response = sg.client.whitelabel.ips._(id).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Validate an IP whitelabel @@ -4820,9 +4820,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/API_ ```python id = "test_url_param" response = sg.client.whitelabel.ips._(id).validate.post() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Create a Link Whitelabel @@ -4843,9 +4843,9 @@ data = { } 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve all link whitelabels @@ -4861,9 +4861,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/API_ ```python params = {'limit': 1} response = sg.client.whitelabel.links.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 Default Link Whitelabel @@ -4886,9 +4886,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/API_ ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve Associated Link Whitelabel @@ -4908,9 +4908,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/API_ ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Disassociate a Link Whitelabel @@ -4930,9 +4930,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/API_ ```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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Update a Link Whitelabel @@ -4951,9 +4951,9 @@ 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Retrieve a Link Whitelabel @@ -4969,9 +4969,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/API_ ```python id = "test_url_param" response = sg.client.whitelabel.links._(id).get() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Delete a Link Whitelabel @@ -4987,9 +4987,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/API_ ```python id = "test_url_param" response = sg.client.whitelabel.links._(id).delete() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Validate a Link Whitelabel @@ -5005,9 +5005,9 @@ For more information, please see our [User Guide](https://sendgrid.com/docs/API_ ```python id = "test_url_param" response = sg.client.whitelabel.links._(id).validate.post() -print response.status_code -print response.body -print response.headers +print(response.status_code) +print(response.body) +print(response.headers) ``` ## Associate a Link Whitelabel @@ -5030,7 +5030,8 @@ data = { } 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 +print(response.status_code) +print(response.body) +print(response.headers) ``` + diff --git a/docker-test/Dockerfile b/docker-test/Dockerfile index c17d790d6..140b5df68 100644 --- a/docker-test/Dockerfile +++ b/docker-test/Dockerfile @@ -1,23 +1,20 @@ FROM python:3.6-alpine +WORKDIR /root + ENV OAI_SPEC_URL="https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/oai_stoplight.json" ENV SENDGRID_API_KEY $SENDGRID_API_KEY -RUN apk add --no-cache curl -RUN apk add --update bash && rm -rf /var/cache/apk/* - -# install Prism -WORKDIR /root -ADD https://raw.githubusercontent.com/stoplightio/prism/master/install.sh install.sh -RUN chmod +x ./install.sh && sync && \ - ./install.sh && \ - rm ./install.sh +RUN apk add --update --no-cache bash curl -# set up default sendgrid env -WORKDIR /root +# Install Prism +ADD prism.sh install.sh +RUN sync && bash install.sh +# Set up default SendGrid env RUN mkdir sendgrid-python COPY entrypoint.sh entrypoint.sh RUN chmod +x entrypoint.sh + ENTRYPOINT ["./entrypoint.sh"] CMD ["--mock"] diff --git a/docker-test/README.md b/docker-test/README.md index d44ebf759..e3163b65d 100644 --- a/docker-test/README.md +++ b/docker-test/README.md @@ -1,4 +1,4 @@ -Use Docker to easily try out or contribute to the sendgrid-python library. +Use Docker to easily test the sendgrid-python library. This Docker image contains: - Python 3.6 @@ -20,7 +20,7 @@ This Docker image contains: - `python setup.py install` 2. [Install Docker](https://docs.docker.com/install/) 3. [Setup local environment variable SENDGRID_API_KEY](https://github.com/sendgrid/sendgrid-php#setup-environment-variables) -4. Build Docker image, run Docker container, login to the Docker container +4. Build a Docker image, run Docker container, login to the Docker container - `docker image build --tag="sendgrid/python3.6" ./docker-test` - `docker run -itd --name="sendgrid_python3.6" -v $(pwd):/root/sendgrid-python sendgrid/python3.6 /bin/bash` 5. Run the tests within the Docker container diff --git a/docker-test/entrypoint.sh b/docker-test/entrypoint.sh old mode 100644 new mode 100755 index f64d0cccb..e8ae30ddb --- a/docker-test/entrypoint.sh +++ b/docker-test/entrypoint.sh @@ -5,13 +5,13 @@ 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 & + /prism/bin/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 & + /prism/bin/prism run --spec $OAI_SPEC_URL 2> /dev/null & fi cd sendgrid-python python3.6 setup.py install -pip install pyyaml six werkzeug flask +pip install pyyaml six werkzeug flask python_http_client exec $SHELL diff --git a/docker-test/prism.sh b/docker-test/prism.sh new file mode 100755 index 000000000..46acce8c0 --- /dev/null +++ b/docker-test/prism.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +set -eu + +install () { + +echo "Installing Prism..." + +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 + +mkdir -p ../prism/bin +#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" +DEST=../prism/bin/prism + +if [ -z $LATEST ] ; then + echo "Error requesting. Download binary from ${URL}" + exit 1 +else + curl -L $URL -o $DEST + chmod +x $DEST +fi +} + +if [ -f ../prism/bin/prism ]; then + echo "Prism is already installed." +else + echo "Prism is not installed." + install +fi \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index e891e497c..cf2d36b6b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -2,6 +2,9 @@ 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" +ARG SENDGRID-PYTHON_VERSION +ARG BRANCH_HTTP_CLIENT + # install testing versions of python, including old versions, from deadsnakes RUN set -x \ && apt-get update \ @@ -25,19 +28,20 @@ RUN chmod +x ./install.sh && sync && \ # install pip, tox ADD https://bootstrap.pypa.io/get-pip.py get-pip.py RUN python2.7 get-pip.py && \ + python3.6 get-pip.py && \ pip install tox && \ rm get-pip.py #install pyyaml, six, werkzeug RUN python3.6 -m pip install pyyaml RUN python3.6 -m pip install six -RUN Python3.6 -m pip install werkzeug -RUN Python3.6 -m pip install flask +RUN python3.6 -m pip install werkzeug +RUN python3.6 -m pip install flask # 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 +RUN git clone https://github.com/sendgrid/sendgrid-python.git --branch $SENDGRID-PYTHON_VERSION && \ + git clone https://github.com/sendgrid/python-http-client.git --branch $HTTP-CLIENT_VERSION WORKDIR /root RUN ln -s /root/sources/sendgrid-python/sendgrid && \ ln -s /root/sources/python-http-client/python_http_client diff --git a/docker/Makefile b/docker/Makefile new file mode 100644 index 000000000..76ccb73af --- /dev/null +++ b/docker/Makefile @@ -0,0 +1,20 @@ +stop: + docker-compose stop + +rm: stop + docker-compose stop -fvs + +clean: + docker rmi %(docker images -aq) + +clean_untagged: + docker rmi $(docker images --quiet --filter "dangling=true") 2>/dev/null + +build: + docker-compose up -d + +build-build: + docker-compose up --build -d + +up: rm clean build-build + echo "Sendgrid-python environment is alive :D" diff --git a/docker/README.md b/docker/README.md index a523dc93d..7c3611fd5 100644 --- a/docker/README.md +++ b/docker/README.md @@ -20,6 +20,7 @@ - `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) diff --git a/docker/USAGE.md b/docker/USAGE.md index cd543c402..0d3f90b90 100644 --- a/docker/USAGE.md +++ b/docker/USAGE.md @@ -70,6 +70,63 @@ $ docker run -it -v /path/to/cool-sendgrid-python:/mnt/sendgrid-python sendgrid/ Note that the paths you specify in `-v` must be absolute. +# Docker Compose + + +# Quickstart + +1. Install docker-compose on your machine. +2. Must copy sendgrid.env to .env file. +3. Edit .env file for your versions and paths. +4. Must create env folder for clone yours repo. +5. Have fun! :D + +## Using tag's for versions - DockerHub: + +### Edit variable TAG on .env/env_sample file + +```sh-session +$ sed -ie 's/TAG=latest/TAG=choice_a_version/g' +``` +### Run service using tags + +```sh-session +$ cd /path/to/sendgrid-python/docker +$ docker-compose up -d sendgrid +``` + +## Specifying specific versions: + +### Edit variable TAG on .env/env_sample file + +```sh-session +$ sed -ie 's/SENDGRID_PYTHON_VERSION=vy.x.z/SENDGRID_PYTHON_VERSION=vx.y.z/g' +$ sed -ie 's/HTTP_CLIENT_VERSION=vy.x.z/HTTP_CLIENT_VERSION=vx.y.z/g' +``` + +### Run service + +```sh-session +$ cd /path/to/sendgrid-python/docker +$ docker-compose up -d sendgrid-dev +``` + +## Specifying your own fork: + +### Edit variable TAG on .env/env_sample file + +```sh-session +$ sed -ie 's/TAG=latest/TAG=choice_a_version/g' +$ sed -ie 's/SENDGRID_PYTHON_VERSION=vy.x.z/SENDGRID_PYTHON_VERSION=vx.y.z/g' +``` + +### Run service + +```sh-session +$ cd /path/to/sendgrid-python/docker +$ docker-compose up -d sendgrid-beta +``` + # Testing Testing is easy! Run the container, `cd sendgrid`, and run `tox`. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 000000000..2a435b39f --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,35 @@ +version: "3.3" + +services: + sendgrid: + image: sendgrid/sendgrid-python:${TAG} + restart: unless-stopped + container_name: sendgrid-prod + tty: true + env_file: + - .env + + sendgrid-dev: + build: + context: . + args: + - SENDGRID-PYTHON_VERSION=${SENDGRID_PYTHON_VERSION} + - HTTP-CLIENT_VERSION=${HTTP_CLIENT_VERSION} + restart: unless-stopped + container_name: sendgrid-dev + tty: true + env_file: + - .env + volumes: + - ${PATH_TO_SENDGRID_PYTHON_DEV}:/mnt/sendgrid-python + - ${PATH_TO_HTTP_CLIENT_DEV}:/mnt/python-http-client + + sendgrid-beta: + image: sendgrid/sendgrid-python:${TAG} + restart: unless-stopped + container_name: sendgrid-beta + tty: true + env_file: + - .env + volumes: + - ${PATH_TO_SENDGRID_PYTHON_FORK}:/root/sources/sendgrid-python/sendgrid diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh old mode 100644 new mode 100755 diff --git a/docker/sendgrid.env b/docker/sendgrid.env new file mode 100644 index 000000000..ace58fafa --- /dev/null +++ b/docker/sendgrid.env @@ -0,0 +1,8 @@ +TAG=latest +SENDGRID_PYTHON_VERSION="v3.6.1" +HTTP_CLIENT_VERSION="v1.2.4" +PATH_TO_SENDGRID_PYTHON_DEV=../env/python-dev/sendgrid-python +PATH_TO_HTTP_CLIENT_DEV=../env/python-dev/python-http-client +PATH_TO_SENDGRID_PYTHON_PROD=../env/python-prod/sendgrid-python +PATH_TO_HTTP_CLIENT_PROD=../env/python-prod/python-http-client +PATH_TO_SENDGRID_PYTHON_FORK=../env/python-fork/sendgrid-python diff --git a/examples/helpers/README.md b/examples/helpers/README.md new file mode 100644 index 000000000..95981b497 --- /dev/null +++ b/examples/helpers/README.md @@ -0,0 +1,69 @@ +## 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, subject, to_email, 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](https://github.com/sendgrid/sendgrid-python/blob/master/sendgrid/helpers/mail/mail.py) + +### Creating Personalizations + +To create personalizations, you need a dictionary to store all your email components. See example [here](https://github.com/sendgrid/sendgrid-python/blob/0b683169b08d3a7c204107cd333be33053297e74/examples/helpers/mail_example.py#L47) +After creating a dictionary, you can go ahead and create a `Personalization` object. +``` + mock_personalization = Personalization() + for to_addr in personalization['to_list']: + mock_personalization.add_to(to_addr) +``` + +### 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](https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/attachment.md) + +### Managing Settings + +To configure settings in mail, you can use the `MailSettings` class. The class takes some [parameters](https://github.com/sendgrid/sendgrid-python/blob/master/sendgrid/helpers/mail/mail_settings.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](https://github.com/sendgrid/sendgrid-python/blob/master/examples/helpers/mail_example.py#L118) and parameters and usage [here](https://github.com/sendgrid/sendgrid-python/blob/master/sendgrid/helpers/mail/tracking_settings.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.client.mail.send.post(request_body=data) +``` +Make sure you have [environment variable](https://github.com/sendgrid/sendgrid-python/blob/master/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/mail/mail_example.py b/examples/helpers/mail/mail_example.py index b2de7f0a0..9b7f71e4a 100644 --- a/examples/helpers/mail/mail_example.py +++ b/examples/helpers/mail/mail_example.py @@ -9,211 +9,257 @@ 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")) + ## 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 - return mail.get() + message = Mail(from_email=From('from@example.com.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() -def build_personalization(personalization): - """Build personalization mock instance from a mock dict""" - mock_personalization = Personalization() - for to_addr in personalization['to_list']: - mock_personalization.add_to(to_addr) + except SendGridException as e: + print(e.message) - for cc_addr in personalization['cc_list']: - mock_personalization.add_to(cc_addr) - - for bcc_addr in personalization['bcc_list']: - mock_personalization.add_bcc(bcc_addr) - - for header in personalization['headers']: - mock_personalization.add_header(header) - - for substitution in personalization['substitutions']: - mock_personalization.add_substitution(substitution) - - for arg in personalization['custom_args']: - mock_personalization.add_custom_arg(arg) - - mock_personalization.subject = personalization['subject'] - mock_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_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('dx@sendgrid.com', 'DX') + + message.reply_to = ReplyTo('dx_reply@sendgrid.com', 'DX 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") -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.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, 1, - "https://spamcatcher.sendgrid.com") - return mail_settings + mail_settings.spam_check = SpamCheck(True, SpamThreshold(5), SpamUrl("https://example.com")) + message.mail_settings = 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() + 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.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) + message = build_hello_email() + sendgrid_client = SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message=message) print(response.status_code) - print(response.headers) print(response.body) + print(response.headers) 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) + message = build_kitchen_sink() + sendgrid_client = SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message=message) print(response.status_code) - print(response.headers) print(response.body) + print(response.headers) -# this will actually send an email -send_hello_email() +## 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() +## 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..f3196881a --- /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 move this file to the root directory of this project to execute properly. + +# 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(apikey=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/mail/mail.py b/examples/mail/mail.py index e853d422c..b04304ec8 100644 --- a/examples/mail/mail.py +++ b/examples/mail/mail.py @@ -28,7 +28,7 @@ # 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). +# [here](https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/README.md). data = { "asm": { diff --git a/live_test.py b/live_test.py new file mode 100644 index 000000000..12c52eeed --- /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('dx@sendgrid.com', 'DX'), + to_emails=To('elmer.thomas@sendgrid.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('elmer.thomas@sendgrid.com', 'Elmer SendGrid'), + To('elmer.thomas@gmail.com', 'Elmer Thomas') +] +message = Mail(from_email=From('dx@sendgrid.com', 'DX'), + 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='elmer.thomas@sendgrid.com', + name='Elmer SendGrid', + substitutions={ + Substitution('-name-', 'Elmer SendGrid'), + 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('dx@sendgrid.com', 'DX'), + 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('dx@sendgrid.com', 'DX') + +message.reply_to = ReplyTo('dx_reply@sendgrid.com', 'DX 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('dx@sendgrid.com', 'DX'), + to_emails=To('elmer.thomas@sendgrid.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) \ No newline at end of file diff --git a/proposals/mail-helper-refactor.md b/proposals/mail-helper-refactor.md index 5a0127fd6..13fefdbd3 100644 --- a/proposals/mail-helper-refactor.md +++ b/proposals/mail-helper-refactor.md @@ -1,3 +1,5 @@ +# 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. @@ -6,21 +8,22 @@ 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(apikey=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()) ``` @@ -113,19 +116,19 @@ msg = Mail(from_email=From('from@example.com', 'From Name'), # 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,12 +187,12 @@ 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') @@ -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,13 +226,13 @@ 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: diff --git a/register.py b/register.py deleted file mode 100644 index 0a7ffe8d8..000000000 --- a/register.py +++ /dev/null @@ -1,20 +0,0 @@ -import pypandoc -from io import open - -output = pypandoc.convert('README.md', 'rst') -with open('README.txt', 'w+') as f: - f.write(output) - -readme_rst = open('./README.txt', 'r', encoding='utf-8').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', encoding='utf-8') as f: - f.write(final_text) diff --git a/requirements.txt b/requirements.txt index 34d770b5b..6ec05dd87 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ -Flask==0.10.1 -PyYAML==3.11 -python-http-client==2.2.1 -six==1.10.0 +Flask==1.0.2 +PyYAML>=4.2b1 +python-http-client==3.1.0 +six==1.11.0 +pytest==3.8.2 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/VERSION.txt b/sendgrid/VERSION.txt new file mode 100644 index 000000000..ade65226e --- /dev/null +++ b/sendgrid/VERSION.txt @@ -0,0 +1 @@ +5.4.1 diff --git a/sendgrid/__init__.py b/sendgrid/__init__.py index 2bbd38b59..a40ac5ecc 100644 --- a/sendgrid/__init__.py +++ b/sendgrid/__init__.py @@ -2,7 +2,7 @@ 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. +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 @@ -15,7 +15,13 @@ Modules to help with common tasks. """ -from .version import __version__ # noqa -# v3 API +import os from .sendgrid import SendGridAPIClient # noqa -from .helpers.mail import Email # noqa +from .helpers.mail import * # noqa +from .helpers.endpoints import * # noqa +from .helpers.inbound import * # noqa +from .helpers.stats import * # noqa + +dir_path = os.path.dirname(os.path.realpath(__file__)) +if os.path.isfile(os.path.join(dir_path, 'VERSION.txt')): + __version__ = open(os.path.join(dir_path, 'VERSION.txt')).read().strip() 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/inbound/README.md b/sendgrid/helpers/inbound/README.md index bc69d1189..93d0817b6 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 diff --git a/sendgrid/helpers/inbound/config.py b/sendgrid/helpers/inbound/config.py index d0c6517bc..32bec0793 100644 --- a/sendgrid/helpers/inbound/config.py +++ b/sendgrid/helpers/inbound/config.py @@ -15,7 +15,7 @@ def __init__(self, **opts): self.path = opts.get( 'path', os.path.abspath(os.path.dirname(__file__)) ) - with open(self.path + '/config.yml') as stream: + with open('{0}/config.yml'.format(self.path)) as stream: config = yaml.load(stream) self._debug_mode = config['debug_mode'] self._endpoint = config['endpoint'] @@ -28,8 +28,9 @@ 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('=') diff --git a/sendgrid/helpers/inbound/send.py b/sendgrid/helpers/inbound/send.py index 6de575aab..8dbfa68d2 100644 --- a/sendgrid/helpers/inbound/send.py +++ b/sendgrid/helpers/inbound/send.py @@ -37,9 +37,11 @@ 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') @@ -54,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/mail/README.md b/sendgrid/helpers/mail/README.md index 09e3e2035..d4f67ed50 100644 --- a/sendgrid/helpers/mail/README.md +++ b/sendgrid/helpers/mail/README.md @@ -2,14 +2,9 @@ # 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](https://github.com/sendgrid/sendgrid-python/tree/master/use_cases) +-The comple v3 API Documentation can be found [here](https://sendgrid.com/docs/API_Reference/api_v3.html) \ No newline at end of file diff --git a/sendgrid/helpers/mail/__init__.py b/sendgrid/helpers/mail/__init__.py index 1dd769e99..15cc1cc7e 100644 --- a/sendgrid/helpers/mail/__init__.py +++ b/sendgrid/helpers/mail/__init__.py @@ -1,24 +1,59 @@ -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_list_management import BypassListManagement 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 .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 .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 .validators import ValidateAPIKey +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/asm.py b/sendgrid/helpers/mail/asm.py index 333f3e98c..62db8372a 100644 --- a/sendgrid/helpers/mail/asm.py +++ b/sendgrid/helpers/mail/asm.py @@ -1,13 +1,17 @@ -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 = None self._groups_to_display = None @@ -22,40 +26,55 @@ def __init__(self, group_id=None, groups_to_display=None): 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..de9960ca2 --- /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): + """A unix timestamp. + + :rtype: string + """ + return self._batch_id + + @batch_id.setter + def batch_id(self, value): + """A unix timestamp. + + :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 SendAt 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_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/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 cff8ac498..88bab33d0 100644 --- a/sendgrid/helpers/mail/content.py +++ b/sendgrid/helpers/mail/content.py @@ -1,4 +1,5 @@ -from .validators import ValidateAPIKey +from .validators import ValidateApiKey + class Content(object): """Content to be included in your email. @@ -6,50 +7,62 @@ class Content(object): 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 = None - self._value = None - self._validator = ValidateAPIKey() + self._mime_type = None + self._content = None + self._validator = ValidateApiKey() - if type_ is not None: - self.type = type_ + if mime_type is not None: + self.mime_type = mime_type - if value is not None: - self.value = value + 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". :rtype: string """ - return self._type + return self._mime_type + + @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". - @type.setter - def type(self, value): - self._type = value + :param value: The MIME type of the content you are including in your + email. + For example, "text/plain" or "text/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): + @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._value = value + self._content = value def get(self): """ @@ -59,9 +72,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..d682dbf2d --- /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-serializeable structure. + + :param dynamic_template_data: Data for a transactional template. + :type dynamic_template_data: A JSON-serializeable 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-serializeable 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-serializeable 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-serializeable 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-serializeable structure. + """ + return self.dynamic_template_data diff --git a/sendgrid/helpers/mail/email.py b/sendgrid/helpers/mail/email.py index 0cc633986..5f2d541a5 100644 --- a/sendgrid/helpers/mail/email.py +++ b/sendgrid/helpers/mail/email.py @@ -3,27 +3,69 @@ except ImportError: import email.utils as rfc822 +import sys +if sys.version_info[:3] >= (3, 5, 0): + import html + html_entity_decode = html.unescape +else: + try: + # Python 2.6-2.7 + from HTMLParser import HTMLParser + except ImportError: + # Python < 3.5 + from html.parser import HTMLParser + __html_parser__ = HTMLParser() + html_entity_decode = __html_parser__.unescape + 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): """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 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 """ + self._name = None + self._email = None + self._substitutions = None + self._subject = None + self._personalization = None + 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 + + if substitutions is not None: + self.substitutions = substitutions + + if subject is not None: + self.subject = subject + + if p is not None: + self.personalization = p @property def name(self): @@ -35,6 +77,19 @@ 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, str)): + raise TypeError('name must be of type string.') + + # Escape common CSV delimiters as workaround for + # https://github.com/sendgrid/sendgrid-python/issues/578 + if value is not None and (',' in value or ';' in value): + value = html_entity_decode(value) + value = '"' + value + '"' self._name = value @property @@ -44,29 +99,98 @@ 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 subject(self): + """Subject for this sender or recipient. + + :rtype: string """ - email = {} - if self.name is not None: - email["name"] = self.name + return self._subject - if self.email is not None: - email["email"] = self.email - return email + @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 +207,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 index ab4dd9c0c..db17848c5 100644 --- a/sendgrid/helpers/mail/exceptions.py +++ b/sendgrid/helpers/mail/exceptions.py @@ -2,21 +2,64 @@ # Various types of extensible SendGrid related exceptions ################################################################ + class SendGridException(Exception): """Wrapper/default SendGrid-related exception""" pass -class APIKeyIncludedException(SendGridException): - """Exception raised for when SendGrid API Key included in message text - Attributes: - expression -- input expression in which the error occurred - message -- explanation of the error - """ +class ApiKeyIncludedException(SendGridException): + """Exception raised for when SendGrid API Key included in message text""" - def __init__(self, - expression="Email body", + def __init__(self, + expression="Email body", message="SendGrid API Key detected"): - self.expression = expression - self.message = message + """Create an exception for when 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..c5c0d6995 --- /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 FileContente. + + :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..143ab6853 100644 --- a/sendgrid/helpers/mail/footer_settings.py +++ b/sendgrid/helpers/mail/footer_settings.py @@ -11,9 +11,18 @@ def __init__(self, enable=None, text=None, html=None): :param html: HTML content of this footer :type html: string, 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 b955b7241..f1e88f4b9 100644 --- a/sendgrid/helpers/mail/ganalytics.py +++ b/sendgrid/helpers/mail/ganalytics.py @@ -41,8 +41,8 @@ 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 field: string + :param value: Value to be set, ignored if None :type value: Any """ if value is not None: @@ -58,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 @@ -83,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 @@ -95,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 @@ -107,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 @@ -119,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): @@ -136,6 +168,9 @@ def get(self): for key in keys: value = getattr(self, key, None) if value is not None: - ganalytics[key] = value + if isinstance(value, bool): + 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 7c031465d..7f3bd4c4d 100644 --- a/sendgrid/helpers/mail/header.py +++ b/sendgrid/helpers/mail/header.py @@ -7,21 +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 = 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): @@ -33,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 @@ -45,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 6554d4508..3636ac653 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -1,397 +1,990 @@ -"""v3/mail/send response body builder""" -from .personalization import Personalization +"""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 .email import Email +from .from_email import From from .header import Header +from .html_content import HtmlContent +from .mime_type import MimeType +from .personalization import Personalization +from .plain_text_content import PlainTextContent +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 +from .dynamic_template_data import DynamicTemplateData class Mail(object): - """A request to be sent with the SendGrid v3 Mail Send API (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 any parameters are not supplied, they must be set after initialization. - :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 + """Creates the response body for v3/mail/send""" + def __init__( + self, + from_email=None, + to_emails=None, + subject=None, + plain_text_content=None, + 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, tuple, 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 + """ + 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 = [] - - if from_email: - self.from_email = from_email + self._send_at = None + self._subject = None + self._template_id = None + self._tracking_settings = None - if subject: + # 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 - - if to_email: - personalization = Personalization() - personalization.add_to(to_email) - self.add_personalization(personalization) - - if content: - self.add_content(content) + if plain_text_content is not None: + self.add_content(plain_text_content, MimeType.text) + 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 + append_to = append_to or [] + append_to.insert(index, new_items) + return append_to - if self.personalizations: - mail["personalizations"] = [ - personalization.get() - for personalization in self.personalizations - ] + def _ensure_insert(self, new_items, insert_to): + """Ensure an item is inserted to a list or create a new empty list - if self.contents: - mail["content"] = [ob.get() for ob in self.contents] - - if self.attachments: - mail["attachments"] = [ob.get() for ob in self.attachments] - - if self.template_id is not None: - mail["template_id"] = self.template_id - - if self.sections: - sections = {} - for key in self.sections: - sections.update(key.get()) - mail["sections"] = sections - - if self.headers: - headers = {} - for key in self.headers: - headers.update(key.get()) - mail["headers"] = headers + :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.categories: - mail["categories"] = [category.get() for category in - self.categories] + def _flatten_dicts(self, dicts): + """Flatten a dict - if self.custom_args: - custom_args = {} - for key in self.custom_args: - custom_args.update(key.get()) - mail["custom_args"] = custom_args + :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.send_at is not None: - mail["send_at"] = self.send_at + def _get_or_none(self, from_obj): + """Get the JSON representation of the object, else return None - if self.batch_id is not None: - mail["batch_id"] = self.batch_id + :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 personilization 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.asm is not None: - mail["asm"] = self.asm.get() + @property + def personalizations(self): + """A list of one or more Personaliztion objects - if self.ip_pool_name is not None: - mail["ip_pool_name"] = self.ip_pool_name + :rtype: list(Personalization) + """ + return self._personalizations - if self.mail_settings is not None: - mail["mail_settings"] = self.mail_settings.get() + def add_personalization(self, personalization, index=0): + """Add a Personaliztion object - if self.tracking_settings is not None: - mail["tracking_settings"] = self.tracking_settings.get() + :param personalizations: Add a Personalization object + :type personalizations: Personalization + :param index: The index where to add the Personalization + :type index: int + """ + self._personalizations = self._ensure_append( + personalization, self._personalizations, index) - if self.reply_to is not None: - mail["reply_to"] = self.reply_to.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: An To or list of To objects + :type to_emails: To, list(To), str, tuple + :param global_substitutions: A dict of substitutions for all recipients + :type global_substitutions: dict + :param is_multiple: Create a new personilization 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_emails: A To object + :type to_emails: To, str, tuple + :param global_substitutions: A dict of substitutions for all recipients + :type global_substitutions: dict + :param is_multiple: Create a new personilization for each recipient + :type is_multiple: bool + :param p: p is the Personalization object or Personalization object + index + :type p: Personalization, integer, optional + """ - return mail + if isinstance(to_email, list): + for email in to_email: + if isinstance(email, str): + email = To(email, None) + if isinstance(email, tuple): + email = To(email[0], email[1]) + 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 personilization 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 personilization 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 personilization 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 personilization 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 + + :rtype: list(Header) + """ + return self._headers + + @property + def header(self): + pass - 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. + @header.setter + def header(self, headers): + """Add headers to the email - :rtype: int + :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 - :rtype: ASM + @custom_arg.setter + def custom_arg(self, custom_arg): + """Add custom args to the email + + :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 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-serializeable 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): - self._reply_to = value - - @property - def personalizations(self): - """The Personalizations applied to this Mail. + """The reply to email address - 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. - - :rtype: list + :param value: The reply to email address + :type value: ReplyTo, str, tuple """ - return self._personalizations - - def add_personalization(self, personalizations): - """Add a new Personalization to this Mail. - - :type personalizations: Personalization - """ - self._personalizations.append(personalizations) + if isinstance(value, str): + value = ReplyTo(value, None) + if isinstance(value, tuple): + value = ReplyTo(value[0], value[1]) + self._reply_to = 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 """ - if self._contents is None: - self._contents = [] - - # Text content should be before HTML content - if content._type == "text/plain": - self._contents.insert(0, content) + if isinstance(content, str): + content = Content(mime_type, content) + # Content of mime type text/plain must always come first + if content.mime_type == "text/plain": + self._contents = self._ensure_insert(content, self._contents) else: - self._contents.append(content) + 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 section: 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): - if self._custom_args is None: - self._custom_args = [] - self._custom_args.append(custom_arg) + @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 + + @property + def tracking_settings(self): + """The tracking settings for this email + + :rtype: TrackingSettings + """ + 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), + } + + 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..45b7db77f 100644 --- a/sendgrid/helpers/mail/mail_settings.py +++ b/sendgrid/helpers/mail/mail_settings.py @@ -1,14 +1,49 @@ 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_list_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_list_management: Whether this MailSettings bypasses list + management + :type bypass_list_management: BypassListManagement, 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_list_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_list_management is not None: + self.bypass_list_management = bypass_list_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,6 +54,11 @@ 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 @@ -31,6 +71,11 @@ 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 @@ -43,6 +88,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 +105,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 +122,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): diff --git a/sendgrid/helpers/mail/mime_type.py b/sendgrid/helpers/mail/mime_type.py new file mode 100644 index 000000000..0d0c9b3b3 --- /dev/null +++ b/sendgrid/helpers/mail/mime_type.py @@ -0,0 +1,5 @@ +class MimeType(object): + """The MIME type of the content included in your email. + """ + text = "text/plain" + html = "text/html" diff --git a/sendgrid/helpers/mail/open_tracking.py b/sendgrid/helpers/mail/open_tracking.py index 633f2d9ed..32d90e841 100644 --- a/sendgrid/helpers/mail/open_tracking.py +++ b/sendgrid/helpers/mail/open_tracking.py @@ -13,8 +13,14 @@ def __init__(self, enable=None, substitution_tag=None): :param substitution_tag: Tag in body to be replaced by tracking pixel. :type substitution_tag: string, 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 8bb4bed0b..835933017 100644 --- a/sendgrid/helpers/mail/personalization.py +++ b/sendgrid/helpers/mail/personalization.py @@ -4,7 +4,7 @@ class Personalization(object): """ def __init__(self): - """Create an empty Personalization.""" + """Create an empty Personalization and initialize member variables.""" self._tos = [] self._ccs = [] self._bccs = [] @@ -13,6 +13,20 @@ 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 + raise ValueError('Please use a To, Cc or Bcc object.') @property def tos(self): @@ -31,6 +45,17 @@ 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.subject: + if isinstance(email.subject, str): + self.subject = email.subject + else: + self.subject = email.subject.get() self._tos.append(email.get()) @property @@ -79,6 +104,7 @@ def subject(self): Char length requirements, according to the RFC: https://stackoverflow.com/a/1592310 + :rtype: string """ return self._subject @@ -123,7 +149,10 @@ def add_substitution(self, substitution): :type substitution: Substitution """ - self._substitutions.append(substitution.get()) + if isinstance(substitution, dict): + self._substitutions.append(substitution) + else: + self._substitutions.append(substitution.get()) @property def custom_args(self): @@ -158,6 +187,19 @@ 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-serializeable structure. + + :rtype: JSON-serializeable structure + """ + return self._dynamic_template_data + + @dynamic_template_data.setter + def dynamic_template_data(self, value): + self._dynamic_template_data = value + def get(self): """ Get a JSON-ready representation of this Personalization. @@ -166,36 +208,23 @@ 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 + + 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..a7fecec8f --- /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 Subjuct. + + :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..08c690b94 100644 --- a/sendgrid/helpers/mail/subscription_tracking.py +++ b/sendgrid/helpers/mail/subscription_tracking.py @@ -4,7 +4,8 @@ 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. @@ -13,13 +14,23 @@ def __init__(self, enable=None, text=None, html=None, substitution_tag=None): :type text: string, 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. + :param substitution_tag: Tag replaced with URL. Overrides text, html + params. :type substitution_tag: string, 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..d0e59837e 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): + """The substitution value. + + :rtype value: string + """ return 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 index b4a69f697..00e3276e4 100644 --- a/sendgrid/helpers/mail/validators.py +++ b/sendgrid/helpers/mail/validators.py @@ -1,18 +1,18 @@ -from .exceptions import APIKeyIncludedException -################################################################ -# Various types of Validators -################################################################ +from .exceptions import ApiKeyIncludedException -class ValidateAPIKey(object): + +class ValidateApiKey(object): """Validates content to ensure SendGrid API key is not present""" regexes = None def __init__(self, regex_strings=None, use_default=True): - """Constructor - Args: - regex_strings (list): list of regex strings - use_default (bool): Whether or not to include default regex + """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 @@ -24,18 +24,18 @@ def __init__(self, regex_strings=None, use_default=True): self.regexes.add(re.compile(regex_string)) if use_default: - default_regex_string = 'SG\.[0-9a-zA-Z]+\.[0-9a-zA-Z]+' + 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 - Args: - request_body (:obj:`dict`): message parameter that is - an argument to: mail.send.post() - Raises: - APIKeyIncludedException: If any content in request_body matches regex + """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 @@ -44,9 +44,9 @@ def validate_message_dict(self, 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 @@ -54,18 +54,16 @@ def validate_message_dict(self, request_body): 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 - Args: - message_string (str): message that will be sent - Raises: - APIKeyIncludedException: If message_string matches a regex string - """ + :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() - + raise ApiKeyIncludedException() diff --git a/sendgrid/helpers/stats/README.md b/sendgrid/helpers/stats/README.md new file mode 100644 index 000000000..4ef738410 --- /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](https://github.com/sendgrid/sendgrid-python/tree/master/examples/helpers/stats) (make sure you have set your environment variable to include your SENDGRID_API_KEY). + +## Usage + +- See the [examples](https://github.com/sendgrid/sendgrid-python/tree/master/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..8fe1399a2 --- /dev/null +++ b/sendgrid/helpers/stats/stats.py @@ -0,0 +1,222 @@ +class Stats(object): + def __init__( + self, start_date=None): + 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): + return str(self.get()) + + def get(self): + """ + :return: 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): + return self._start_date + + @start_date.setter + def start_date(self, value): + self._start_date = value + + @property + def end_date(self): + return self._end_date + + @end_date.setter + def end_date(self, value): + self._end_date = value + + @property + def aggregated_by(self): + return self._aggregated_by + + @aggregated_by.setter + def aggregated_by(self, value): + self._aggregated_by = value + + @property + def sort_by_metric(self): + return self._sort_by_metric + + @sort_by_metric.setter + def sort_by_metric(self, value): + self._sort_by_metric = value + + @property + def sort_by_direction(self): + return self._sort_by_direction + + @sort_by_direction.setter + def sort_by_direction(self, value): + self._sort_by_direction = value + + @property + def limit(self): + return self._limit + + @limit.setter + def limit(self, value): + self._limit = value + + @property + def offset(self): + return self._offset + + @offset.setter + def offset(self, value): + self._offset = value + + +class CategoryStats(Stats): + def __init__(self, start_date=None, categories=None): + 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): + """ + :return: 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 + if self.categories is not None: + stats['categories'] = [category.get() for category in + self.categories] + return stats + + @property + def categories(self): + return self._categories + + def add_category(self, category): + if self._categories is None: + self._categories = [] + self._categories.append(category) + + +class SubuserStats(Stats): + def __init__(self, start_date=None, subusers=None): + 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): + """ + :return: 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 + if self.subusers is not None: + stats['subusers'] = [subuser.get() for subuser in + self.subusers] + return stats + + @property + def subusers(self): + return self._subusers + + def add_subuser(self, subuser): + if self._subusers is None: + self._subusers = [] + self._subusers.append(subuser) + + +class Category(object): + + def __init__(self, name=None): + self._name = None + if name is not None: + self._name = name + + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + + def get(self): + return self.name + + +class Subuser(object): + + def __init__(self, name=None): + self._name = None + if name is not None: + self._name = name + + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + + def get(self): + return self.name diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index 0f09bd542..166466d02 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -18,14 +18,12 @@ 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')) + sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ... mail = Mail(from_email, subject, to_email, content) response = sg.client.mail.send.post(request_body=mail.get()) @@ -36,50 +34,44 @@ class SendGridAPIClient(object): def __init__( self, - apikey=None, api_key=None, - impersonate_subuser=None, host='https://api.sendgrid.com', - **opts): # TODO: remove **opts for 6.x release + impersonate_subuser=None): """ Construct SendGrid v3 API object. - Note that underlying client being set up during initialization, therefore changing - attributes in runtime will not affect HTTP client behaviour. - - :param apikey: SendGrid API key to use. If not provided, key will be read from - environment variable "SENDGRID_API_KEY" - :type apikey: basestring - :param api_key: SendGrid API key to use. Provides backward compatibility - .. deprecated:: 5.3 - Use apikey instead - :type api_key: basestring - :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: basestring + 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: SendGrid API key to use. If not provided, key 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: basestring - :param opts: dispatcher for deprecated arguments. Added for backward-compatibility - with `path` parameter. Should be removed during 6.x release + :type host: string """ - if opts: - warnings.warn( - 'Unsupported argument(s) provided: {}'.format(list(opts.keys())), - DeprecationWarning) - self.apikey = apikey or api_key or os.environ.get('SENDGRID_API_KEY') + from . import __version__ + self.api_key = api_key or os.environ.get('SENDGRID_API_KEY') self.impersonate_subuser = impersonate_subuser self.host = host - self.useragent = 'sendgrid/{0};python'.format(__version__) self.version = __version__ + self.useragent = 'sendgrid/{};python'.format(self.version) - self.client = python_http_client.Client(host=self.host, - request_headers=self._default_headers, - version=3) + 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 SendGrid v3 API call""" headers = { - "Authorization": 'Bearer {0}'.format(self.apikey), + "Authorization": 'Bearer {}'.format(self.api_key), "User-agent": self.useragent, "Accept": 'application/json' } @@ -89,17 +81,19 @@ def _default_headers(self): return headers def reset_request_headers(self): + self.client.request_headers = self._default_headers - @property - def api_key(self): - """ - Alias for reading API key - .. deprecated:: 5.3 - Use apikey instead - """ - return self.apikey + def send(self, message): + """Make a SendGrid v3 API request with the request body generated by + the Mail object - @api_key.setter - def api_key(self, value): - self.apikey = value + :param message: The SendGrid v3 API request body generated by the Mail + object + :type message: Mail + """ + if isinstance(message, dict): + response = self.client.mail.send.post(request_body=message) + else: + response = self.client.mail.send.post(request_body=message.get()) + return response diff --git a/sendgrid/version.py b/sendgrid/version.py deleted file mode 100644 index c191754f8..000000000 --- a/sendgrid/version.py +++ /dev/null @@ -1,2 +0,0 @@ -version_info = (5, 4, 1) -__version__ = '.'.join(str(v) for v in version_info) diff --git a/setup.py b/setup.py index 014691b61..426222575 100644 --- a/setup.py +++ b/setup.py @@ -1,44 +1,40 @@ -import sys +import io import os -from io import open +from distutils.file_util import copy_file 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', 'r', encoding='utf-8').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 + +dir_path = os.path.abspath(os.path.dirname(__file__)) +readme = io.open(os.path.join(dir_path, 'README.rst'), encoding='utf-8').read() +version = io.open(os.path.join(dir_path, 'sendgrid/VERSION.txt'), encoding='utf-8').read().strip() +copy_file(os.path.join(dir_path, 'sendgrid', 'VERSION.txt'), + os.path.join(dir_path, 'sendgrid', 'VERSION.txt'), + verbose=0) + setup( name='sendgrid', - version=str(__version__), + version=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"]), + packages=find_packages(exclude=["temp*.py", "test"]), include_package_data=True, license='MIT', description='SendGrid library for Python', - long_description=long_description, + long_description=readme, install_requires=getRequires(), + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', 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', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6' + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', ] ) diff --git a/test/test_app.py b/test/test_app.py index 1a8e4a698..56027d570 100644 --- a/test/test_app.py +++ b/test/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/test_config.py b/test/test_config.py index 301bacc92..81c196fb2 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -1,10 +1,8 @@ import os +import unittest + 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): @@ -39,11 +37,11 @@ def test_initialization(self): self.assertTrue(host, self.config.host) self.assertTrue(port, self.config.port) for key in keys: - self.assertTrue(key in self.config.keys) + self.assertIn(key, 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' + 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() diff --git a/test/test_email.py b/test/test_email.py index 902c59d4e..665d7de52 100644 --- a/test/test_email.py +++ b/test/test_email.py @@ -1,13 +1,8 @@ # -*- 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): @@ -40,7 +35,7 @@ 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") @@ -58,3 +53,10 @@ def test_empty_obj_add_email(self): email.email = address self.assertEqual(email.email, address) + + def test_add_name_with_comma(self): + email = Email() + name = "Name, Some" + email.name = name + + self.assertEqual(email.name, '"' + name + '"') diff --git a/test/test_send.py b/test/test_inbound_send.py similarity index 57% rename from test/test_send.py rename to test/test_inbound_send.py index 16d496b85..19ee5de1d 100644 --- a/test/test_send.py +++ b/test/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/test_mail.py b/test/test_mail.py deleted file mode 100644 index 7721b5205..000000000 --- a/test/test_mail.py +++ /dev/null @@ -1,564 +0,0 @@ -# -*- coding: utf-8 -*- -import json - -from sendgrid.helpers.mail import ( - ASM, - APIKeyIncludedException, - Attachment, - BCCSettings, - BypassListManagement, - Category, - ClickTracking, - Content, - CustomArg, - Email, - FooterSettings, - Ganalytics, - Header, - Mail, - MailSettings, - OpenTracking, - Personalization, - SandBoxMode, - Section, - SendGridException, - SpamCheck, - SubscriptionTracking, - Substitution, - TrackingSettings, - ValidateAPIKey -) - -try: - import unittest2 as unittest -except ImportError: - import unittest - - -class UnitTests(unittest.TestCase): - - def test_sendgridAPIKey(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 as e: - pass - - #Exception not thrown - else: - self.fail("Should have failed as SendGrid API key included") - - - def test_helloEmail(self): - self.max_diff = 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_helloEmailAdditionalContent(self): - """Tests bug found in Issue-451 with Content ordering causing a crash""" - - 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/html", "some text here")) - mail.add_content(Content("text/plain", "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.max_diff = 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.max_diff = 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}} - ) - - def test_directly_setting_substitutions(self): - personalization = Personalization() - personalization.substitutions = [{'a': 0}] diff --git a/test/test_mail_helpers.py b/test/test_mail_helpers.py new file mode 100644 index 000000000..5b04d9037 --- /dev/null +++ b/test/test_mail_helpers.py @@ -0,0 +1,972 @@ +# -*- 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, ApiKeyIncludedException, Attachment, BccSettings, + BypassListManagement, Category, ClickTracking, Content, CustomArg, + DynamicTemplateData, Email, FooterSettings, From, Ganalytics, Header, + Mail, MailSettings, OpenTracking, Personalization, SandBoxMode, Section, + SendGridException, SpamCheck, Subject, SubscriptionTracking, Substitution, + TrackingSettings, To, ValidateApiKey +) + + +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_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_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, + BypassListManagement, 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('dx@example.com', 'DX') + + message.reply_to = ReplyTo('dx_reply@example.com', 'DX 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_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 + 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": "dx@example.com", + "name": "DX" + }, + "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_list_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": "dx_reply@example.com", + "name": "DX 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}} + ) diff --git a/test/test_parse.py b/test/test_parse.py index 897b67655..1c899bbbb 100644 --- a/test/test_parse.py +++ b/test/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/test_project.py b/test/test_project.py index a762474ec..d488840f6 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -1,23 +1,15 @@ import os +import unittest -try: - import unittest2 as unittest -except ImportError: - import unittest class ProjectTests(unittest.TestCase): - # ./docker def test_docker_dir(self): - self.assertTrue(os.path.isdir("./docker")) - - # ./docker-test - def test_docker_test_dir(self): - self.assertTrue(os.path.isdir("./docker-test")) + self.assertTrue(os.path.isfile("./docker/Dockerfile")) - # # ./docker-compose.yml or ./docker/docker-compose.yml - # def test_docker_compose(self): - # self.assertTrue(os.path.isfile('docker-compose.yml')) + # ./docker-compose.yml or ./docker/docker-compose.yml + def test_docker_compose(self): + self.assertTrue(os.path.isfile('./docker/docker-compose.yml')) # ./.env_sample def test_env(self): @@ -59,9 +51,9 @@ def test_license(self): def test_pr_template(self): self.assertTrue(os.path.isfile('./.github/PULL_REQUEST_TEMPLATE')) - # ./README.md + # ./README.rst def test_readme(self): - self.assertTrue(os.path.isfile('./README.md')) + self.assertTrue(os.path.isfile('./README.rst')) # ./TROUBLESHOOTING.md def test_troubleshooting(self): @@ -71,9 +63,14 @@ def test_troubleshooting(self): def test_usage(self): self.assertTrue(os.path.isfile('./USAGE.md')) + # ./sendgrid/VERSION.txt + def test_version(self): + self.assertTrue(os.path.isfile('./sendgrid/VERSION.txt')) + # ./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/test_sendgrid.py b/test/test_sendgrid.py index c545cbb2d..e1e12ab6d 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -1,15 +1,9 @@ import sendgrid +from sendgrid.helpers.endpoints.ip.unassigned import unassigned 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 unittest host = "http://localhost:4010" @@ -19,79 +13,26 @@ 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) 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')) + # Support the previous naming convention for API keys + self.assertEqual(self.sg.api_key, self.sg.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' @@ -101,7 +42,7 @@ def test_impersonate_subuser_init(self): 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): @@ -134,19 +75,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._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,7 +860,10 @@ def test_ips_get(self): headers = {'X-Mock': 200} response = self.sg.client.ips.get( query_params=params, request_headers=headers) - self.assertEqual(response.status_code, 200) + data = response.body + unused = unassigned(data) + self.assertEqual(type(unused), list) + self.assertEqual(response.status_code, 200) def test_ips_assigned_get(self): headers = {'X-Mock': 200} @@ -2373,7 +2307,9 @@ def test_license_year(self): LICENSE_FILE = 'LICENSE.txt' 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) + self.assertEqual( + 'Copyright (c) 2012-%s SendGrid, Inc.' % datetime.datetime.now().year, + copyright_line) # @classmethod # def tearDownClass(cls): diff --git a/test/test_spam_check.py b/test/test_spam_check.py new file mode 100644 index 000000000..79a51998b --- /dev/null +++ b/test/test_spam_check.py @@ -0,0 +1,39 @@ +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/tox.ini b/tox.ini index 9336a97b8..926a3c9dd 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 [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,9 @@ 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 diff --git a/use_cases/README.md b/use_cases/README.md index 188464d09..14b36e638 100644 --- a/use_cases/README.md +++ b/use_cases/README.md @@ -4,17 +4,26 @@ This directory provides examples for specific use cases of this library. Please ## Table of Contents -### How-Tos +### 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) +* [Kitchen Sink - an example with all settings used](kitchen_sink.md) +* [Transactional Templates](transational_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) + +### Troubleshooting +* [Error Handling](error_handling.md) +### How-Tos * [How to Create a Django app, Deployed on Heroku, to Send Email with SendGrid](django.md) * [How to Deploy A Simple Hello Email App on AWS](aws.md) -* [How to Setup a Domain Whitelabel](domain_whitelabel.md) +* [How to Deploy a simple Flask app, to send Email with SendGrid, on Heroku](flask_heroku.md) +* [How to Setup a Domain Authentication](domain_authentication.md) * [How to View Email Statistics](email_stats.md) - -### Working with Mail -* [Asynchronous Mail Send](asynchronous_mail_send.md) -* [Attachment](attachment.md) -* [Transactional Templates](transational_templates.md) - -### Library Features -* [Error Handling](error_handling.md) \ No newline at end of file diff --git a/use_cases/asynchronous_mail_send.md b/use_cases/asynchronous_mail_send.md index 57dd61b2a..0e21eacaa 100644 --- a/use_cases/asynchronous_mail_send.md +++ b/use_cases/asynchronous_mail_send.md @@ -5,32 +5,32 @@ 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 * +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Content, Mail, From, To, Mail import os import asyncio -sg = sendgrid.SendGridAPIClient( - apikey=os.getenv("SENDGRID_API_KEY") -) +sendgrid_client = SendGridAPIClient( + api_key=os.environ.get('SENDGRID_API_KEY')) -from_email = Email("test@example.com") -to_email = Email("test1@example.com") +from_email = From("test@example.com") +to_email = To("test1@example.com") -content = Content("text/plain", "This is asynchronous sending test.") +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, "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) +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] @@ -44,7 +44,7 @@ async def send_email(n, email): email: single mail object. ''' try: - response = sg.client.mail.send.post(request_body=email.get()) + 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: diff --git a/use_cases/attachment.md b/use_cases/attachment.md index 86614a009..f23e59b8d 100644 --- a/use_cases/attachment.md +++ b/use_cases/attachment.md @@ -2,9 +2,12 @@ ```python import base64 -import sendgrid import os -from sendgrid.helpers.mail import Email, Content, Mail, Attachment +import json +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import ( + Mail, Attachment, FileContent, FileName, + FileType, Disposition, ContentId) try: # Python 3 import urllib.request as urllib @@ -12,34 +15,35 @@ 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") +import os +import json +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail -file_path = "file_path.pdf" -with open(file_path,'rb') as f: +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') +file_path = 'example.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) +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: - response = sg.client.mail.send.post(request_body=mail.get()) -except urllib.HTTPError as e: - print(e.read()) - exit() + 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) -print(response.status_code) -print(response.body) -print(response.headers) ``` \ No newline at end of file diff --git a/use_cases/aws.md b/use_cases/aws.md index 2ff04bd1f..ff0ff5072 100644 --- a/use_cases/aws.md +++ b/use_cases/aws.md @@ -9,25 +9,25 @@ The neat thing is that CodeStar provides all of this in a pre-configured package 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. +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 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. +*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. +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". +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. +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. +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. For more information about creating API keys, see https://sendgrid.com/docs/Classroom/Send/How_Emails_Are_Sent/api_keys.html @@ -36,15 +36,15 @@ On the next menu, you have the option to choose what programming language you'll 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. +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: +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: +note: this assumes you cloned the Git repo inside your current directory. My directory is: ``` ~/projects/hello-email @@ -100,7 +100,7 @@ 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. +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: ``` @@ -113,18 +113,19 @@ Now go ahead and modify the `index.py` file to match below: ```python import json import datetime -import sendgrid +from sendgrid import SendGridAPIClient import os -from sendgrid.helpers.mail import * +from sendgrid.helpers.mail import (From, To, PlainTextContent, HtmlContent, Mail) 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") + sendgrid_client = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) + from_email = From("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) - response = sg.client.mail.send.post(request_body=mail.get()) + 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) @@ -157,16 +158,16 @@ $ 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. +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: +Scroll down to the "Environment Variables" section. Here we need to populate our 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. +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! \ No newline at end of file +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 index 554e5fa21..dfd658f48 100644 --- a/use_cases/django.md +++ b/use_cases/django.md @@ -54,23 +54,24 @@ import os from django.http import HttpResponse -import sendgrid -from sendgrid.helpers.mail import * +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import (From, To, PlainTextContent, HtmlContent, Mail) 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') + 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 SendGrid is Fun' - content = Content( - 'text/plain', + plain_text_content = PlainTextContent( '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()) + 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!') ``` diff --git a/use_cases/domain_authentication.md b/use_cases/domain_authentication.md new file mode 100644 index 000000000..7828f6045 --- /dev/null +++ b/use_cases/domain_authentication.md @@ -0,0 +1,5 @@ +# How to Setup a Domain Whitelabel + +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](https://github.com/sendgrid/sendgrid-python/blob/master/USAGE.md#sender-authentication). + +Find more information about all of SendGrid's domain authentication related documentation [here](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication). \ No newline at end of file diff --git a/use_cases/domain_whitelabel.md b/use_cases/domain_whitelabel.md deleted file mode 100644 index c28ad055d..000000000 --- a/use_cases/domain_whitelabel.md +++ /dev/null @@ -1,5 +0,0 @@ -# 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). \ No newline at end of file diff --git a/use_cases/error_handling.md b/use_cases/error_handling.md index 1d667d1cd..5536259d6 100644 --- a/use_cases/error_handling.md +++ b/use_cases/error_handling.md @@ -1,26 +1,29 @@ # 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. +[Custom exceptions](https://github.com/sendgrid/python-http-client/blob/master/python_http_client/exceptions.py) for `python_http_client` are now supported. Please see [here](https://github.com/sendgrid/python-http-client/blob/master/python_http_client/exceptions.py) for a list of supported exceptions. +There are also email specific exceptions located [here](https://github.com/sendgrid/sendgrid-python/blob/master/sendgrid/helpers/mail/exceptions.py) + ```python - import sendgrid import os - from sendgrid.helpers.mail import * + from sendgrid import SendGridAPIClient + from sendgrid.helpers.mail import (From, To, Subject, PlainTextContent, HtmlContent, Mail) 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) + sendgrid_client = SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) + from_email = From("dx@sendgrid.com") + to_email = To("elmer.thomas@sendgrid.com") + 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, to_email, subject, plain_text_content, html_content) try: - response = sg.client.mail.send.post(request_body=mail.get()) + response = sendgrid_client.send(message) + print(response.status_code) + print(response.body) + print(response.headers) except exceptions.BadRequestsError as e: print(e.body) exit() - print(response.status_code) - print(response.body) - print(response.headers) ``` \ No newline at end of file diff --git a/use_cases/flask_heroku.md b/use_cases/flask_heroku.md new file mode 100644 index 000000000..33d06aabb --- /dev/null +++ b/use_cases/flask_heroku.md @@ -0,0 +1,9 @@ +# Create a Flask app to send email with SendGrid + +This tutorial explains how to deploy a simple Flask app, to send an email using the SendGrid Python SDK, on Heroku. + +1. Create a 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! \ No newline at end of file diff --git a/use_cases/kitchen_sink.md b/use_cases/kitchen_sink.md new file mode 100644 index 000000000..f40136f74 --- /dev/null +++ b/use_cases/kitchen_sink.md @@ -0,0 +1,226 @@ +```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, + BypassListManagement, 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 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('dx@example.com', 'DX') + +message.reply_to = ReplyTo('dx_reply@example.com', 'DX 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_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(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/legacy_templates.md b/use_cases/legacy_templates.md new file mode 100644 index 000000000..0cb5877f4 --- /dev/null +++ b/use_cases/legacy_templates.md @@ -0,0 +1,116 @@ +# Legacy Templates + +For this example, we assume you have created a [legacy template](https://sendgrid.com/docs/ui//sending-email/create-and-edit-legacy-transactional-templates). 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) +``` \ No newline at end of file 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..48ae13b37 --- /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 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_to_multiple_recipients.md b/use_cases/send_a_single_email_to_multiple_recipients.md new file mode 100644 index 000000000..c253951fe --- /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 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_multiple_emails_to_multiple_recipients.md b/use_cases/send_multiple_emails_to_multiple_recipients.md new file mode 100644 index 000000000..7217d0a0b --- /dev/null +++ b/use_cases/send_multiple_emails_to_multiple_recipients.md @@ -0,0 +1,39 @@ +```python +import os +import json +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail, To + +to_emails = [ + To(email='test+to0@example.com', + name='Example Name 0', + substitutions={ + '-name-': 'Example Name Substitution 0', + '-github-': 'https://example.com/test0', + }, + subject='Override Global Subject'), + To(email='test+to1@example.com', + name='Example Name 1', + substitutions={ + '-name-': 'Example Name Substitution 1', + '-github-': 'https://example.com/test1', + }), +] +global_substitutions = {'-time-': '2019-01-01 00:00:00'} +message = Mail( + from_email=('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject='Hi -name-, this is the global subject', + html_content='Hello -name-, your URL is ' + + 'here email sent at -time-', + global_substitutions=global_substitutions, + is_multiple=True) +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/sending_html_content.md b/use_cases/sending_html_content.md new file mode 100644 index 000000000..ba38b19aa --- /dev/null +++ b/use_cases/sending_html_content.md @@ -0,0 +1,57 @@ +# 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 = Email("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) +mail.add_content(plain_content) + +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() +``` \ No newline at end of file diff --git a/use_cases/slack_event_api_integration.md b/use_cases/slack_event_api_integration.md new file mode 100644 index 000000000..828149433 --- /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 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 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. + +``` +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 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/transational_templates.md b/use_cases/transational_templates.md index d3e3a005d..8572d7e05 100644 --- a/use_cases/transational_templates.md +++ b/use_cases/transational_templates.md @@ -2,16 +2,66 @@ 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): +Email Subject: ```text -13b8f94f-bcae-4ec6-b752-70d6cb59f932 +{{ 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 +import json +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%> +{{{ subject }}} ``` Template Body: @@ -22,95 +72,41 @@ Template Body: -Hello -name-, +Hello {{{ name }}},

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

<%body%>

-I hope you are having a great day in -city- :) +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) -``` +import json +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail -## 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" +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: - 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) + 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