From 1936e24d0ae79600fe52aace3b8b6780f2397edd Mon Sep 17 00:00:00 2001 From: d grossman Date: Tue, 17 Oct 2017 10:38:54 -0700 Subject: [PATCH 001/462] Get unassigned IPs #392, made helper function --- sendgrid/helpers/endpoints/__init__.py | 0 sendgrid/helpers/endpoints/ip/__init__.py | 0 .../helpers/endpoints/ip/test_unassigned.py | 80 +++++++++++++++++++ sendgrid/helpers/endpoints/ip/unassigned.py | 52 ++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 sendgrid/helpers/endpoints/__init__.py create mode 100644 sendgrid/helpers/endpoints/ip/__init__.py create mode 100644 sendgrid/helpers/endpoints/ip/test_unassigned.py create mode 100644 sendgrid/helpers/endpoints/ip/unassigned.py 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/test_unassigned.py b/sendgrid/helpers/endpoints/ip/test_unassigned.py new file mode 100644 index 000000000..cc2af3b40 --- /dev/null +++ b/sendgrid/helpers/endpoints/ip/test_unassigned.py @@ -0,0 +1,80 @@ +import json +import pytest + +from .unassigned import unassigned + + +def test_unassigned_ip(): + + ret_json = '''[ { + "ip": "167.89.21.3", + "pools": [ + "pool1", + "pool2" + ], + "whitelabeled": false, + "start_date": 1409616000, + "subusers": [ + "tim@sendgrid.net" + ], + "warmup": false, + "assigned_at": 1482883200 + }, + { + "ip": "192.168.1.1", + "pools": [ + "pool1", + "pool2" + ], + "whitelabeled": false, + "start_date": 1409616000, + "subusers": [ + "tim@sendgrid.net" + ], + "warmup": false, + "assigned_at": 1482883200 + }, + { + "ip": "208.115.214.22", + "pools": [], + "whitelabeled": true, + "rdns": "o1.email.burgermail.com", + "start_date": 1409616000, + "subusers": [], + "warmup": false, + "assigned_at": 1482883200 + }, + { + "ip": "208.115.214.23", + "pools": [], + "whitelabeled": true, + "rdns": "o1.email.burgermail.com", + "start_date": 1409616000, + "subusers": [], + "warmup": false, + "assigned_at": 1482883200 + + } ] + ''' + + def get_all_ip(): + ret_val = json.loads(ret_json) + return ret_val + + data = {"208.115.214.23", "208.115.214.22"} + + as_json = True + calculated = unassigned(get_all_ip(), as_json=as_json) + calculated = json.loads(calculated) + + for item in calculated: + assert item["ip"] in data + + as_json = False + calculated = unassigned(get_all_ip(), as_json=as_json) + + for item in calculated: + assert item["ip"] in data + + calculated = unassigned(dict(), as_json=as_json) + assert calculated == [] diff --git a/sendgrid/helpers/endpoints/ip/unassigned.py b/sendgrid/helpers/endpoints/ip/unassigned.py new file mode 100644 index 000000000..075f19857 --- /dev/null +++ b/sendgrid/helpers/endpoints/ip/unassigned.py @@ -0,0 +1,52 @@ +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 usera assigned + + + data (response.body from sg.client.ips.get()) + as_json False -> get list of dicts + True -> get json object + + example: + sg = sendgrid.SendGridAPIClient(apikey=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 = unassinged(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 From c8fc9eb87a764ee27b9a0b7a9ea40fd29c3ba1f0 Mon Sep 17 00:00:00 2001 From: d grossman Date: Tue, 17 Oct 2017 10:45:29 -0700 Subject: [PATCH 002/462] break up tests --- .../helpers/endpoints/ip/test_unassigned.py | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/sendgrid/helpers/endpoints/ip/test_unassigned.py b/sendgrid/helpers/endpoints/ip/test_unassigned.py index cc2af3b40..a639f34a9 100644 --- a/sendgrid/helpers/endpoints/ip/test_unassigned.py +++ b/sendgrid/helpers/endpoints/ip/test_unassigned.py @@ -3,15 +3,12 @@ from .unassigned import unassigned - -def test_unassigned_ip(): - - ret_json = '''[ { - "ip": "167.89.21.3", +ret_json = '''[ { + "ip": "167.89.21.3", "pools": [ - "pool1", - "pool2" - ], + "pool1", + "pool2" + ], "whitelabeled": false, "start_date": 1409616000, "subusers": [ @@ -57,9 +54,13 @@ def test_unassigned_ip(): } ] ''' - def get_all_ip(): - ret_val = json.loads(ret_json) - return ret_val +def get_all_ip(): + ret_val = json.loads(ret_json) + return ret_val + + + +def test_unassigned_ip_json(): data = {"208.115.214.23", "208.115.214.22"} @@ -70,11 +71,17 @@ def get_all_ip(): for item in calculated: assert item["ip"] in data +def test_unassigned_ip_obj(): + + data = {"208.115.214.23", "208.115.214.22"} + as_json = False calculated = unassigned(get_all_ip(), as_json=as_json) for item in calculated: assert item["ip"] in data +def test_unassigned_baddata(): + as_json = False calculated = unassigned(dict(), as_json=as_json) assert calculated == [] From df73eb0b066b73fa62963fbb2e925d587d679d18 Mon Sep 17 00:00:00 2001 From: d grossman Date: Tue, 17 Oct 2017 10:53:21 -0700 Subject: [PATCH 003/462] make the python2.6 travis build happy --- sendgrid/helpers/endpoints/ip/test_unassigned.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sendgrid/helpers/endpoints/ip/test_unassigned.py b/sendgrid/helpers/endpoints/ip/test_unassigned.py index a639f34a9..be5904018 100644 --- a/sendgrid/helpers/endpoints/ip/test_unassigned.py +++ b/sendgrid/helpers/endpoints/ip/test_unassigned.py @@ -59,10 +59,17 @@ def get_all_ip(): return ret_val +def make_data(): + data = set() + data.add("208.115.214.23") + data.add("208.115.214.22") + return data + + def test_unassigned_ip_json(): - data = {"208.115.214.23", "208.115.214.22"} + data = make_data() as_json = True calculated = unassigned(get_all_ip(), as_json=as_json) @@ -73,7 +80,7 @@ def test_unassigned_ip_json(): def test_unassigned_ip_obj(): - data = {"208.115.214.23", "208.115.214.22"} + data = make_data() as_json = False calculated = unassigned(get_all_ip(), as_json=as_json) From 9f0932f4634f5abeb24947d6078e6d49558f5a41 Mon Sep 17 00:00:00 2001 From: Aaron Mak Kang Sheng Date: Sun, 22 Oct 2017 10:11:27 +0800 Subject: [PATCH 004/462] Add global stats helper --- examples/helpers/stats/stats_example.py | 27 +++++++ sendgrid/helpers/stats/README.md | 1 + sendgrid/helpers/stats/__init__.py | 1 + sendgrid/helpers/stats/stats.py | 98 +++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 examples/helpers/stats/stats_example.py create mode 100644 sendgrid/helpers/stats/README.md create mode 100644 sendgrid/helpers/stats/__init__.py create mode 100644 sendgrid/helpers/stats/stats.py diff --git a/examples/helpers/stats/stats_example.py b/examples/helpers/stats/stats_example.py new file mode 100644 index 000000000..3d2846e11 --- /dev/null +++ b/examples/helpers/stats/stats_example.py @@ -0,0 +1,27 @@ +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. + + +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 get_global_stats(): + # 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')) + stats_params = build_global_stats() + response = sg.client.stats.get(query_params=stats_params) + print(response.status_code) + print(response.headers) + print(json.dumps(json.loads(response.body), indent=4, sort_keys=True)) + +get_global_stats() \ No newline at end of file diff --git a/sendgrid/helpers/stats/README.md b/sendgrid/helpers/stats/README.md new file mode 100644 index 000000000..2c062c3be --- /dev/null +++ b/sendgrid/helpers/stats/README.md @@ -0,0 +1 @@ +**This helper allows you to quickly and easily build a Stats object for sending your email stats to a database.** \ No newline at end of file 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..398fe7583 --- /dev/null +++ b/sendgrid/helpers/stats/stats.py @@ -0,0 +1,98 @@ +import json +import csv + + +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 From eb65f75e55c8bf5dc0d5ba076ab79f9bf01e4bd0 Mon Sep 17 00:00:00 2001 From: Aaron Mak Kang Sheng Date: Sun, 22 Oct 2017 12:52:30 +0800 Subject: [PATCH 005/462] Add child class CategoryStats --- examples/helpers/stats/stats_example.py | 44 +++++++++++++++-- sendgrid/helpers/stats/stats.py | 64 ++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 5 deletions(-) diff --git a/examples/helpers/stats/stats_example.py b/examples/helpers/stats/stats_example.py index 3d2846e11..8688440d4 100644 --- a/examples/helpers/stats/stats_example.py +++ b/examples/helpers/stats/stats_example.py @@ -5,6 +5,10 @@ # 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 build_global_stats(): global_stats = Stats() @@ -14,14 +18,46 @@ def build_global_stats(): return global_stats.get() +def build_category_stats(): + category_stats = CategoryStats() + 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 get_global_stats(): - # 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')) stats_params = build_global_stats() response = sg.client.stats.get(query_params=stats_params) + response_body = json.loads(response.body)[0]['date'] + print(response.status_code) + print(response.headers) + print(json.dumps(response_body, indent=4, sort_keys=True)) + + +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) + print(json.dumps(json.loads(response.body), indent=4, sort_keys=True)) + + +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) print(json.dumps(json.loads(response.body), indent=4, sort_keys=True)) -get_global_stats() \ No newline at end of file +get_global_stats() +get_category_stats() +get_category_stats_sums() diff --git a/sendgrid/helpers/stats/stats.py b/sendgrid/helpers/stats/stats.py index 398fe7583..8719a9f7d 100644 --- a/sendgrid/helpers/stats/stats.py +++ b/sendgrid/helpers/stats/stats.py @@ -1,7 +1,6 @@ import json import csv - class Stats(object): def __init__( self, start_date=None): @@ -96,3 +95,66 @@ def offset(self): @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 + self.categories = categories + + 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 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 \ No newline at end of file From a1fab6789e21932cf736d8550a474a95e26fb199 Mon Sep 17 00:00:00 2001 From: Aaron Mak Kang Sheng Date: Sun, 22 Oct 2017 12:53:10 +0800 Subject: [PATCH 006/462] fix typo --- examples/helpers/stats/stats_example.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/helpers/stats/stats_example.py b/examples/helpers/stats/stats_example.py index 8688440d4..3862b2097 100644 --- a/examples/helpers/stats/stats_example.py +++ b/examples/helpers/stats/stats_example.py @@ -37,10 +37,9 @@ def build_category_stats_sums(): def get_global_stats(): stats_params = build_global_stats() response = sg.client.stats.get(query_params=stats_params) - response_body = json.loads(response.body)[0]['date'] print(response.status_code) print(response.headers) - print(json.dumps(response_body, indent=4, sort_keys=True)) + print(json.dumps(json.loads(response.body), indent=4, sort_keys=True)) def get_category_stats(): From 67d0b634712d3b8c60f4a9c2a1e5ecf810016e07 Mon Sep 17 00:00:00 2001 From: Aaron Mak Kang Sheng Date: Sun, 22 Oct 2017 13:04:20 +0800 Subject: [PATCH 007/462] Clean up json and refine CategoryStats Class --- examples/helpers/stats/stats_example.py | 19 ++++++++++++------- sendgrid/helpers/stats/stats.py | 3 ++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/examples/helpers/stats/stats_example.py b/examples/helpers/stats/stats_example.py index 3862b2097..41f4b85ab 100644 --- a/examples/helpers/stats/stats_example.py +++ b/examples/helpers/stats/stats_example.py @@ -10,6 +10,10 @@ sg = SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +def pprint_json(json_raw): + print(json.dumps(json.loads(json_raw), indent=4, sort_keys=True)) + + def build_global_stats(): global_stats = Stats() global_stats.start_date = '2017-10-14' @@ -19,10 +23,10 @@ def build_global_stats(): def build_category_stats(): - category_stats = CategoryStats() - category_stats.start_date = '2017-10-15' - category_stats.add_category(Category("foo")) - category_stats.add_category(Category("bar")) + 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() @@ -39,15 +43,16 @@ def get_global_stats(): response = sg.client.stats.get(query_params=stats_params) print(response.status_code) print(response.headers) - print(json.dumps(json.loads(response.body), indent=4, sort_keys=True)) + pprint_json(response.body) def get_category_stats(): stats_params = build_category_stats() + print(stats_params) response = sg.client.categories.stats.get(query_params=stats_params) print(response.status_code) print(response.headers) - print(json.dumps(json.loads(response.body), indent=4, sort_keys=True)) + pprint_json(response.body) def get_category_stats_sums(): @@ -55,7 +60,7 @@ def get_category_stats_sums(): response = sg.client.categories.stats.sums.get(query_params=stats_params) print(response.status_code) print(response.headers) - print(json.dumps(json.loads(response.body), indent=4, sort_keys=True)) + pprint_json(response.body) get_global_stats() get_category_stats() diff --git a/sendgrid/helpers/stats/stats.py b/sendgrid/helpers/stats/stats.py index 8719a9f7d..b5ddebcc1 100644 --- a/sendgrid/helpers/stats/stats.py +++ b/sendgrid/helpers/stats/stats.py @@ -105,7 +105,8 @@ def __init__(self, start_date=None, categories=None): # Minimum required for category stats if start_date and categories: self.start_date = start_date - self.categories = categories + for cat in categories: + self.add_category(Category(cat)) def get(self): """ From f8183a689a0952445dec9b59bb00e8b35ec550ba Mon Sep 17 00:00:00 2001 From: Aaron Mak Kang Sheng Date: Sun, 22 Oct 2017 13:41:53 +0800 Subject: [PATCH 008/462] Add SubuserStats class --- examples/helpers/stats/stats_example.py | 42 +++++++++++++-- sendgrid/helpers/stats/stats.py | 70 +++++++++++++++++++++++-- 2 files changed, 102 insertions(+), 10 deletions(-) diff --git a/examples/helpers/stats/stats_example.py b/examples/helpers/stats/stats_example.py index 41f4b85ab..00a704c47 100644 --- a/examples/helpers/stats/stats_example.py +++ b/examples/helpers/stats/stats_example.py @@ -11,7 +11,7 @@ def pprint_json(json_raw): - print(json.dumps(json.loads(json_raw), indent=4, sort_keys=True)) + print(json.dumps(json.loads(json_raw), indent=2, sort_keys=True)) def build_global_stats(): @@ -38,6 +38,21 @@ def build_category_stats_sums(): 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) @@ -48,7 +63,6 @@ def get_global_stats(): def get_category_stats(): stats_params = build_category_stats() - print(stats_params) response = sg.client.categories.stats.get(query_params=stats_params) print(response.status_code) print(response.headers) @@ -62,6 +76,24 @@ def get_category_stats_sums(): print(response.headers) pprint_json(response.body) -get_global_stats() -get_category_stats() -get_category_stats_sums() + +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/sendgrid/helpers/stats/stats.py b/sendgrid/helpers/stats/stats.py index b5ddebcc1..550599138 100644 --- a/sendgrid/helpers/stats/stats.py +++ b/sendgrid/helpers/stats/stats.py @@ -1,6 +1,3 @@ -import json -import csv - class Stats(object): def __init__( self, start_date=None): @@ -105,8 +102,8 @@ def __init__(self, start_date=None, categories=None): # Minimum required for category stats if start_date and categories: self.start_date = start_date - for cat in categories: - self.add_category(Category(cat)) + for cat_name in categories: + self.add_category(Category(cat_name)) def get(self): """ @@ -142,8 +139,71 @@ def add_category(self, category): 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: From 57439d976c5b3423f3797ee4c39b7006e394ba92 Mon Sep 17 00:00:00 2001 From: Aaron Mak Kang Sheng Date: Sun, 22 Oct 2017 13:54:56 +0800 Subject: [PATCH 009/462] Add to readme --- sendgrid/helpers/stats/README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sendgrid/helpers/stats/README.md b/sendgrid/helpers/stats/README.md index 2c062c3be..1fe31558b 100644 --- a/sendgrid/helpers/stats/README.md +++ b/sendgrid/helpers/stats/README.md @@ -1 +1,10 @@ -**This helper allows you to quickly and easily build a Stats object for sending your email stats to a database.** \ No newline at end of file +**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) \ No newline at end of file From 31221dac7443624f01783ed359c73f9d6c834fae Mon Sep 17 00:00:00 2001 From: Krista LaFentres Date: Mon, 23 Oct 2017 14:41:07 -0500 Subject: [PATCH 010/462] Update Contributing.md Updating version 2.7.8 to 2.7.11 to match version in pyenv install. --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e022ec28..1d41c2248 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -151,7 +151,7 @@ pyenv install 2.7.11 pyenv install 3.4.3 pyenv install 3.5.0 python setup.py install -pyenv local 3.5.0 3.4.3 2.7.8 2.6.9 +pyenv local 3.5.0 3.4.3 2.7.11 2.6.9 pyenv rehash ``` From 246efcc9ed1d525847fb73c4da874340358d48ce Mon Sep 17 00:00:00 2001 From: Aaron Mak Kang Sheng Date: Fri, 27 Oct 2017 08:15:56 +0800 Subject: [PATCH 011/462] add tests for stats helper --- examples/helpers/stats/stats_example.py | 10 ++-- sendgrid/helpers/stats/stats.py | 3 +- test/test_stats.py | 79 +++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 test/test_stats.py diff --git a/examples/helpers/stats/stats_example.py b/examples/helpers/stats/stats_example.py index 00a704c47..d48664c3f 100644 --- a/examples/helpers/stats/stats_example.py +++ b/examples/helpers/stats/stats_example.py @@ -92,8 +92,8 @@ def get_subuser_stats_sums(): print(response.headers) pprint_json(response.body) -# get_global_stats() -# get_category_stats() -# get_category_stats_sums() -# get_subuser_stats() -# get_subuser_stats_sums() +get_global_stats() +get_category_stats() +get_category_stats_sums() +get_subuser_stats() +get_subuser_stats_sums() diff --git a/sendgrid/helpers/stats/stats.py b/sendgrid/helpers/stats/stats.py index 550599138..8fe1399a2 100644 --- a/sendgrid/helpers/stats/stats.py +++ b/sendgrid/helpers/stats/stats.py @@ -202,6 +202,7 @@ def name(self, value): def get(self): return self.name + class Subuser(object): def __init__(self, name=None): @@ -218,4 +219,4 @@ def name(self, value): self._name = value def get(self): - return self.name \ No newline at end of file + return self.name diff --git a/test/test_stats.py b/test/test_stats.py new file mode 100644 index 000000000..eafd131db --- /dev/null +++ b/test/test_stats.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +import json +from sendgrid.helpers.stats import * + +try: + import unittest2 as unittest +except ImportError: + import unittest + + +class UnitTests(unittest.TestCase): + + def test_basicStats(self): + + """Minimum required for stats""" + global_stats = Stats(start_date='12-09-2017') + + self.assertEqual( + json.dumps( + global_stats.get(), + sort_keys=True), + '{"start_date": "12-09-2017"}' + ) + + self.assertTrue(isinstance(str(global_stats), str)) + + def test_Stats(self): + + all_stats = Stats(start_date='12-09-2017') + all_stats.end_date = '12-10-2017' + all_stats.aggregated_by = 'day' + all_stats._sort_by_direction = 'asc' + all_stats._limit = 100 + all_stats._offset = 2 + + self.assertEqual( + json.dumps( + all_stats.get(), + sort_keys=True), + '{"aggregated_by": "day", "end_date": "12-10-2017", ' + '"limit": 100, "offset": 2, "sort_by_direction": "asc", ' + '"start_date": "12-09-2017"}' + ) + + def test_categoryStats(self): + + category_stats = CategoryStats(start_date='12-09-2017', categories=['foo', 'bar']) + category_stats.end_date = '12-10-2017' + category_stats.aggregated_by = 'day' + category_stats._sort_by_direction = 'asc' + category_stats._limit = 100 + category_stats._offset = 2 + + self.assertEqual( + json.dumps( + category_stats.get(), + sort_keys=True), + '{"aggregated_by": "day", "categories": ["foo", "bar"], ' + '"end_date": "12-10-2017", "limit": 100, "offset": 2, ' + '"sort_by_direction": "asc", "start_date": "12-09-2017"}' + ) + + def test_subuserStats(self): + + subuser_stats = SubuserStats(start_date = '12-09-2017', subusers=['foo', 'bar']) + subuser_stats.end_date = '12-10-2017' + subuser_stats.aggregated_by = 'day' + subuser_stats._sort_by_direction = 'asc' + subuser_stats._limit = 100 + subuser_stats._offset = 2 + + self.assertEqual( + json.dumps( + subuser_stats.get(), + sort_keys=True), + '{"aggregated_by": "day", "end_date": "12-10-2017", ' + '"limit": 100, "offset": 2, "sort_by_direction": "asc", ' + '"start_date": "12-09-2017", "subusers": ["foo", "bar"]}' + ) From 760c5f105a76ebf4eeac32f002e5f4b68bf183e0 Mon Sep 17 00:00:00 2001 From: Aaron Mak Kang Sheng Date: Fri, 27 Oct 2017 08:43:37 +0800 Subject: [PATCH 012/462] Add more tests --- test/test_stats.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/test_stats.py b/test/test_stats.py index eafd131db..7da54b2e2 100644 --- a/test/test_stats.py +++ b/test/test_stats.py @@ -45,6 +45,7 @@ def test_Stats(self): def test_categoryStats(self): category_stats = CategoryStats(start_date='12-09-2017', categories=['foo', 'bar']) + category_stats.add_category(Category('woo')) category_stats.end_date = '12-10-2017' category_stats.aggregated_by = 'day' category_stats._sort_by_direction = 'asc' @@ -55,7 +56,7 @@ def test_categoryStats(self): json.dumps( category_stats.get(), sort_keys=True), - '{"aggregated_by": "day", "categories": ["foo", "bar"], ' + '{"aggregated_by": "day", "categories": ["foo", "bar", "woo"], ' '"end_date": "12-10-2017", "limit": 100, "offset": 2, ' '"sort_by_direction": "asc", "start_date": "12-09-2017"}' ) @@ -63,6 +64,7 @@ def test_categoryStats(self): def test_subuserStats(self): subuser_stats = SubuserStats(start_date = '12-09-2017', subusers=['foo', 'bar']) + subuser_stats.add_subuser(Subuser('blah')) subuser_stats.end_date = '12-10-2017' subuser_stats.aggregated_by = 'day' subuser_stats._sort_by_direction = 'asc' @@ -75,5 +77,5 @@ def test_subuserStats(self): sort_keys=True), '{"aggregated_by": "day", "end_date": "12-10-2017", ' '"limit": 100, "offset": 2, "sort_by_direction": "asc", ' - '"start_date": "12-09-2017", "subusers": ["foo", "bar"]}' + '"start_date": "12-09-2017", "subusers": ["foo", "bar", "blah"]}' ) From b12cb7bd464405598f9b43b1d43d418b38b1228e Mon Sep 17 00:00:00 2001 From: Aaron Mak Kang Sheng Date: Fri, 27 Oct 2017 22:47:18 +0800 Subject: [PATCH 013/462] add more tests --- test/test_stats.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/test_stats.py b/test/test_stats.py index 7da54b2e2..c71117397 100644 --- a/test/test_stats.py +++ b/test/test_stats.py @@ -30,6 +30,7 @@ def test_Stats(self): all_stats.end_date = '12-10-2017' all_stats.aggregated_by = 'day' all_stats._sort_by_direction = 'asc' + all_stats.sort_by_metric = 'clicks' all_stats._limit = 100 all_stats._offset = 2 @@ -39,7 +40,7 @@ def test_Stats(self): sort_keys=True), '{"aggregated_by": "day", "end_date": "12-10-2017", ' '"limit": 100, "offset": 2, "sort_by_direction": "asc", ' - '"start_date": "12-09-2017"}' + '"sort_by_metric": "clicks", "start_date": "12-09-2017"}' ) def test_categoryStats(self): @@ -49,6 +50,7 @@ def test_categoryStats(self): category_stats.end_date = '12-10-2017' category_stats.aggregated_by = 'day' category_stats._sort_by_direction = 'asc' + category_stats.sort_by_metric = 'clicks' category_stats._limit = 100 category_stats._offset = 2 @@ -58,7 +60,8 @@ def test_categoryStats(self): sort_keys=True), '{"aggregated_by": "day", "categories": ["foo", "bar", "woo"], ' '"end_date": "12-10-2017", "limit": 100, "offset": 2, ' - '"sort_by_direction": "asc", "start_date": "12-09-2017"}' + '"sort_by_direction": "asc", "sort_by_metric": "clicks", ' + '"start_date": "12-09-2017"}' ) def test_subuserStats(self): @@ -68,6 +71,7 @@ def test_subuserStats(self): subuser_stats.end_date = '12-10-2017' subuser_stats.aggregated_by = 'day' subuser_stats._sort_by_direction = 'asc' + subuser_stats.sort_by_metric = 'clicks' subuser_stats._limit = 100 subuser_stats._offset = 2 @@ -77,5 +81,6 @@ def test_subuserStats(self): sort_keys=True), '{"aggregated_by": "day", "end_date": "12-10-2017", ' '"limit": 100, "offset": 2, "sort_by_direction": "asc", ' - '"start_date": "12-09-2017", "subusers": ["foo", "bar", "blah"]}' + '"sort_by_metric": "clicks", "start_date": "12-09-2017", ' + '"subusers": ["foo", "bar", "blah"]}' ) From 24fc747f4557704fee9857a53b95c2ee19fc768d Mon Sep 17 00:00:00 2001 From: Ryan D'souza Date: Fri, 27 Oct 2017 17:34:29 +0200 Subject: [PATCH 014/462] Insert text content first --- sendgrid/helpers/mail/mail.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index a2159b25d..d064e723b 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -200,7 +200,12 @@ def contents(self): def add_content(self, content): if self._contents is None: self._contents = [] - self._contents.append(content) + + #Fix for issue-451: text content should be before html content + if content._type == "text/plain": + self._contents.insert(0, content) + else: + self._contents.append(content) @property def attachments(self): From 8bb3a754ba1d7d4fa104018c59181ae419d7b4c1 Mon Sep 17 00:00:00 2001 From: Ryan D'souza Date: Fri, 27 Oct 2017 17:40:37 +0200 Subject: [PATCH 015/462] Test case for issue --- test/test_mail.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/test_mail.py b/test/test_mail.py index 3356eef52..4697e1d40 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -68,6 +68,39 @@ def test_helloEmail(self): 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.maxDiff = None From 744e290cb145231900a5c379a043ea56b202a150 Mon Sep 17 00:00:00 2001 From: Piotr Szwarc Date: Sat, 28 Oct 2017 01:50:51 +0200 Subject: [PATCH 016/462] Fixed PEP8 issues except E501. --- examples/accesssettings/accesssettings.py | 33 ++- examples/alerts/alerts.py | 9 +- examples/apikeys/apikeys.py | 27 +- examples/asm/asm.py | 45 +-- examples/browsers/browsers.py | 4 +- examples/campaigns/campaigns.py | 63 ++--- examples/categories/categories.py | 7 +- examples/clients/clients.py | 7 +- examples/contactdb/contactdb.py | 147 +++++----- examples/devices/devices.py | 4 +- examples/geo/geo.py | 4 +- examples/helpers/mail/mail_example.py | 29 +- examples/ips/ips.py | 12 +- examples/mail/mail.py | 260 +++++++++--------- examples/mailboxproviders/mailboxproviders.py | 4 +- examples/mailsettings/mailsettings.py | 47 ++-- examples/partnersettings/partnersettings.py | 7 +- examples/scopes/scopes.py | 1 - examples/senders/senders.py | 61 ++-- examples/stats/stats.py | 4 +- examples/subusers/subusers.py | 42 +-- examples/suppression/suppression.py | 41 ++- examples/templates/templates.py | 33 +-- examples/trackingsettings/trackingsettings.py | 32 +-- examples/user/user.py | 73 ++--- examples/whitelabel/whitelabel.py | 57 ++-- register.py | 6 +- sendgrid/helpers/inbound/config.py | 3 +- sendgrid/helpers/inbound/parse.py | 1 + sendgrid/helpers/inbound/send.py | 1 + sendgrid/helpers/mail/mail.py | 10 +- test/test_email.py | 9 +- test/test_mail.py | 1 - test/test_sendgrid.py | 17 +- 34 files changed, 566 insertions(+), 535 deletions(-) diff --git a/examples/accesssettings/accesssettings.py b/examples/accesssettings/accesssettings.py index aac0e4a54..5cbf1c35b 100644 --- a/examples/accesssettings/accesssettings.py +++ b/examples/accesssettings/accesssettings.py @@ -20,17 +20,17 @@ # POST /access_settings/whitelist # data = { - "ips": [ - { - "ip": "192.168.1.1" - }, - { - "ip": "192.*.*.*" - }, - { - "ip": "192.168.1.3/32" - } - ] + "ips": [ + { + "ip": "192.168.1.1" + }, + { + "ip": "192.*.*.*" + }, + { + "ip": "192.168.1.3/32" + } + ] } response = sg.client.access_settings.whitelist.post(request_body=data) print(response.status_code) @@ -51,11 +51,11 @@ # DELETE /access_settings/whitelist # data = { - "ids": [ - 1, - 2, - 3 - ] + "ids": [ + 1, + 2, + 3 + ] } response = sg.client.access_settings.whitelist.delete(request_body=data) print(response.status_code) @@ -81,4 +81,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/alerts/alerts.py b/examples/alerts/alerts.py index e30d48748..df2aef700 100644 --- a/examples/alerts/alerts.py +++ b/examples/alerts/alerts.py @@ -10,9 +10,9 @@ # POST /alerts # data = { - "email_to": "example@example.com", - "frequency": "daily", - "type": "stats_notification" + "email_to": "example@example.com", + "frequency": "daily", + "type": "stats_notification" } response = sg.client.alerts.post(request_body=data) print(response.status_code) @@ -33,7 +33,7 @@ # PATCH /alerts/{alert_id} # data = { - "email_to": "example@example.com" + "email_to": "example@example.com" } alert_id = "test_url_param" response = sg.client.alerts._(alert_id).patch(request_body=data) @@ -60,4 +60,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/apikeys/apikeys.py b/examples/apikeys/apikeys.py index 42c3afa10..0699d385b 100644 --- a/examples/apikeys/apikeys.py +++ b/examples/apikeys/apikeys.py @@ -10,13 +10,13 @@ # POST /api_keys # data = { - "name": "My API Key", - "sample": "data", - "scopes": [ - "mail.send", - "alerts.create", - "alerts.read" - ] + "name": "My API Key", + "sample": "data", + "scopes": [ + "mail.send", + "alerts.create", + "alerts.read" + ] } response = sg.client.api_keys.post(request_body=data) print(response.status_code) @@ -38,11 +38,11 @@ # PUT /api_keys/{api_key_id} # data = { - "name": "A New Hope", - "scopes": [ - "user.profile.read", - "user.profile.update" - ] + "name": "A New Hope", + "scopes": [ + "user.profile.read", + "user.profile.update" + ] } api_key_id = "test_url_param" response = sg.client.api_keys._(api_key_id).put(request_body=data) @@ -55,7 +55,7 @@ # PATCH /api_keys/{api_key_id} # data = { - "name": "A New Hope" + "name": "A New Hope" } api_key_id = "test_url_param" response = sg.client.api_keys._(api_key_id).patch(request_body=data) @@ -82,4 +82,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/asm/asm.py b/examples/asm/asm.py index 43130cf06..40f35576a 100644 --- a/examples/asm/asm.py +++ b/examples/asm/asm.py @@ -10,9 +10,9 @@ # POST /asm/groups # data = { - "description": "Suggestions for products our users might like.", - "is_default": True, - "name": "Product Suggestions" + "description": "Suggestions for products our users might like.", + "is_default": True, + "name": "Product Suggestions" } response = sg.client.asm.groups.post(request_body=data) print(response.status_code) @@ -34,9 +34,9 @@ # PATCH /asm/groups/{group_id} # data = { - "description": "Suggestions for items our users might like.", - "id": 103, - "name": "Item Suggestions" + "description": "Suggestions for items our users might like.", + "id": 103, + "name": "Item Suggestions" } group_id = "test_url_param" response = sg.client.asm.groups._(group_id).patch(request_body=data) @@ -69,13 +69,14 @@ # POST /asm/groups/{group_id}/suppressions # data = { - "recipient_emails": [ - "test1@example.com", - "test2@example.com" - ] + "recipient_emails": [ + "test1@example.com", + "test2@example.com" + ] } group_id = "test_url_param" -response = sg.client.asm.groups._(group_id).suppressions.post(request_body=data) +response = sg.client.asm.groups._( + group_id).suppressions.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) @@ -95,14 +96,15 @@ # POST /asm/groups/{group_id}/suppressions/search # data = { - "recipient_emails": [ - "exists1@example.com", - "exists2@example.com", - "doesnotexists@example.com" - ] + "recipient_emails": [ + "exists1@example.com", + "exists2@example.com", + "doesnotexists@example.com" + ] } group_id = "test_url_param" -response = sg.client.asm.groups._(group_id).suppressions.search.post(request_body=data) +response = sg.client.asm.groups._( + group_id).suppressions.search.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) @@ -132,10 +134,10 @@ # POST /asm/suppressions/global # data = { - "recipient_emails": [ - "test1@example.com", - "test2@example.com" - ] + "recipient_emails": [ + "test1@example.com", + "test2@example.com" + ] } response = sg.client.asm.suppressions._("global").post(request_body=data) print(response.status_code) @@ -171,4 +173,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/browsers/browsers.py b/examples/browsers/browsers.py index c123c12e5..a24f88fd9 100644 --- a/examples/browsers/browsers.py +++ b/examples/browsers/browsers.py @@ -9,9 +9,9 @@ # Retrieve email statistics by browser. # # GET /browsers/stats # -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'browsers': 'test_string', 'limit': 'test_string', 'offset': 'test_string', 'start_date': '2016-01-01'} +params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'browsers': 'test_string', + 'limit': 'test_string', 'offset': 'test_string', 'start_date': '2016-01-01'} response = sg.client.browsers.stats.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/campaigns/campaigns.py b/examples/campaigns/campaigns.py index c77fc878b..76bdaeb9e 100644 --- a/examples/campaigns/campaigns.py +++ b/examples/campaigns/campaigns.py @@ -10,24 +10,24 @@ # POST /campaigns # data = { - "categories": [ - "spring line" - ], - "custom_unsubscribe_url": "", - "html_content": "

Check out our spring line!

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

Check out our spring line!

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

Check out our summer line!

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

Check out our summer line!

", + "plain_content": "Check out our summer line!", + "subject": "New Products for Summer!", + "title": "May Newsletter" } campaign_id = "test_url_param" response = sg.client.campaigns._(campaign_id).patch(request_body=data) @@ -88,10 +88,11 @@ # PATCH /campaigns/{campaign_id}/schedules # data = { - "send_at": 1489451436 + "send_at": 1489451436 } campaign_id = "test_url_param" -response = sg.client.campaigns._(campaign_id).schedules.patch(request_body=data) +response = sg.client.campaigns._( + campaign_id).schedules.patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) @@ -101,7 +102,7 @@ # POST /campaigns/{campaign_id}/schedules # data = { - "send_at": 1489771528 + "send_at": 1489771528 } campaign_id = "test_url_param" response = sg.client.campaigns._(campaign_id).schedules.post(request_body=data) @@ -144,11 +145,11 @@ # POST /campaigns/{campaign_id}/schedules/test # data = { - "to": "your.email@example.com" + "to": "your.email@example.com" } campaign_id = "test_url_param" -response = sg.client.campaigns._(campaign_id).schedules.test.post(request_body=data) +response = sg.client.campaigns._( + campaign_id).schedules.test.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/categories/categories.py b/examples/categories/categories.py index 7984f0fe0..1b0297f2f 100644 --- a/examples/categories/categories.py +++ b/examples/categories/categories.py @@ -19,7 +19,8 @@ # Retrieve Email Statistics for Categories # # GET /categories/stats # -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01', 'categories': 'test_string'} +params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, + 'offset': 1, 'start_date': '2016-01-01', 'categories': 'test_string'} response = sg.client.categories.stats.get(query_params=params) print(response.status_code) print(response.body) @@ -29,9 +30,9 @@ # Retrieve sums of email stats for each category [Needs: Stats object defined, has category ID?] # # GET /categories/stats/sums # -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} +params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, + 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} response = sg.client.categories.stats.sums.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/clients/clients.py b/examples/clients/clients.py index 7831ef78f..80905d12f 100644 --- a/examples/clients/clients.py +++ b/examples/clients/clients.py @@ -9,7 +9,8 @@ # Retrieve email statistics by client type. # # GET /clients/stats # -params = {'aggregated_by': 'day', 'start_date': '2016-01-01', 'end_date': '2016-04-01'} +params = {'aggregated_by': 'day', + 'start_date': '2016-01-01', 'end_date': '2016-04-01'} response = sg.client.clients.stats.get(query_params=params) print(response.status_code) print(response.body) @@ -19,10 +20,10 @@ # Retrieve stats by a specific client type. # # GET /clients/{client_type}/stats # -params = {'aggregated_by': 'day', 'start_date': '2016-01-01', 'end_date': '2016-04-01'} +params = {'aggregated_by': 'day', + 'start_date': '2016-01-01', 'end_date': '2016-04-01'} client_type = "test_url_param" response = sg.client.clients._(client_type).stats.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/contactdb/contactdb.py b/examples/contactdb/contactdb.py index c234d7724..b9da0ee2a 100644 --- a/examples/contactdb/contactdb.py +++ b/examples/contactdb/contactdb.py @@ -10,8 +10,8 @@ # POST /contactdb/custom_fields # data = { - "name": "pet", - "type": "text" + "name": "pet", + "type": "text" } response = sg.client.contactdb.custom_fields.post(request_body=data) print(response.status_code) @@ -52,7 +52,7 @@ # POST /contactdb/lists # data = { - "name": "your list name" + "name": "your list name" } response = sg.client.contactdb.lists.post(request_body=data) print(response.status_code) @@ -73,10 +73,10 @@ # DELETE /contactdb/lists # data = [ - 1, - 2, - 3, - 4 + 1, + 2, + 3, + 4 ] response = sg.client.contactdb.lists.delete(request_body=data) print(response.status_code) @@ -88,11 +88,12 @@ # PATCH /contactdb/lists/{list_id} # data = { - "name": "newlistname" + "name": "newlistname" } params = {'list_id': 1} list_id = "test_url_param" -response = sg.client.contactdb.lists._(list_id).patch(request_body=data, query_params=params) +response = sg.client.contactdb.lists._(list_id).patch( + request_body=data, query_params=params) print(response.status_code) print(response.body) print(response.headers) @@ -124,11 +125,12 @@ # POST /contactdb/lists/{list_id}/recipients # data = [ - "recipient_id1", - "recipient_id2" + "recipient_id1", + "recipient_id2" ] list_id = "test_url_param" -response = sg.client.contactdb.lists._(list_id).recipients.post(request_body=data) +response = sg.client.contactdb.lists._( + list_id).recipients.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) @@ -139,7 +141,8 @@ params = {'page': 1, 'page_size': 1} list_id = "test_url_param" -response = sg.client.contactdb.lists._(list_id).recipients.get(query_params=params) +response = sg.client.contactdb.lists._( + list_id).recipients.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) @@ -150,7 +153,8 @@ list_id = "test_url_param" recipient_id = "test_url_param" -response = sg.client.contactdb.lists._(list_id).recipients._(recipient_id).post() +response = sg.client.contactdb.lists._( + list_id).recipients._(recipient_id).post() print(response.status_code) print(response.body) print(response.headers) @@ -162,7 +166,8 @@ params = {'recipient_id': 1, 'list_id': 1} list_id = "test_url_param" recipient_id = "test_url_param" -response = sg.client.contactdb.lists._(list_id).recipients._(recipient_id).delete(query_params=params) +response = sg.client.contactdb.lists._(list_id).recipients._( + recipient_id).delete(query_params=params) print(response.status_code) print(response.body) print(response.headers) @@ -172,11 +177,11 @@ # PATCH /contactdb/recipients # data = [ - { - "email": "jones@example.com", - "first_name": "Guy", - "last_name": "Jones" - } + { + "email": "jones@example.com", + "first_name": "Guy", + "last_name": "Jones" + } ] response = sg.client.contactdb.recipients.patch(request_body=data) print(response.status_code) @@ -188,18 +193,18 @@ # POST /contactdb/recipients # data = [ - { - "age": 25, - "email": "example@example.com", - "first_name": "", - "last_name": "User" - }, - { - "age": 25, - "email": "example2@example.com", - "first_name": "Example", - "last_name": "User" - } + { + "age": 25, + "email": "example@example.com", + "first_name": "", + "last_name": "User" + }, + { + "age": 25, + "email": "example2@example.com", + "first_name": "Example", + "last_name": "User" + } ] response = sg.client.contactdb.recipients.post(request_body=data) print(response.status_code) @@ -221,8 +226,8 @@ # DELETE /contactdb/recipients # data = [ - "recipient_id1", - "recipient_id2" + "recipient_id1", + "recipient_id2" ] response = sg.client.contactdb.recipients.delete(request_body=data) print(response.status_code) @@ -301,28 +306,28 @@ # POST /contactdb/segments # data = { - "conditions": [ - { - "and_or": "", - "field": "last_name", - "operator": "eq", - "value": "Miller" - }, - { - "and_or": "and", - "field": "last_clicked", - "operator": "gt", - "value": "01/02/2015" - }, - { - "and_or": "or", - "field": "clicks.campaign_identifier", - "operator": "eq", - "value": "513" - } - ], - "list_id": 4, - "name": "Last Name Miller" + "conditions": [ + { + "and_or": "", + "field": "last_name", + "operator": "eq", + "value": "Miller" + }, + { + "and_or": "and", + "field": "last_clicked", + "operator": "gt", + "value": "01/02/2015" + }, + { + "and_or": "or", + "field": "clicks.campaign_identifier", + "operator": "eq", + "value": "513" + } + ], + "list_id": 4, + "name": "Last Name Miller" } response = sg.client.contactdb.segments.post(request_body=data) print(response.status_code) @@ -343,20 +348,21 @@ # PATCH /contactdb/segments/{segment_id} # data = { - "conditions": [ - { - "and_or": "", - "field": "last_name", - "operator": "eq", - "value": "Miller" - } - ], - "list_id": 5, - "name": "The Millers" + "conditions": [ + { + "and_or": "", + "field": "last_name", + "operator": "eq", + "value": "Miller" + } + ], + "list_id": 5, + "name": "The Millers" } params = {'segment_id': 'test_string'} segment_id = "test_url_param" -response = sg.client.contactdb.segments._(segment_id).patch(request_body=data, query_params=params) +response = sg.client.contactdb.segments._(segment_id).patch( + request_body=data, query_params=params) print(response.status_code) print(response.body) print(response.headers) @@ -378,7 +384,8 @@ params = {'delete_contacts': 'true'} segment_id = "test_url_param" -response = sg.client.contactdb.segments._(segment_id).delete(query_params=params) +response = sg.client.contactdb.segments._( + segment_id).delete(query_params=params) print(response.status_code) print(response.body) print(response.headers) @@ -389,8 +396,8 @@ params = {'page': 1, 'page_size': 1} segment_id = "test_url_param" -response = sg.client.contactdb.segments._(segment_id).recipients.get(query_params=params) +response = sg.client.contactdb.segments._( + segment_id).recipients.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/devices/devices.py b/examples/devices/devices.py index 108e98452..c779bfb65 100644 --- a/examples/devices/devices.py +++ b/examples/devices/devices.py @@ -9,9 +9,9 @@ # Retrieve email statistics by device type. # # GET /devices/stats # -params = {'aggregated_by': 'day', 'limit': 1, 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} +params = {'aggregated_by': 'day', 'limit': 1, + 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} response = sg.client.devices.stats.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/geo/geo.py b/examples/geo/geo.py index 7d58ec085..e68b55792 100644 --- a/examples/geo/geo.py +++ b/examples/geo/geo.py @@ -9,9 +9,9 @@ # Retrieve email statistics by country and state/province. # # GET /geo/stats # -params = {'end_date': '2016-04-01', 'country': 'US', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01'} +params = {'end_date': '2016-04-01', 'country': 'US', 'aggregated_by': 'day', + 'limit': 1, 'offset': 1, 'start_date': '2016-01-01'} response = sg.client.geo.stats.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/helpers/mail/mail_example.py b/examples/helpers/mail/mail_example.py index 9d5133d5e..46eeaff59 100644 --- a/examples/helpers/mail/mail_example.py +++ b/examples/helpers/mail/mail_example.py @@ -4,7 +4,9 @@ from sendgrid.helpers.mail import * from sendgrid import * -# NOTE: you will need move this file to the root directory of this project to execute properly. +# NOTE: you will need move this file to the root directory of this project +# to execute properly. + def build_hello_email(): """Minimum required to send an email""" @@ -17,6 +19,7 @@ def build_hello_email(): return mail.get() + def build_kitchen_sink(): """All settings set""" mail = Mail() @@ -60,7 +63,8 @@ def build_kitchen_sink(): mail.add_personalization(personalization2) mail.add_content(Content("text/plain", "some text here")) - mail.add_content(Content("text/html", "some text here")) + mail.add_content( + Content("text/html", "some text here")) attachment = Attachment() attachment.content = "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12" @@ -104,22 +108,28 @@ def build_kitchen_sink(): 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.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_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") + 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") return mail.get() + def send_hello_email(): # Assumes you set your environment variable: # https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key @@ -130,6 +140,7 @@ def send_hello_email(): print(response.headers) print(response.body) + def send_kitchen_sink(): # Assumes you set your environment variable: # https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key @@ -140,5 +151,5 @@ def send_kitchen_sink(): print(response.headers) print(response.body) -send_hello_email() # this will actually send an email -send_kitchen_sink() # this will only send an email if you set SandBox Mode to False +send_hello_email() # this will actually send an email +send_kitchen_sink() # this will only send an email if you set SandBox Mode to False diff --git a/examples/ips/ips.py b/examples/ips/ips.py index 6c48ae306..5d3e4e0bd 100644 --- a/examples/ips/ips.py +++ b/examples/ips/ips.py @@ -9,7 +9,8 @@ # Retrieve all IP addresses # # GET /ips # -params = {'subuser': 'test_string', 'ip': 'test_string', 'limit': 1, 'exclude_whitelabels': 'true', 'offset': 1} +params = {'subuser': 'test_string', 'ip': 'test_string', + 'limit': 1, 'exclude_whitelabels': 'true', 'offset': 1} response = sg.client.ips.get(query_params=params) print(response.status_code) print(response.body) @@ -29,7 +30,7 @@ # POST /ips/pools # data = { - "name": "marketing" + "name": "marketing" } response = sg.client.ips.pools.post(request_body=data) print(response.status_code) @@ -50,7 +51,7 @@ # PUT /ips/pools/{pool_name} # data = { - "name": "new_pool_name" + "name": "new_pool_name" } pool_name = "test_url_param" response = sg.client.ips.pools._(pool_name).put(request_body=data) @@ -83,7 +84,7 @@ # POST /ips/pools/{pool_name}/ips # data = { - "ip": "0.0.0.0" + "ip": "0.0.0.0" } pool_name = "test_url_param" response = sg.client.ips.pools._(pool_name).ips.post(request_body=data) @@ -107,7 +108,7 @@ # POST /ips/warmup # data = { - "ip": "0.0.0.0" + "ip": "0.0.0.0" } response = sg.client.ips.warmup.post(request_body=data) print(response.status_code) @@ -152,4 +153,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/mail/mail.py b/examples/mail/mail.py index fef420e87..253c2558a 100644 --- a/examples/mail/mail.py +++ b/examples/mail/mail.py @@ -27,148 +27,148 @@ ################################################## # v3 Mail Send # # POST /mail/send # -# This endpoint has a helper, check it out [here](https://github.com/sendgrid/sendgrid-python/blob/master/sendgrid/helpers/mail/README.md). +# This endpoint has a helper, check it out +# [here](https://github.com/sendgrid/sendgrid-python/blob/master/sendgrid/helpers/mail/README.md). data = { - "asm": { - "group_id": 1, - "groups_to_display": [ - 1, - 2, - 3 - ] - }, - "attachments": [ - { - "content": "[BASE64 encoded content block here]", - "content_id": "ii_139db99fdb5c3704", - "disposition": "inline", - "filename": "file1.jpg", - "name": "file1", - "type": "jpg" - } - ], - "batch_id": "[YOUR BATCH ID GOES HERE]", - "categories": [ - "category1", - "category2" - ], - "content": [ - { - "type": "text/html", - "value": "

Hello, world!

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

Thanks
The SendGrid Team

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

Hello, world!

" } - ], - "custom_args": { - "New Argument 1": "New Value 1", - "activationAttempt": "1", + ], + "custom_args": { + "New Argument 1": "New Value 1", + "activationAttempt": "1", "customerAccountNumber": "[CUSTOMER ACCOUNT NUMBER GOES HERE]" - }, - "headers": { - "X-Accept-Language": "en", - "X-Mailer": "MyApp" - }, - "send_at": 1409348513, - "subject": "Hello, World!", - "substitutions": { - "id": "substitutions", - "type": "object" - }, - "to": [ + }, + "from": { + "email": "sam.smith@example.com", + "name": "Sam Smith" + }, + "headers": {}, + "ip_pool_name": "[YOUR POOL NAME GOES HERE]", + "mail_settings": { + "bcc": { + "email": "ben.doe@example.com", + "enable": True + }, + "bypass_list_management": { + "enable": True + }, + "footer": { + "enable": True, + "html": "

Thanks
The SendGrid Team

", + "text": "Thanks,/n The SendGrid Team" + }, + "sandbox_mode": { + "enable": False + }, + "spam_check": { + "enable": True, + "post_to_url": "http://example.com/compliance", + "threshold": 3 + } + }, + "personalizations": [ { - "email": "john.doe@example.com", - "name": "John Doe" + "bcc": [ + { + "email": "sam.doe@example.com", + "name": "Sam Doe" + } + ], + "cc": [ + { + "email": "jane.doe@example.com", + "name": "Jane Doe" + } + ], + "custom_args": { + "New Argument 1": "New Value 1", + "activationAttempt": "1", + "customerAccountNumber": "[CUSTOMER ACCOUNT NUMBER GOES HERE]" + }, + "headers": { + "X-Accept-Language": "en", + "X-Mailer": "MyApp" + }, + "send_at": 1409348513, + "subject": "Hello, World!", + "substitutions": { + "id": "substitutions", + "type": "object" + }, + "to": [ + { + "email": "john.doe@example.com", + "name": "John Doe" + } + ] + } + ], + "reply_to": { + "email": "sam.smith@example.com", + "name": "Sam Smith" + }, + "sections": { + "section": { + ":sectionName1": "section 1 text", + ":sectionName2": "section 2 text" + } + }, + "send_at": 1409348513, + "subject": "Hello, World!", + "template_id": "[YOUR TEMPLATE ID GOES HERE]", + "tracking_settings": { + "click_tracking": { + "enable": True, + "enable_text": True + }, + "ganalytics": { + "enable": True, + "utm_campaign": "[NAME OF YOUR REFERRER SOURCE]", + "utm_content": "[USE THIS SPACE TO DIFFERENTIATE YOUR EMAIL FROM ADS]", + "utm_medium": "[NAME OF YOUR MARKETING MEDIUM e.g. email]", + "utm_name": "[NAME OF YOUR CAMPAIGN]", + "utm_term": "[IDENTIFY PAID KEYWORDS HERE]" + }, + "open_tracking": { + "enable": True, + "substitution_tag": "%opentrack" + }, + "subscription_tracking": { + "enable": True, + "html": "If you would like to unsubscribe and stop receiving these emails <% clickhere %>.", + "substitution_tag": "<%click here%>", + "text": "If you would like to unsubscribe and stop receiveing these emails <% click here %>." } - ] - } - ], - "reply_to": { - "email": "sam.smith@example.com", - "name": "Sam Smith" - }, - "sections": { - "section": { - ":sectionName1": "section 1 text", - ":sectionName2": "section 2 text" - } - }, - "send_at": 1409348513, - "subject": "Hello, World!", - "template_id": "[YOUR TEMPLATE ID GOES HERE]", - "tracking_settings": { - "click_tracking": { - "enable": True, - "enable_text": True - }, - "ganalytics": { - "enable": True, - "utm_campaign": "[NAME OF YOUR REFERRER SOURCE]", - "utm_content": "[USE THIS SPACE TO DIFFERENTIATE YOUR EMAIL FROM ADS]", - "utm_medium": "[NAME OF YOUR MARKETING MEDIUM e.g. email]", - "utm_name": "[NAME OF YOUR CAMPAIGN]", - "utm_term": "[IDENTIFY PAID KEYWORDS HERE]" - }, - "open_tracking": { - "enable": True, - "substitution_tag": "%opentrack" - }, - "subscription_tracking": { - "enable": True, - "html": "If you would like to unsubscribe and stop receiving these emails <% clickhere %>.", - "substitution_tag": "<%click here%>", - "text": "If you would like to unsubscribe and stop receiveing these emails <% click here %>." } - } } response = sg.client.mail.send.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/mailboxproviders/mailboxproviders.py b/examples/mailboxproviders/mailboxproviders.py index 1b75ecac5..0fdf0ec15 100644 --- a/examples/mailboxproviders/mailboxproviders.py +++ b/examples/mailboxproviders/mailboxproviders.py @@ -9,9 +9,9 @@ # Retrieve email statistics by mailbox provider. # # GET /mailbox_providers/stats # -params = {'end_date': '2016-04-01', 'mailbox_providers': 'test_string', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01'} +params = {'end_date': '2016-04-01', 'mailbox_providers': 'test_string', + 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01'} response = sg.client.mailbox_providers.stats.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/mailsettings/mailsettings.py b/examples/mailsettings/mailsettings.py index 18c57b960..e0d89b5fa 100644 --- a/examples/mailsettings/mailsettings.py +++ b/examples/mailsettings/mailsettings.py @@ -20,11 +20,11 @@ # PATCH /mail_settings/address_whitelist # data = { - "enabled": True, - "list": [ - "email1@example.com", - "example.com" - ] + "enabled": True, + "list": [ + "email1@example.com", + "example.com" + ] } response = sg.client.mail_settings.address_whitelist.patch(request_body=data) print(response.status_code) @@ -45,8 +45,8 @@ # PATCH /mail_settings/bcc # data = { - "email": "email@example.com", - "enabled": False + "email": "email@example.com", + "enabled": False } response = sg.client.mail_settings.bcc.patch(request_body=data) print(response.status_code) @@ -67,9 +67,9 @@ # PATCH /mail_settings/bounce_purge # data = { - "enabled": True, - "hard_bounces": 5, - "soft_bounces": 5 + "enabled": True, + "hard_bounces": 5, + "soft_bounces": 5 } response = sg.client.mail_settings.bounce_purge.patch(request_body=data) print(response.status_code) @@ -90,9 +90,9 @@ # PATCH /mail_settings/footer # data = { - "enabled": True, - "html_content": "...", - "plain_content": "..." + "enabled": True, + "html_content": "...", + "plain_content": "..." } response = sg.client.mail_settings.footer.patch(request_body=data) print(response.status_code) @@ -113,8 +113,8 @@ # PATCH /mail_settings/forward_bounce # data = { - "email": "example@example.com", - "enabled": True + "email": "example@example.com", + "enabled": True } response = sg.client.mail_settings.forward_bounce.patch(request_body=data) print(response.status_code) @@ -135,8 +135,8 @@ # PATCH /mail_settings/forward_spam # data = { - "email": "", - "enabled": False + "email": "", + "enabled": False } response = sg.client.mail_settings.forward_spam.patch(request_body=data) print(response.status_code) @@ -157,7 +157,7 @@ # PATCH /mail_settings/plain_content # data = { - "enabled": False + "enabled": False } response = sg.client.mail_settings.plain_content.patch(request_body=data) print(response.status_code) @@ -178,9 +178,9 @@ # PATCH /mail_settings/spam_check # data = { - "enabled": True, - "max_score": 5, - "url": "url" + "enabled": True, + "max_score": 5, + "url": "url" } response = sg.client.mail_settings.spam_check.patch(request_body=data) print(response.status_code) @@ -201,8 +201,8 @@ # PATCH /mail_settings/template # data = { - "enabled": True, - "html_content": "<% body %>" + "enabled": True, + "html_content": "<% body %>" } response = sg.client.mail_settings.template.patch(request_body=data) print(response.status_code) @@ -217,4 +217,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/partnersettings/partnersettings.py b/examples/partnersettings/partnersettings.py index 37f77f4e6..fa2589b55 100644 --- a/examples/partnersettings/partnersettings.py +++ b/examples/partnersettings/partnersettings.py @@ -20,9 +20,9 @@ # PATCH /partner_settings/new_relic # data = { - "enable_subuser_statistics": True, - "enabled": True, - "license_key": "" + "enable_subuser_statistics": True, + "enabled": True, + "license_key": "" } response = sg.client.partner_settings.new_relic.patch(request_body=data) print(response.status_code) @@ -37,4 +37,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/scopes/scopes.py b/examples/scopes/scopes.py index 124f77d39..c9d4f1209 100644 --- a/examples/scopes/scopes.py +++ b/examples/scopes/scopes.py @@ -13,4 +13,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/senders/senders.py b/examples/senders/senders.py index f21459b71..f198a8f91 100644 --- a/examples/senders/senders.py +++ b/examples/senders/senders.py @@ -10,21 +10,21 @@ # POST /senders # data = { - "address": "123 Elm St.", - "address_2": "Apt. 456", - "city": "Denver", - "country": "United States", - "from": { - "email": "from@example.com", - "name": "Example INC" - }, - "nickname": "My Sender ID", - "reply_to": { - "email": "replyto@example.com", - "name": "Example INC" - }, - "state": "Colorado", - "zip": "80202" + "address": "123 Elm St.", + "address_2": "Apt. 456", + "city": "Denver", + "country": "United States", + "from": { + "email": "from@example.com", + "name": "Example INC" + }, + "nickname": "My Sender ID", + "reply_to": { + "email": "replyto@example.com", + "name": "Example INC" + }, + "state": "Colorado", + "zip": "80202" } response = sg.client.senders.post(request_body=data) print(response.status_code) @@ -45,21 +45,21 @@ # PATCH /senders/{sender_id} # data = { - "address": "123 Elm St.", - "address_2": "Apt. 456", - "city": "Denver", - "country": "United States", - "from": { - "email": "from@example.com", - "name": "Example INC" - }, - "nickname": "My Sender ID", - "reply_to": { - "email": "replyto@example.com", - "name": "Example INC" - }, - "state": "Colorado", - "zip": "80202" + "address": "123 Elm St.", + "address_2": "Apt. 456", + "city": "Denver", + "country": "United States", + "from": { + "email": "from@example.com", + "name": "Example INC" + }, + "nickname": "My Sender ID", + "reply_to": { + "email": "replyto@example.com", + "name": "Example INC" + }, + "state": "Colorado", + "zip": "80202" } sender_id = "test_url_param" response = sg.client.senders._(sender_id).patch(request_body=data) @@ -96,4 +96,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/stats/stats.py b/examples/stats/stats.py index a7bf3362e..4ddce6b75 100644 --- a/examples/stats/stats.py +++ b/examples/stats/stats.py @@ -9,9 +9,9 @@ # Retrieve global email statistics # # GET /stats # -params = {'aggregated_by': 'day', 'limit': 1, 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} +params = {'aggregated_by': 'day', 'limit': 1, + 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} response = sg.client.stats.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/subusers/subusers.py b/examples/subusers/subusers.py index 6aa91e535..4d59e77b8 100644 --- a/examples/subusers/subusers.py +++ b/examples/subusers/subusers.py @@ -10,13 +10,13 @@ # POST /subusers # data = { - "email": "John@example.com", - "ips": [ - "1.1.1.1", - "2.2.2.2" - ], - "password": "johns_password", - "username": "John@example.com" + "email": "John@example.com", + "ips": [ + "1.1.1.1", + "2.2.2.2" + ], + "password": "johns_password", + "username": "John@example.com" } response = sg.client.subusers.post(request_body=data) print(response.status_code) @@ -47,7 +47,8 @@ # Retrieve email statistics for your subusers. # # GET /subusers/stats # -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01', 'subusers': 'test_string'} +params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, + 'offset': 1, 'start_date': '2016-01-01', 'subusers': 'test_string'} response = sg.client.subusers.stats.get(query_params=params) print(response.status_code) print(response.body) @@ -57,7 +58,8 @@ # Retrieve monthly stats for all subusers # # GET /subusers/stats/monthly # -params = {'subuser': 'test_string', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'date': 'test_string', 'sort_by_direction': 'asc'} +params = {'subuser': 'test_string', 'limit': 1, 'sort_by_metric': 'test_string', + 'offset': 1, 'date': 'test_string', 'sort_by_direction': 'asc'} response = sg.client.subusers.stats.monthly.get(query_params=params) print(response.status_code) print(response.body) @@ -67,7 +69,8 @@ # Retrieve the totals for each email statistic metric for all subusers. # # GET /subusers/stats/sums # -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} +params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, + 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} response = sg.client.subusers.stats.sums.get(query_params=params) print(response.status_code) print(response.body) @@ -78,7 +81,7 @@ # PATCH /subusers/{subuser_name} # data = { - "disabled": False + "disabled": False } subuser_name = "test_url_param" response = sg.client.subusers._(subuser_name).patch(request_body=data) @@ -101,7 +104,7 @@ # PUT /subusers/{subuser_name}/ips # data = [ - "127.0.0.1" + "127.0.0.1" ] subuser_name = "test_url_param" response = sg.client.subusers._(subuser_name).ips.put(request_body=data) @@ -114,8 +117,8 @@ # PUT /subusers/{subuser_name}/monitor # data = { - "email": "example@example.com", - "frequency": 500 + "email": "example@example.com", + "frequency": 500 } subuser_name = "test_url_param" response = sg.client.subusers._(subuser_name).monitor.put(request_body=data) @@ -128,8 +131,8 @@ # POST /subusers/{subuser_name}/monitor # data = { - "email": "example@example.com", - "frequency": 50000 + "email": "example@example.com", + "frequency": 50000 } subuser_name = "test_url_param" response = sg.client.subusers._(subuser_name).monitor.post(request_body=data) @@ -161,10 +164,11 @@ # Retrieve the monthly email statistics for a single subuser # # GET /subusers/{subuser_name}/stats/monthly # -params = {'date': 'test_string', 'sort_by_direction': 'asc', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1} +params = {'date': 'test_string', 'sort_by_direction': 'asc', + 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1} subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).stats.monthly.get(query_params=params) +response = sg.client.subusers._( + subuser_name).stats.monthly.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/suppression/suppression.py b/examples/suppression/suppression.py index abdaef76d..391dbe299 100644 --- a/examples/suppression/suppression.py +++ b/examples/suppression/suppression.py @@ -20,11 +20,11 @@ # DELETE /suppression/blocks # data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] } response = sg.client.suppression.blocks.delete(request_body=data) print(response.status_code) @@ -66,11 +66,11 @@ # DELETE /suppression/bounces # data = { - "delete_all": True, - "emails": [ - "example@example.com", - "example2@example.com" - ] + "delete_all": True, + "emails": [ + "example@example.com", + "example2@example.com" + ] } response = sg.client.suppression.bounces.delete(request_body=data) print(response.status_code) @@ -113,11 +113,11 @@ # DELETE /suppression/invalid_emails # data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] } response = sg.client.suppression.invalid_emails.delete(request_body=data) print(response.status_code) @@ -179,11 +179,11 @@ # DELETE /suppression/spam_reports # data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] } response = sg.client.suppression.spam_reports.delete(request_body=data) print(response.status_code) @@ -199,4 +199,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/templates/templates.py b/examples/templates/templates.py index 9d3d5dd4b..a370006b2 100644 --- a/examples/templates/templates.py +++ b/examples/templates/templates.py @@ -10,7 +10,7 @@ # POST /templates # data = { - "name": "example_name" + "name": "example_name" } response = sg.client.templates.post(request_body=data) print(response.status_code) @@ -31,7 +31,7 @@ # PATCH /templates/{template_id} # data = { - "name": "new_example_name" + "name": "new_example_name" } template_id = "test_url_param" response = sg.client.templates._(template_id).patch(request_body=data) @@ -64,12 +64,12 @@ # POST /templates/{template_id}/versions # data = { - "active": 1, - "html_content": "<%body%>", - "name": "example_version_name", - "plain_content": "<%body%>", - "subject": "<%subject%>", - "template_id": "ddb96bbc-9b92-425e-8979-99464621b543" + "active": 1, + "html_content": "<%body%>", + "name": "example_version_name", + "plain_content": "<%body%>", + "subject": "<%subject%>", + "template_id": "ddb96bbc-9b92-425e-8979-99464621b543" } template_id = "test_url_param" response = sg.client.templates._(template_id).versions.post(request_body=data) @@ -82,15 +82,16 @@ # PATCH /templates/{template_id}/versions/{version_id} # data = { - "active": 1, - "html_content": "<%body%>", - "name": "updated_example_name", - "plain_content": "<%body%>", - "subject": "<%subject%>" + "active": 1, + "html_content": "<%body%>", + "name": "updated_example_name", + "plain_content": "<%body%>", + "subject": "<%subject%>" } template_id = "test_url_param" version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).patch(request_body=data) +response = sg.client.templates._(template_id).versions._( + version_id).patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) @@ -123,8 +124,8 @@ template_id = "test_url_param" version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).activate.post() +response = sg.client.templates._( + template_id).versions._(version_id).activate.post() print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/trackingsettings/trackingsettings.py b/examples/trackingsettings/trackingsettings.py index 80dbe243a..0c45e10d9 100644 --- a/examples/trackingsettings/trackingsettings.py +++ b/examples/trackingsettings/trackingsettings.py @@ -20,7 +20,7 @@ # PATCH /tracking_settings/click # data = { - "enabled": True + "enabled": True } response = sg.client.tracking_settings.click.patch(request_body=data) print(response.status_code) @@ -41,14 +41,15 @@ # PATCH /tracking_settings/google_analytics # data = { - "enabled": True, - "utm_campaign": "website", - "utm_content": "", - "utm_medium": "email", - "utm_source": "sendgrid.com", - "utm_term": "" + "enabled": True, + "utm_campaign": "website", + "utm_content": "", + "utm_medium": "email", + "utm_source": "sendgrid.com", + "utm_term": "" } -response = sg.client.tracking_settings.google_analytics.patch(request_body=data) +response = sg.client.tracking_settings.google_analytics.patch( + request_body=data) print(response.status_code) print(response.body) print(response.headers) @@ -67,7 +68,7 @@ # PATCH /tracking_settings/open # data = { - "enabled": True + "enabled": True } response = sg.client.tracking_settings.open.patch(request_body=data) print(response.status_code) @@ -88,12 +89,12 @@ # PATCH /tracking_settings/subscription # data = { - "enabled": True, - "html_content": "html content", - "landing": "landing page html", - "plain_content": "text content", - "replace": "replacement tag", - "url": "url" + "enabled": True, + "html_content": "html content", + "landing": "landing page html", + "plain_content": "text content", + "replace": "replacement tag", + "url": "url" } response = sg.client.tracking_settings.subscription.patch(request_body=data) print(response.status_code) @@ -108,4 +109,3 @@ print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/user/user.py b/examples/user/user.py index 9e3f24766..be458a977 100644 --- a/examples/user/user.py +++ b/examples/user/user.py @@ -28,7 +28,7 @@ # PUT /user/email # data = { - "email": "example@example.com" + "email": "example@example.com" } response = sg.client.user.email.put(request_body=data) print(response.status_code) @@ -49,8 +49,8 @@ # PUT /user/password # data = { - "new_password": "new_password", - "old_password": "old_password" + "new_password": "new_password", + "old_password": "old_password" } response = sg.client.user.password.put(request_body=data) print(response.status_code) @@ -62,9 +62,9 @@ # PATCH /user/profile # data = { - "city": "Orange", - "first_name": "Example", - "last_name": "User" + "city": "Orange", + "first_name": "Example", + "last_name": "User" } response = sg.client.user.profile.patch(request_body=data) print(response.status_code) @@ -85,8 +85,8 @@ # POST /user/scheduled_sends # data = { - "batch_id": "YOUR_BATCH_ID", - "status": "pause" + "batch_id": "YOUR_BATCH_ID", + "status": "pause" } response = sg.client.user.scheduled_sends.post(request_body=data) print(response.status_code) @@ -107,7 +107,7 @@ # PATCH /user/scheduled_sends/{batch_id} # data = { - "status": "pause" + "status": "pause" } batch_id = "test_url_param" response = sg.client.user.scheduled_sends._(batch_id).patch(request_body=data) @@ -140,8 +140,8 @@ # PATCH /user/settings/enforced_tls # data = { - "require_tls": True, - "require_valid_cert": False + "require_tls": True, + "require_valid_cert": False } response = sg.client.user.settings.enforced_tls.patch(request_body=data) print(response.status_code) @@ -162,7 +162,7 @@ # PUT /user/username # data = { - "username": "test_username" + "username": "test_username" } response = sg.client.user.username.put(request_body=data) print(response.status_code) @@ -183,19 +183,19 @@ # PATCH /user/webhooks/event/settings # data = { - "bounce": True, - "click": True, - "deferred": True, - "delivered": True, - "dropped": True, - "enabled": True, - "group_resubscribe": True, - "group_unsubscribe": True, - "open": True, - "processed": True, - "spam_report": True, - "unsubscribe": True, - "url": "url" + "bounce": True, + "click": True, + "deferred": True, + "delivered": True, + "dropped": True, + "enabled": True, + "group_resubscribe": True, + "group_unsubscribe": True, + "open": True, + "processed": True, + "spam_report": True, + "unsubscribe": True, + "url": "url" } response = sg.client.user.webhooks.event.settings.patch(request_body=data) print(response.status_code) @@ -216,7 +216,7 @@ # POST /user/webhooks/event/test # data = { - "url": "url" + "url": "url" } response = sg.client.user.webhooks.event.test.post(request_body=data) print(response.status_code) @@ -228,10 +228,10 @@ # POST /user/webhooks/parse/settings # data = { - "hostname": "myhostname.com", - "send_raw": False, - "spam_check": True, - "url": "http://email.myhosthame.com" + "hostname": "myhostname.com", + "send_raw": False, + "spam_check": True, + "url": "http://email.myhosthame.com" } response = sg.client.user.webhooks.parse.settings.post(request_body=data) print(response.status_code) @@ -252,12 +252,13 @@ # PATCH /user/webhooks/parse/settings/{hostname} # data = { - "send_raw": True, - "spam_check": False, - "url": "http://newdomain.com/parse" + "send_raw": True, + "spam_check": False, + "url": "http://newdomain.com/parse" } hostname = "test_url_param" -response = sg.client.user.webhooks.parse.settings._(hostname).patch(request_body=data) +response = sg.client.user.webhooks.parse.settings._( + hostname).patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) @@ -286,9 +287,9 @@ # Retrieves Inbound Parse Webhook statistics. # # GET /user/webhooks/parse/stats # -params = {'aggregated_by': 'day', 'limit': 'test_string', 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 'test_string'} +params = {'aggregated_by': 'day', 'limit': 'test_string', + 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 'test_string'} response = sg.client.user.webhooks.parse.stats.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) - diff --git a/examples/whitelabel/whitelabel.py b/examples/whitelabel/whitelabel.py index f529d3ed2..d528e8767 100644 --- a/examples/whitelabel/whitelabel.py +++ b/examples/whitelabel/whitelabel.py @@ -10,16 +10,16 @@ # POST /whitelabel/domains # data = { - "automatic_security": False, - "custom_spf": True, - "default": True, - "domain": "example.com", - "ips": [ - "192.168.1.1", - "192.168.1.2" - ], - "subdomain": "news", - "username": "john@example.com" + "automatic_security": False, + "custom_spf": True, + "default": True, + "domain": "example.com", + "ips": [ + "192.168.1.1", + "192.168.1.2" + ], + "subdomain": "news", + "username": "john@example.com" } response = sg.client.whitelabel.domains.post(request_body=data) print(response.status_code) @@ -30,7 +30,8 @@ # List all domain whitelabels. # # GET /whitelabel/domains # -params = {'username': 'test_string', 'domain': 'test_string', 'exclude_subusers': 'true', 'limit': 1, 'offset': 1} +params = {'username': 'test_string', 'domain': 'test_string', + 'exclude_subusers': 'true', 'limit': 1, 'offset': 1} response = sg.client.whitelabel.domains.get(query_params=params) print(response.status_code) print(response.body) @@ -68,8 +69,8 @@ # PATCH /whitelabel/domains/{domain_id} # data = { - "custom_spf": True, - "default": False + "custom_spf": True, + "default": False } domain_id = "test_url_param" response = sg.client.whitelabel.domains._(domain_id).patch(request_body=data) @@ -102,10 +103,11 @@ # POST /whitelabel/domains/{domain_id}/subuser # data = { - "username": "jane@example.com" + "username": "jane@example.com" } domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).subuser.post(request_body=data) +response = sg.client.whitelabel.domains._( + domain_id).subuser.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) @@ -115,7 +117,7 @@ # POST /whitelabel/domains/{id}/ips # data = { - "ip": "192.168.0.1" + "ip": "192.168.0.1" } id = "test_url_param" response = sg.client.whitelabel.domains._(id).ips.post(request_body=data) @@ -149,9 +151,9 @@ # POST /whitelabel/ips # data = { - "domain": "example.com", - "ip": "192.168.1.1", - "subdomain": "email" + "domain": "example.com", + "ip": "192.168.1.1", + "subdomain": "email" } response = sg.client.whitelabel.ips.post(request_body=data) print(response.status_code) @@ -203,12 +205,13 @@ # POST /whitelabel/links # data = { - "default": True, - "domain": "example.com", - "subdomain": "mail" + "default": True, + "domain": "example.com", + "subdomain": "mail" } params = {'limit': 1, 'offset': 1} -response = sg.client.whitelabel.links.post(request_body=data, query_params=params) +response = sg.client.whitelabel.links.post( + request_body=data, query_params=params) print(response.status_code) print(response.body) print(response.headers) @@ -258,7 +261,7 @@ # PATCH /whitelabel/links/{id} # data = { - "default": True + "default": True } id = "test_url_param" response = sg.client.whitelabel.links._(id).patch(request_body=data) @@ -301,11 +304,11 @@ # POST /whitelabel/links/{link_id}/subuser # data = { - "username": "jane@example.com" + "username": "jane@example.com" } link_id = "test_url_param" -response = sg.client.whitelabel.links._(link_id).subuser.post(request_body=data) +response = sg.client.whitelabel.links._( + link_id).subuser.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) - diff --git a/register.py b/register.py index 340a37325..d65f71004 100644 --- a/register.py +++ b/register.py @@ -2,13 +2,13 @@ import os output = pypandoc.convert('README.md', 'rst') -f = open('README.txt','w+') +f = open('README.txt', 'w+') f.write(str(output.encode('utf-8'))) f.close() readme_rst = open('./README.txt').read() replace = '.. figure:: https://uiux.s3.amazonaws.com/2016-logos/email-logo%402x.png\n :alt: SendGrid Logo\n\n SendGrid Logo\n' replacement = '|SendGrid Logo|\n\n.. |SendGrid Logo| image:: https://uiux.s3.amazonaws.com/2016-logos/email-logo%402x.png\n :target: https://www.sendgrid.com' -final_text = readme_rst.replace(replace,replacement) +final_text = readme_rst.replace(replace, replacement) with open('./README.txt', 'w') as f: - f.write(final_text) + f.write(final_text) diff --git a/sendgrid/helpers/inbound/config.py b/sendgrid/helpers/inbound/config.py index 49242f54e..64f84162f 100644 --- a/sendgrid/helpers/inbound/config.py +++ b/sendgrid/helpers/inbound/config.py @@ -5,6 +5,7 @@ class Config(object): """All configuration for this app is loaded here""" + def __init__(self, **opts): if os.environ.get('ENV') != 'prod': # We are not in Heroku self.init_environment() @@ -35,8 +36,6 @@ def init_environment(): if len(var) == 2: os.environ[var[0]] = var[1] - - @property def debug_mode(self): return self._debug_mode diff --git a/sendgrid/helpers/inbound/parse.py b/sendgrid/helpers/inbound/parse.py index 77b6683ad..49627a121 100644 --- a/sendgrid/helpers/inbound/parse.py +++ b/sendgrid/helpers/inbound/parse.py @@ -7,6 +7,7 @@ class Parse(object): + def __init__(self, config, request): self._keys = config.keys self._request = request diff --git a/sendgrid/helpers/inbound/send.py b/sendgrid/helpers/inbound/send.py index cc3d95612..c9640c7e3 100644 --- a/sendgrid/helpers/inbound/send.py +++ b/sendgrid/helpers/inbound/send.py @@ -11,6 +11,7 @@ class Send(object): + def __init__(self, url): self._url = url diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index a2159b25d..5661c9b52 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -3,6 +3,7 @@ class Mail(object): """Creates the response body for v3/mail/send""" + def __init__( self, from_email=None, subject=None, to_email=None, content=None): self._from_email = None @@ -267,7 +268,7 @@ def __init__(self, email=None, name=None): # allows passing emails as "dude Fella " self.parse_email(email) else: - #allows backwards compatibility for Email(email, name) + # allows backwards compatibility for Email(email, name) if email is not None: self.email = email self.name = name @@ -302,9 +303,9 @@ def parse_email(self, email_info): import rfc822 except ImportError: import email.utils as rfc822 - + name, email = rfc822.parseaddr(email_info) - + # more than likely a string was passed here instead of an email address if "@" not in email: name = email @@ -312,7 +313,7 @@ def parse_email(self, email_info): if not name: name = None - + if not email: email = None @@ -320,6 +321,7 @@ def parse_email(self, email_info): self.email = email return name, email + class Content(object): def __init__(self, type_=None, value=None): diff --git a/test/test_email.py b/test/test_email.py index 92ae10aaa..902c59d4e 100644 --- a/test/test_email.py +++ b/test/test_email.py @@ -10,18 +10,19 @@ class TestEmailObject(unittest.TestCase): + def test_add_email_address(self): address = "test@example.com" email = Email(address) self.assertEqual(email.email, "test@example.com") - + def test_add_name(self): name = "SomeName" email = Email(name=name) self.assertEqual(email.name, name) - + def test_add_name_email(self): name = "SomeName" address = "test@example.com" @@ -43,7 +44,7 @@ def test_add_rfc_email(self): email = Email(name_address) self.assertEqual(email.name, name) self.assertEqual(email.email, "test@example.com") - + def test_empty_obj_add_name(self): email = Email() name = "SomeName" @@ -56,4 +57,4 @@ def test_empty_obj_add_email(self): address = "test@example.com" email.email = address - self.assertEqual(email.email, address) \ No newline at end of file + self.assertEqual(email.email, address) diff --git a/test/test_mail.py b/test/test_mail.py index 3356eef52..6e1f00481 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -411,7 +411,6 @@ def test_kitchenSink(self): ) def test_unicode_values_in_substitutions_helper(self): - """ Test that the Substitutions helper accepts unicode values """ self.maxDiff = None diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index 73b6b2935..290b42d6d 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -45,10 +45,13 @@ def setUpClass(cls): 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')) + 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')) + prism_cmd = os.path.expanduser( + os.path.join('~', 'bin', 'prism')) else: raise RuntimeError() except Exception as e: @@ -121,16 +124,18 @@ def test_reset_request_headers(self): self.assertNotIn('blah', self.sg.client.request_headers) self.assertNotIn('blah2x', self.sg.client.request_headers) - for k,v in self.sg._get_default_headers().items(): + for k, v in self.sg._get_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") + 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'}) + 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} From 7c1937276c79c187c1f656db370165a412a2b672 Mon Sep 17 00:00:00 2001 From: Piotr Szwarc Date: Sat, 28 Oct 2017 09:50:37 +0200 Subject: [PATCH 017/462] Updated according to PR comments. --- examples/clients/clients.py | 6 ++++-- examples/contactdb/contactdb.py | 6 ++++-- examples/devices/devices.py | 4 +++- examples/geo/geo.py | 8 ++++++-- examples/helpers/mail/mail_example.py | 21 ++++++++++++++++----- 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/examples/clients/clients.py b/examples/clients/clients.py index 80905d12f..1a36fdd0d 100644 --- a/examples/clients/clients.py +++ b/examples/clients/clients.py @@ -10,7 +10,8 @@ # GET /clients/stats # params = {'aggregated_by': 'day', - 'start_date': '2016-01-01', 'end_date': '2016-04-01'} + '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) @@ -21,7 +22,8 @@ # GET /clients/{client_type}/stats # params = {'aggregated_by': 'day', - 'start_date': '2016-01-01', 'end_date': '2016-04-01'} + '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) diff --git a/examples/contactdb/contactdb.py b/examples/contactdb/contactdb.py index b9da0ee2a..f07336c1d 100644 --- a/examples/contactdb/contactdb.py +++ b/examples/contactdb/contactdb.py @@ -93,7 +93,8 @@ params = {'list_id': 1} list_id = "test_url_param" response = sg.client.contactdb.lists._(list_id).patch( - request_body=data, query_params=params) + request_body=data, + query_params=params) print(response.status_code) print(response.body) print(response.headers) @@ -362,7 +363,8 @@ params = {'segment_id': 'test_string'} segment_id = "test_url_param" response = sg.client.contactdb.segments._(segment_id).patch( - request_body=data, query_params=params) + request_body=data, + query_params=params) print(response.status_code) print(response.body) print(response.headers) diff --git a/examples/devices/devices.py b/examples/devices/devices.py index c779bfb65..8fab92921 100644 --- a/examples/devices/devices.py +++ b/examples/devices/devices.py @@ -10,7 +10,9 @@ # GET /devices/stats # params = {'aggregated_by': 'day', 'limit': 1, - 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 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) diff --git a/examples/geo/geo.py b/examples/geo/geo.py index e68b55792..78bf1552f 100644 --- a/examples/geo/geo.py +++ b/examples/geo/geo.py @@ -9,8 +9,12 @@ # Retrieve email statistics by country and state/province. # # GET /geo/stats # -params = {'end_date': '2016-04-01', 'country': 'US', 'aggregated_by': 'day', - 'limit': 1, 'offset': 1, 'start_date': '2016-01-01'} +params = {'end_date': '2016-04-01', + 'country': 'US', + 'aggregated_by': 'day', + 'limit': 1, + 'offset': 1, + 'start_date': '2016-01-01'} response = sg.client.geo.stats.get(query_params=params) print(response.status_code) print(response.body) diff --git a/examples/helpers/mail/mail_example.py b/examples/helpers/mail/mail_example.py index 46eeaff59..18194b352 100644 --- a/examples/helpers/mail/mail_example.py +++ b/examples/helpers/mail/mail_example.py @@ -109,7 +109,9 @@ def build_kitchen_sink(): 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") + True, + "Footer Text", + "Footer Text") mail_settings.sandbox_mode = SandBoxMode(True) mail_settings.spam_check = SpamCheck( True, 1, "https://spamcatcher.sendgrid.com") @@ -118,11 +120,20 @@ def build_kitchen_sink(): 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") + 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") + True, + "some source", + "some medium", + "some term", + "some_content", + "some_campaign") mail.tracking_settings = tracking_settings mail.reply_to = Email("test@example.com") From 47de889f7f4a123ebe97fd6a0f4aabe7afa55d0a Mon Sep 17 00:00:00 2001 From: Piotr Szwarc Date: Sat, 28 Oct 2017 10:00:30 +0200 Subject: [PATCH 018/462] Fix PEP8 E501 where possible. --- examples/browsers/browsers.py | 8 ++++++-- examples/categories/categories.py | 9 +++++++-- examples/mailboxproviders/mailboxproviders.py | 8 ++++++-- examples/subusers/subusers.py | 16 ++++++++++++---- examples/user/user.py | 7 +++++-- 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/examples/browsers/browsers.py b/examples/browsers/browsers.py index a24f88fd9..eb0b9b8ad 100644 --- a/examples/browsers/browsers.py +++ b/examples/browsers/browsers.py @@ -9,8 +9,12 @@ # Retrieve email statistics by browser. # # GET /browsers/stats # -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'browsers': 'test_string', - 'limit': 'test_string', 'offset': 'test_string', 'start_date': '2016-01-01'} +params = {'end_date': '2016-04-01', + 'aggregated_by': 'day', + 'browsers': 'test_string', + 'limit': 'test_string', + 'offset': 'test_string', + 'start_date': '2016-01-01'} response = sg.client.browsers.stats.get(query_params=params) print(response.status_code) print(response.body) diff --git a/examples/categories/categories.py b/examples/categories/categories.py index 1b0297f2f..774284ee8 100644 --- a/examples/categories/categories.py +++ b/examples/categories/categories.py @@ -30,8 +30,13 @@ # Retrieve sums of email stats for each category [Needs: Stats object defined, has category ID?] # # GET /categories/stats/sums # -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, - 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} +params = {'end_date': '2016-04-01', + 'aggregated_by': 'day', + 'limit': 1, + 'sort_by_metric': 'test_string', + 'offset': 1, + 'start_date': '2016-01-01', + 'sort_by_direction': 'asc'} response = sg.client.categories.stats.sums.get(query_params=params) print(response.status_code) print(response.body) diff --git a/examples/mailboxproviders/mailboxproviders.py b/examples/mailboxproviders/mailboxproviders.py index 0fdf0ec15..a95b388b4 100644 --- a/examples/mailboxproviders/mailboxproviders.py +++ b/examples/mailboxproviders/mailboxproviders.py @@ -9,8 +9,12 @@ # Retrieve email statistics by mailbox provider. # # GET /mailbox_providers/stats # -params = {'end_date': '2016-04-01', 'mailbox_providers': 'test_string', - 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01'} +params = {'end_date': '2016-04-01', + 'mailbox_providers': 'test_string', + 'aggregated_by': 'day', + 'limit': 1, + 'offset': 1, + 'start_date': '2016-01-01'} response = sg.client.mailbox_providers.stats.get(query_params=params) print(response.status_code) print(response.body) diff --git a/examples/subusers/subusers.py b/examples/subusers/subusers.py index 4d59e77b8..1ac4786ba 100644 --- a/examples/subusers/subusers.py +++ b/examples/subusers/subusers.py @@ -47,8 +47,12 @@ # Retrieve email statistics for your subusers. # # GET /subusers/stats # -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, - 'offset': 1, 'start_date': '2016-01-01', 'subusers': 'test_string'} +params = {'end_date': '2016-04-01', + 'aggregated_by': 'day', + 'limit': 1, + 'offset': 1, + 'start_date': '2016-01-01', + 'subusers': 'test_string'} response = sg.client.subusers.stats.get(query_params=params) print(response.status_code) print(response.body) @@ -58,8 +62,12 @@ # Retrieve monthly stats for all subusers # # GET /subusers/stats/monthly # -params = {'subuser': 'test_string', 'limit': 1, 'sort_by_metric': 'test_string', - 'offset': 1, 'date': 'test_string', 'sort_by_direction': 'asc'} +params = {'subuser': 'test_string', + 'limit': 1, + 'sort_by_metric': 'test_string', + 'offset': 1, + 'date': 'test_string', + 'sort_by_direction': 'asc'} response = sg.client.subusers.stats.monthly.get(query_params=params) print(response.status_code) print(response.body) diff --git a/examples/user/user.py b/examples/user/user.py index be458a977..c7aabd4ac 100644 --- a/examples/user/user.py +++ b/examples/user/user.py @@ -287,8 +287,11 @@ # Retrieves Inbound Parse Webhook statistics. # # GET /user/webhooks/parse/stats # -params = {'aggregated_by': 'day', 'limit': 'test_string', - 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 'test_string'} +params = {'aggregated_by': 'day', + 'limit': 'test_string', + 'start_date': '2016-01-01', + 'end_date': '2016-04-01', + 'offset': 'test_string'} response = sg.client.user.webhooks.parse.stats.get(query_params=params) print(response.status_code) print(response.body) From 0bd8dc14b6bcb5ebdadb67f3c3db15e30872ee64 Mon Sep 17 00:00:00 2001 From: Piotr Szwarc Date: Sat, 28 Oct 2017 10:06:57 +0200 Subject: [PATCH 019/462] Fix PEP8 E501 where possible. --- examples/subusers/subusers.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/examples/subusers/subusers.py b/examples/subusers/subusers.py index 1ac4786ba..3d79bfb11 100644 --- a/examples/subusers/subusers.py +++ b/examples/subusers/subusers.py @@ -77,8 +77,13 @@ # Retrieve the totals for each email statistic metric for all subusers. # # GET /subusers/stats/sums # -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, - 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} +params = {'end_date': '2016-04-01', + 'aggregated_by': 'day', + 'limit': 1, + 'sort_by_metric': 'test_string', + 'offset': 1, + 'start_date': '2016-01-01', + 'sort_by_direction': 'asc'} response = sg.client.subusers.stats.sums.get(query_params=params) print(response.status_code) print(response.body) @@ -172,8 +177,11 @@ # Retrieve the monthly email statistics for a single subuser # # GET /subusers/{subuser_name}/stats/monthly # -params = {'date': 'test_string', 'sort_by_direction': 'asc', - 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1} +params = {'date': 'test_string', + 'sort_by_direction': 'asc', + 'limit': 1, + 'sort_by_metric': 'test_string', + 'offset': 1} subuser_name = "test_url_param" response = sg.client.subusers._( subuser_name).stats.monthly.get(query_params=params) From 1b9f336880b683e9c571185c8ba40da46d2c83d5 Mon Sep 17 00:00:00 2001 From: navinpai Date: Sun, 29 Oct 2017 00:28:24 +0530 Subject: [PATCH 020/462] Add slack event integration --- USE_CASES.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/USE_CASES.md b/USE_CASES.md index 810d345ca..b59981e19 100644 --- a/USE_CASES.md +++ b/USE_CASES.md @@ -7,6 +7,7 @@ This documentation provides examples for specific use cases. Please [open an iss * [How to Setup a Domain Whitelabel](#domain_whitelabel) * [How to View Email Statistics](#email_stats) * [Asynchronous Mail Send](#asynchronous-mail-send) +* [Slack Event API Integration](#slack_event_integration) # Transactional Templates @@ -272,3 +273,50 @@ if __name__ == "__main__": loop.run_until_complete(task) ``` + +# 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 +import sendgrid +from sendgrid.helpers.mail import * + +sg = sendgrid.SendGridAPIClient(apikey=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 = Email("slack_integration@example.com") + to_email = Email("test@example.com") + subject = "Psst... Someone needs help!" + content = Content("text/plain", message) + mail = Mail(from_email, subject, to_email, content) + response = sg.client.mail.send.post(request_body=mail.get()) + return response.status_code + +# Start the slack event listener server on port 3000 +slack_events_adapter.start(port=3000) +``` From 57c30401fab8c8883fd5bd5a7e6d9de7ff1bbcf8 Mon Sep 17 00:00:00 2001 From: heisendumb Date: Mon, 30 Oct 2017 00:26:08 -0200 Subject: [PATCH 021/462] docker-compose for issue #444 --- docker/Dockerfile | 4 +- docker/Makefile | 28 ++ docker/docker-compose.yml | 24 ++ .../examples/accesssettings/accesssettings.py | 84 ++++ docker/examples/alerts/alerts.py | 63 +++ docker/examples/apikeys/apikeys.py | 85 ++++ docker/examples/asm/asm.py | 174 ++++++++ docker/examples/browsers/browsers.py | 17 + docker/examples/campaigns/campaigns.py | 154 +++++++ docker/examples/categories/categories.py | 37 ++ docker/examples/clients/clients.py | 28 ++ docker/examples/contactdb/contactdb.py | 396 ++++++++++++++++++ docker/examples/devices/devices.py | 17 + docker/examples/geo/geo.py | 17 + docker/examples/helpers/mail/mail_example.py | 219 ++++++++++ docker/examples/ips/ips.py | 155 +++++++ docker/examples/mail/mail.py | 174 ++++++++ .../mailboxproviders/mailboxproviders.py | 17 + docker/examples/mailsettings/mailsettings.py | 220 ++++++++++ .../partnersettings/partnersettings.py | 40 ++ docker/examples/scopes/scopes.py | 16 + docker/examples/senders/senders.py | 99 +++++ docker/examples/stats/stats.py | 17 + docker/examples/subusers/subusers.py | 170 ++++++++ docker/examples/suppression/suppression.py | 202 +++++++++ .../trackingsettings/trackingsettings.py | 111 +++++ docker/examples/user/user.py | 294 +++++++++++++ docker/examples/whitelabel/whitelabel.py | 311 ++++++++++++++ 28 files changed, 3171 insertions(+), 2 deletions(-) create mode 100644 docker/Makefile create mode 100644 docker/docker-compose.yml create mode 100644 docker/examples/accesssettings/accesssettings.py create mode 100644 docker/examples/alerts/alerts.py create mode 100644 docker/examples/apikeys/apikeys.py create mode 100644 docker/examples/asm/asm.py create mode 100644 docker/examples/browsers/browsers.py create mode 100644 docker/examples/campaigns/campaigns.py create mode 100644 docker/examples/categories/categories.py create mode 100644 docker/examples/clients/clients.py create mode 100644 docker/examples/contactdb/contactdb.py create mode 100644 docker/examples/devices/devices.py create mode 100644 docker/examples/geo/geo.py create mode 100644 docker/examples/helpers/mail/mail_example.py create mode 100644 docker/examples/ips/ips.py create mode 100644 docker/examples/mail/mail.py create mode 100644 docker/examples/mailboxproviders/mailboxproviders.py create mode 100644 docker/examples/mailsettings/mailsettings.py create mode 100644 docker/examples/partnersettings/partnersettings.py create mode 100644 docker/examples/scopes/scopes.py create mode 100644 docker/examples/senders/senders.py create mode 100644 docker/examples/stats/stats.py create mode 100644 docker/examples/subusers/subusers.py create mode 100644 docker/examples/suppression/suppression.py create mode 100644 docker/examples/trackingsettings/trackingsettings.py create mode 100644 docker/examples/user/user.py create mode 100644 docker/examples/whitelabel/whitelabel.py diff --git a/docker/Dockerfile b/docker/Dockerfile index bc4ce8e79..393d91bfe 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -30,8 +30,8 @@ RUN python2.7 get-pip.py && \ # set up default sendgrid env WORKDIR /root/sources -RUN git clone https://github.com/sendgrid/sendgrid-python.git && \ - git clone https://github.com/sendgrid/python-http-client.git +RUN git clone https://github.com/sendgrid/sendgrid-python.git --branch && \ + git clone https://github.com/sendgrid/python-http-client.git --branch 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..6ec47459d --- /dev/null +++ b/docker/Makefile @@ -0,0 +1,28 @@ +# import deploy config +deployfile=deploy.env +ifdef dpl +deployfile=$(dpl) +endif +include $(deployfile) +export $(shell sed 's/=.*//' $(deployfile)) + +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 dev environment is alive :D" diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 000000000..d35772fd2 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,24 @@ +version: "3.3" + +services: + sendgrid: + image: sendgrid/sendgrid-python:${TAG} + restart: unless-stopped + container_name: sendgrid-prod + volumes: + - ${PATH_TO_SENDGRID-PYTHON_PROD}:/mnt/sendgrid-python + - ${PATH_TO_HTTP-CLIENT_PROD}:/mnt/python-http-client + 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 + env_file: .env + volumes: + - ${PATH_TO_SENDGRID-PYTHON}:/mnt/sendgrid-python + - ${PATH_TO_HTTP-CLIENT}:/mnt/python-http-client diff --git a/docker/examples/accesssettings/accesssettings.py b/docker/examples/accesssettings/accesssettings.py new file mode 100644 index 000000000..aac0e4a54 --- /dev/null +++ b/docker/examples/accesssettings/accesssettings.py @@ -0,0 +1,84 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Retrieve all recent access attempts # +# GET /access_settings/activity # + +params = {'limit': 1} +response = sg.client.access_settings.activity.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Add one or more IPs to the whitelist # +# POST /access_settings/whitelist # + +data = { + "ips": [ + { + "ip": "192.168.1.1" + }, + { + "ip": "192.*.*.*" + }, + { + "ip": "192.168.1.3/32" + } + ] +} +response = sg.client.access_settings.whitelist.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve a list of currently whitelisted IPs # +# GET /access_settings/whitelist # + +response = sg.client.access_settings.whitelist.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Remove one or more IPs from the whitelist # +# DELETE /access_settings/whitelist # + +data = { + "ids": [ + 1, + 2, + 3 + ] +} +response = sg.client.access_settings.whitelist.delete(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve a specific whitelisted IP # +# GET /access_settings/whitelist/{rule_id} # + +rule_id = "test_url_param" +response = sg.client.access_settings.whitelist._(rule_id).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Remove a specific IP from the whitelist # +# DELETE /access_settings/whitelist/{rule_id} # + +rule_id = "test_url_param" +response = sg.client.access_settings.whitelist._(rule_id).delete() +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/alerts/alerts.py b/docker/examples/alerts/alerts.py new file mode 100644 index 000000000..e30d48748 --- /dev/null +++ b/docker/examples/alerts/alerts.py @@ -0,0 +1,63 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Create a new Alert # +# POST /alerts # + +data = { + "email_to": "example@example.com", + "frequency": "daily", + "type": "stats_notification" +} +response = sg.client.alerts.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all alerts # +# GET /alerts # + +response = sg.client.alerts.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update an alert # +# PATCH /alerts/{alert_id} # + +data = { + "email_to": "example@example.com" +} +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) + +################################################## +# Retrieve a specific alert # +# GET /alerts/{alert_id} # + +alert_id = "test_url_param" +response = sg.client.alerts._(alert_id).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete an alert # +# DELETE /alerts/{alert_id} # + +alert_id = "test_url_param" +response = sg.client.alerts._(alert_id).delete() +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/apikeys/apikeys.py b/docker/examples/apikeys/apikeys.py new file mode 100644 index 000000000..42c3afa10 --- /dev/null +++ b/docker/examples/apikeys/apikeys.py @@ -0,0 +1,85 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Create API keys # +# POST /api_keys # + +data = { + "name": "My API Key", + "sample": "data", + "scopes": [ + "mail.send", + "alerts.create", + "alerts.read" + ] +} +response = sg.client.api_keys.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all API Keys belonging to the authenticated user # +# GET /api_keys # + +params = {'limit': 1} +response = sg.client.api_keys.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update the name & scopes of an API Key # +# PUT /api_keys/{api_key_id} # + +data = { + "name": "A New Hope", + "scopes": [ + "user.profile.read", + "user.profile.update" + ] +} +api_key_id = "test_url_param" +response = sg.client.api_keys._(api_key_id).put(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update API keys # +# PATCH /api_keys/{api_key_id} # + +data = { + "name": "A New Hope" +} +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) + +################################################## +# Retrieve an existing API Key # +# GET /api_keys/{api_key_id} # + +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) + +################################################## +# Delete API keys # +# DELETE /api_keys/{api_key_id} # + +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) + diff --git a/docker/examples/asm/asm.py b/docker/examples/asm/asm.py new file mode 100644 index 000000000..43130cf06 --- /dev/null +++ b/docker/examples/asm/asm.py @@ -0,0 +1,174 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Create a new suppression group # +# POST /asm/groups # + +data = { + "description": "Suggestions for products our users might like.", + "is_default": True, + "name": "Product Suggestions" +} +response = sg.client.asm.groups.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve information about multiple suppression groups # +# GET /asm/groups # + +params = {'id': 1} +response = sg.client.asm.groups.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update a suppression group. # +# PATCH /asm/groups/{group_id} # + +data = { + "description": "Suggestions for items our users might like.", + "id": 103, + "name": "Item Suggestions" +} +group_id = "test_url_param" +response = sg.client.asm.groups._(group_id).patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Get information on a single suppression group. # +# GET /asm/groups/{group_id} # + +group_id = "test_url_param" +response = sg.client.asm.groups._(group_id).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete a suppression group. # +# DELETE /asm/groups/{group_id} # + +group_id = "test_url_param" +response = sg.client.asm.groups._(group_id).delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Add suppressions to a suppression group # +# POST /asm/groups/{group_id}/suppressions # + +data = { + "recipient_emails": [ + "test1@example.com", + "test2@example.com" + ] +} +group_id = "test_url_param" +response = sg.client.asm.groups._(group_id).suppressions.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all suppressions for a suppression group # +# GET /asm/groups/{group_id}/suppressions # + +group_id = "test_url_param" +response = sg.client.asm.groups._(group_id).suppressions.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Search for suppressions within a group # +# POST /asm/groups/{group_id}/suppressions/search # + +data = { + "recipient_emails": [ + "exists1@example.com", + "exists2@example.com", + "doesnotexists@example.com" + ] +} +group_id = "test_url_param" +response = sg.client.asm.groups._(group_id).suppressions.search.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete a suppression from a suppression group # +# DELETE /asm/groups/{group_id}/suppressions/{email} # + +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) + +################################################## +# Retrieve all suppressions # +# GET /asm/suppressions # + +response = sg.client.asm.suppressions.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Add recipient addresses to the global suppression group. # +# POST /asm/suppressions/global # + +data = { + "recipient_emails": [ + "test1@example.com", + "test2@example.com" + ] +} +response = sg.client.asm.suppressions._("global").post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve a Global Suppression # +# GET /asm/suppressions/global/{email} # + +email = "test_url_param" +response = sg.client.asm.suppressions._("global")._(email).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete a Global Suppression # +# DELETE /asm/suppressions/global/{email} # + +email = "test_url_param" +response = sg.client.asm.suppressions._("global")._(email).delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all suppression groups for an email address # +# GET /asm/suppressions/{email} # + +email = "test_url_param" +response = sg.client.asm.suppressions._(email).get() +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/browsers/browsers.py b/docker/examples/browsers/browsers.py new file mode 100644 index 000000000..c123c12e5 --- /dev/null +++ b/docker/examples/browsers/browsers.py @@ -0,0 +1,17 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Retrieve email statistics by browser. # +# GET /browsers/stats # + +params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'browsers': 'test_string', 'limit': 'test_string', 'offset': 'test_string', 'start_date': '2016-01-01'} +response = sg.client.browsers.stats.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/campaigns/campaigns.py b/docker/examples/campaigns/campaigns.py new file mode 100644 index 000000000..c77fc878b --- /dev/null +++ b/docker/examples/campaigns/campaigns.py @@ -0,0 +1,154 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Create a Campaign # +# POST /campaigns # + +data = { + "categories": [ + "spring line" + ], + "custom_unsubscribe_url": "", + "html_content": "

Check out our spring line!

", + "ip_pool": "marketing", + "list_ids": [ + 110, + 124 + ], + "plain_content": "Check out our spring line!", + "segment_ids": [ + 110 + ], + "sender_id": 124451, + "subject": "New Products for Spring!", + "suppression_group_id": 42, + "title": "March Newsletter" +} +response = sg.client.campaigns.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all Campaigns # +# GET /campaigns # + +params = {'limit': 1, 'offset': 1} +response = sg.client.campaigns.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update a Campaign # +# PATCH /campaigns/{campaign_id} # + +data = { + "categories": [ + "summer line" + ], + "html_content": "

Check out our summer line!

", + "plain_content": "Check out our summer line!", + "subject": "New Products for Summer!", + "title": "May Newsletter" +} +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) + +################################################## +# Retrieve a single campaign # +# GET /campaigns/{campaign_id} # + +campaign_id = "test_url_param" +response = sg.client.campaigns._(campaign_id).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete a Campaign # +# DELETE /campaigns/{campaign_id} # + +campaign_id = "test_url_param" +response = sg.client.campaigns._(campaign_id).delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update a Scheduled Campaign # +# PATCH /campaigns/{campaign_id}/schedules # + +data = { + "send_at": 1489451436 +} +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) + +################################################## +# Schedule a Campaign # +# POST /campaigns/{campaign_id}/schedules # + +data = { + "send_at": 1489771528 +} +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) + +################################################## +# View Scheduled Time of a Campaign # +# GET /campaigns/{campaign_id}/schedules # + +campaign_id = "test_url_param" +response = sg.client.campaigns._(campaign_id).schedules.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Unschedule a Scheduled Campaign # +# DELETE /campaigns/{campaign_id}/schedules # + +campaign_id = "test_url_param" +response = sg.client.campaigns._(campaign_id).schedules.delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Send a Campaign # +# POST /campaigns/{campaign_id}/schedules/now # + +campaign_id = "test_url_param" +response = sg.client.campaigns._(campaign_id).schedules.now.post() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Send a Test Campaign # +# POST /campaigns/{campaign_id}/schedules/test # + +data = { + "to": "your.email@example.com" +} +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) + diff --git a/docker/examples/categories/categories.py b/docker/examples/categories/categories.py new file mode 100644 index 000000000..7984f0fe0 --- /dev/null +++ b/docker/examples/categories/categories.py @@ -0,0 +1,37 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Retrieve all categories # +# GET /categories # + +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) + +################################################## +# Retrieve Email Statistics for Categories # +# GET /categories/stats # + +params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01', 'categories': 'test_string'} +response = sg.client.categories.stats.get(query_params=params) +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?] # +# GET /categories/stats/sums # + +params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} +response = sg.client.categories.stats.sums.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/clients/clients.py b/docker/examples/clients/clients.py new file mode 100644 index 000000000..7831ef78f --- /dev/null +++ b/docker/examples/clients/clients.py @@ -0,0 +1,28 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Retrieve email statistics by client type. # +# GET /clients/stats # + +params = {'aggregated_by': 'day', 'start_date': '2016-01-01', 'end_date': '2016-04-01'} +response = sg.client.clients.stats.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve stats by a specific client type. # +# GET /clients/{client_type}/stats # + +params = {'aggregated_by': 'day', 'start_date': '2016-01-01', 'end_date': '2016-04-01'} +client_type = "test_url_param" +response = sg.client.clients._(client_type).stats.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/contactdb/contactdb.py b/docker/examples/contactdb/contactdb.py new file mode 100644 index 000000000..c234d7724 --- /dev/null +++ b/docker/examples/contactdb/contactdb.py @@ -0,0 +1,396 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Create a Custom Field # +# POST /contactdb/custom_fields # + +data = { + "name": "pet", + "type": "text" +} +response = sg.client.contactdb.custom_fields.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all custom fields # +# GET /contactdb/custom_fields # + +response = sg.client.contactdb.custom_fields.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve a Custom Field # +# GET /contactdb/custom_fields/{custom_field_id} # + +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) + +################################################## +# Delete a Custom Field # +# DELETE /contactdb/custom_fields/{custom_field_id} # + +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) + +################################################## +# Create a List # +# POST /contactdb/lists # + +data = { + "name": "your list name" +} +response = sg.client.contactdb.lists.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all lists # +# GET /contactdb/lists # + +response = sg.client.contactdb.lists.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete Multiple lists # +# DELETE /contactdb/lists # + +data = [ + 1, + 2, + 3, + 4 +] +response = sg.client.contactdb.lists.delete(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update a List # +# PATCH /contactdb/lists/{list_id} # + +data = { + "name": "newlistname" +} +params = {'list_id': 1} +list_id = "test_url_param" +response = sg.client.contactdb.lists._(list_id).patch(request_body=data, query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve a single list # +# GET /contactdb/lists/{list_id} # + +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) + +################################################## +# Delete a List # +# DELETE /contactdb/lists/{list_id} # + +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) + +################################################## +# Add Multiple Recipients to a List # +# POST /contactdb/lists/{list_id}/recipients # + +data = [ + "recipient_id1", + "recipient_id2" +] +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) + +################################################## +# Retrieve all recipients on a List # +# GET /contactdb/lists/{list_id}/recipients # + +params = {'page': 1, 'page_size': 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) + +################################################## +# Add a Single Recipient to a List # +# POST /contactdb/lists/{list_id}/recipients/{recipient_id} # + +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) + +################################################## +# Delete a Single Recipient from a Single List # +# DELETE /contactdb/lists/{list_id}/recipients/{recipient_id} # + +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) + +################################################## +# Update Recipient # +# PATCH /contactdb/recipients # + +data = [ + { + "email": "jones@example.com", + "first_name": "Guy", + "last_name": "Jones" + } +] +response = sg.client.contactdb.recipients.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Add recipients # +# POST /contactdb/recipients # + +data = [ + { + "age": 25, + "email": "example@example.com", + "first_name": "", + "last_name": "User" + }, + { + "age": 25, + "email": "example2@example.com", + "first_name": "Example", + "last_name": "User" + } +] +response = sg.client.contactdb.recipients.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve recipients # +# GET /contactdb/recipients # + +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) + +################################################## +# Delete Recipient # +# DELETE /contactdb/recipients # + +data = [ + "recipient_id1", + "recipient_id2" +] +response = sg.client.contactdb.recipients.delete(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve the count of billable recipients # +# GET /contactdb/recipients/billable_count # + +response = sg.client.contactdb.recipients.billable_count.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve a Count of Recipients # +# GET /contactdb/recipients/count # + +response = sg.client.contactdb.recipients.count.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve recipients matching search criteria # +# GET /contactdb/recipients/search # + +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) + +################################################## +# Retrieve a single recipient # +# GET /contactdb/recipients/{recipient_id} # + +recipient_id = "test_url_param" +response = sg.client.contactdb.recipients._(recipient_id).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete a Recipient # +# DELETE /contactdb/recipients/{recipient_id} # + +recipient_id = "test_url_param" +response = sg.client.contactdb.recipients._(recipient_id).delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve the lists that a recipient is on # +# GET /contactdb/recipients/{recipient_id}/lists # + +recipient_id = "test_url_param" +response = sg.client.contactdb.recipients._(recipient_id).lists.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve reserved fields # +# GET /contactdb/reserved_fields # + +response = sg.client.contactdb.reserved_fields.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Create a Segment # +# POST /contactdb/segments # + +data = { + "conditions": [ + { + "and_or": "", + "field": "last_name", + "operator": "eq", + "value": "Miller" + }, + { + "and_or": "and", + "field": "last_clicked", + "operator": "gt", + "value": "01/02/2015" + }, + { + "and_or": "or", + "field": "clicks.campaign_identifier", + "operator": "eq", + "value": "513" + } + ], + "list_id": 4, + "name": "Last Name Miller" +} +response = sg.client.contactdb.segments.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all segments # +# GET /contactdb/segments # + +response = sg.client.contactdb.segments.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update a segment # +# PATCH /contactdb/segments/{segment_id} # + +data = { + "conditions": [ + { + "and_or": "", + "field": "last_name", + "operator": "eq", + "value": "Miller" + } + ], + "list_id": 5, + "name": "The Millers" +} +params = {'segment_id': 'test_string'} +segment_id = "test_url_param" +response = sg.client.contactdb.segments._(segment_id).patch(request_body=data, query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve a segment # +# GET /contactdb/segments/{segment_id} # + +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) + +################################################## +# Delete a segment # +# DELETE /contactdb/segments/{segment_id} # + +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) + +################################################## +# Retrieve recipients on a segment # +# GET /contactdb/segments/{segment_id}/recipients # + +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) + diff --git a/docker/examples/devices/devices.py b/docker/examples/devices/devices.py new file mode 100644 index 000000000..108e98452 --- /dev/null +++ b/docker/examples/devices/devices.py @@ -0,0 +1,17 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Retrieve email statistics by device type. # +# GET /devices/stats # + +params = {'aggregated_by': 'day', 'limit': 1, 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} +response = sg.client.devices.stats.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/geo/geo.py b/docker/examples/geo/geo.py new file mode 100644 index 000000000..7d58ec085 --- /dev/null +++ b/docker/examples/geo/geo.py @@ -0,0 +1,17 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Retrieve email statistics by country and state/province. # +# GET /geo/stats # + +params = {'end_date': '2016-04-01', 'country': 'US', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01'} +response = sg.client.geo.stats.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/helpers/mail/mail_example.py b/docker/examples/helpers/mail/mail_example.py new file mode 100644 index 000000000..bfd8ea718 --- /dev/null +++ b/docker/examples/helpers/mail/mail_example.py @@ -0,0 +1,219 @@ +import json +import os +import urllib2 +from sendgrid.helpers.mail import * +from sendgrid import * + +# NOTE: you will need move this file to the root +# directory of this project to execute properly. + + +def build_hello_email(): + """Minimum required to send an email""" + from_email = Email("test@example.com") + subject = "Hello World from the SendGrid Python Library" + to_email = Email("test@example.com") + content = Content("text/plain", "some text here") + mail = Mail(from_email, subject, to_email, content) + mail.personalizations[0].add_to(Email("test2@example.com")) + + return mail.get() + + +def build_personalization(personalization): + """Build personalization mock instance from a mock dict""" + mock_personalization = Personalization() + for to_addr in personalization['to_list']: + personalization.add_to(to_addr) + + for cc_addr in personalization['cc_list']: + personalization.add_to(cc_addr) + + for bcc_addr in personalization['bcc_list']: + personalization.add_bc(bcc_addr) + + for header in personalization['headers']: + personalization.add_header(header) + + for substitution in personalization['substitutions']: + personalization.add_substitution(substitution) + + for arg in personalization['custom_args']: + personalization.add_custom_arg(arg) + + personalization.subject = personalization['subject'] + personalization.send_at = personalization['send_at'] + return mock_personalization + + +def get_mock_personalization_dict(): + """Get a dict of personalization mock.""" + mock_pers = dict() + + mock_pers['to_list'] = [Email("test1@example.com", + "Example User"), + Email("test2@example.com", + "Example User")] + + mock_pers['cc_list'] = [Email("test3@example.com", + "Example User"), + Email("test4@example.com", + "Example User")] + + mock_pers['bcc_list'] = [Email("test5@example.com"), + Email("test6@example.com")] + + mock_pers['subject'] = ("Hello World from the Personalized " + "SendGrid Python Library") + + mock_pers['headers'] = [Header("X-Test", "test"), + Header("X-Mock", "true")] + + mock_pers['substitutions'] = [Substitution("%name%", "Example User"), + Substitution("%city%", "Denver")] + + mock_pers['custom_args'] = [CustomArg("user_id", "343"), + CustomArg("type", "marketing")] + + mock_pers['send_at'] = 1443636843 + return mock_pers + + +def build_attachment1(): + """Build attachment mock.""" + attachment = Attachment() + attachment.content = ("TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNl" + "Y3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12") + attachment.type = "application/pdf" + attachment.filename = "balance_001.pdf" + attachment.disposition = "attachment" + attachment.content_id = "Balance Sheet" + return attachment + + +def build_attachment2(): + """Build attachment mock.""" + attachment = Attachment() + attachment.content = "BwdW" + attachment.type = "image/png" + attachment.filename = "banner.png" + attachment.disposition = "inline" + attachment.content_id = "Banner" + return attachment + + +def build_mail_settings(): + """Build mail settings mock.""" + mail_settings = MailSettings() + mail_settings.bcc_settings = BCCSettings(True, Email("test@example.com")) + mail_settings.bypass_list_management = BypassListManagement(True) + mail_settings.footer_settings = FooterSettings(True, "Footer Text", + ("Footer " + "Text")) + mail_settings.sandbox_mode = SandBoxMode(True) + mail_settings.spam_check = SpamCheck(True, 1, + "https://spamcatcher.sendgrid.com") + return mail_settings + + +def build_tracking_settings(): + """Build tracking settings mock.""" + tracking_settings = TrackingSettings() + tracking_settings.click_tracking = ClickTracking(True, True) + tracking_settings.open_tracking = OpenTracking(True, + ("Optional tag to " + "replace with the" + "open image in the " + "body of the message")) + + subs_track = SubscriptionTracking(True, + ("text to insert into the " + "text/plain portion of the" + " message"), + ("html to insert " + "into the text/html portion of " + "the message"), + ("Optional tag to replace with " + "the open image in the body of " + "the message")) + + tracking_settings.subscription_tracking = subs_track + tracking_settings.ganalytics = Ganalytics(True, "some source", + "some medium", "some term", + "some_content", "some_campaign") + return tracking_settings + + +def build_kitchen_sink(): + """All settings set""" + mail = Mail() + + mail.from_email = Email("test@example.com", "Example User") + mail.subject = "Hello World from the SendGrid Python Library" + + personalization = get_mock_personalization_dict() + mail.add_personalization(build_personalization(personalization)) + mail.add_personalization(build_personalization(personalization)) + + mail.add_content(Content("text/plain", "some text here")) + mail.add_content(Content("text/html", ("some text " + "here"))) + + mail.add_attachment(build_attachment1()) + mail.add_attachment(build_attachment2()) + + mail.template_id = "13b8f94f-bcae-4ec6-b752-70d6cb59f932" + + mail.add_section(Section("%section1%", "Substitution Text for Section 1")) + mail.add_section(Section("%section2%", "Substitution Text for Section 2")) + + mail.add_header(Header("X-Test1", "test1")) + mail.add_header(Header("X-Test3", "test2")) + + mail.add_category(Category("May")) + mail.add_category(Category("2016")) + + mail.add_custom_arg(CustomArg("campaign", "welcome")) + mail.add_custom_arg(CustomArg("weekday", "morning")) + + mail.send_at = 1443636842 + + # This must be a valid [batch ID] + # (https://sendgrid.com/docs/API_Reference/SMTP_API/scheduling_parameters.html) to work + # mail.set_batch_id("N2VkYjBjYWItMGU4OC0xMWU2LWJhMzYtZjQ1Yzg5OTBkNzkxLWM5ZTUyZjNhOA") + mail.asm = ASM(99, [4, 5, 6, 7, 8]) + mail.ip_pool_name = "24" + mail.mail_settings = build_mail_settings() + mail.tracking_settings = build_tracking_settings() + mail.reply_to = Email("test@example.com") + + return mail.get() + + +def send_hello_email(): + # Assumes you set your environment variable: + # https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key + sg = SendGridAPIClient() + data = build_hello_email() + response = sg.client.mail.send.post(request_body=data) + print(response.status_code) + print(response.headers) + print(response.body) + + +def send_kitchen_sink(): + # Assumes you set your environment variable: + # https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key + sg = SendGridAPIClient() + data = build_kitchen_sink() + response = sg.client.mail.send.post(request_body=data) + print(response.status_code) + print(response.headers) + print(response.body) + + +# this will actually send an email +send_hello_email() + +# this will only send an email if you set SandBox Mode to False +send_kitchen_sink() diff --git a/docker/examples/ips/ips.py b/docker/examples/ips/ips.py new file mode 100644 index 000000000..6c48ae306 --- /dev/null +++ b/docker/examples/ips/ips.py @@ -0,0 +1,155 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Retrieve all IP addresses # +# GET /ips # + +params = {'subuser': 'test_string', 'ip': 'test_string', 'limit': 1, 'exclude_whitelabels': 'true', 'offset': 1} +response = sg.client.ips.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all assigned IPs # +# GET /ips/assigned # + +response = sg.client.ips.assigned.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Create an IP pool. # +# POST /ips/pools # + +data = { + "name": "marketing" +} +response = sg.client.ips.pools.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all IP pools. # +# GET /ips/pools # + +response = sg.client.ips.pools.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update an IP pools name. # +# PUT /ips/pools/{pool_name} # + +data = { + "name": "new_pool_name" +} +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) + +################################################## +# Retrieve all IPs in a specified pool. # +# GET /ips/pools/{pool_name} # + +pool_name = "test_url_param" +response = sg.client.ips.pools._(pool_name).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete an IP pool. # +# DELETE /ips/pools/{pool_name} # + +pool_name = "test_url_param" +response = sg.client.ips.pools._(pool_name).delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Add an IP address to a pool # +# POST /ips/pools/{pool_name}/ips # + +data = { + "ip": "0.0.0.0" +} +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) + +################################################## +# Remove an IP address from a pool. # +# DELETE /ips/pools/{pool_name}/ips/{ip} # + +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) + +################################################## +# Add an IP to warmup # +# POST /ips/warmup # + +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) + +################################################## +# Retrieve all IPs currently in warmup # +# GET /ips/warmup # + +response = sg.client.ips.warmup.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve warmup status for a specific IP address # +# GET /ips/warmup/{ip_address} # + +ip_address = "test_url_param" +response = sg.client.ips.warmup._(ip_address).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Remove an IP from warmup # +# DELETE /ips/warmup/{ip_address} # + +ip_address = "test_url_param" +response = sg.client.ips.warmup._(ip_address).delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all IP pools an IP address belongs to # +# GET /ips/{ip_address} # + +ip_address = "test_url_param" +response = sg.client.ips._(ip_address).get() +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/mail/mail.py b/docker/examples/mail/mail.py new file mode 100644 index 000000000..fef420e87 --- /dev/null +++ b/docker/examples/mail/mail.py @@ -0,0 +1,174 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Create a batch ID # +# POST /mail/batch # + +response = sg.client.mail.batch.post() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Validate batch ID # +# GET /mail/batch/{batch_id} # + +batch_id = "test_url_param" +response = sg.client.mail.batch._(batch_id).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# 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). + +data = { + "asm": { + "group_id": 1, + "groups_to_display": [ + 1, + 2, + 3 + ] + }, + "attachments": [ + { + "content": "[BASE64 encoded content block here]", + "content_id": "ii_139db99fdb5c3704", + "disposition": "inline", + "filename": "file1.jpg", + "name": "file1", + "type": "jpg" + } + ], + "batch_id": "[YOUR BATCH ID GOES HERE]", + "categories": [ + "category1", + "category2" + ], + "content": [ + { + "type": "text/html", + "value": "

Hello, world!

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

Thanks
The SendGrid Team

", + "text": "Thanks,/n The SendGrid Team" + }, + "sandbox_mode": { + "enable": False + }, + "spam_check": { + "enable": True, + "post_to_url": "http://example.com/compliance", + "threshold": 3 + } + }, + "personalizations": [ + { + "bcc": [ + { + "email": "sam.doe@example.com", + "name": "Sam Doe" + } + ], + "cc": [ + { + "email": "jane.doe@example.com", + "name": "Jane Doe" + } + ], + "custom_args": { + "New Argument 1": "New Value 1", + "activationAttempt": "1", + "customerAccountNumber": "[CUSTOMER ACCOUNT NUMBER GOES HERE]" + }, + "headers": { + "X-Accept-Language": "en", + "X-Mailer": "MyApp" + }, + "send_at": 1409348513, + "subject": "Hello, World!", + "substitutions": { + "id": "substitutions", + "type": "object" + }, + "to": [ + { + "email": "john.doe@example.com", + "name": "John Doe" + } + ] + } + ], + "reply_to": { + "email": "sam.smith@example.com", + "name": "Sam Smith" + }, + "sections": { + "section": { + ":sectionName1": "section 1 text", + ":sectionName2": "section 2 text" + } + }, + "send_at": 1409348513, + "subject": "Hello, World!", + "template_id": "[YOUR TEMPLATE ID GOES HERE]", + "tracking_settings": { + "click_tracking": { + "enable": True, + "enable_text": True + }, + "ganalytics": { + "enable": True, + "utm_campaign": "[NAME OF YOUR REFERRER SOURCE]", + "utm_content": "[USE THIS SPACE TO DIFFERENTIATE YOUR EMAIL FROM ADS]", + "utm_medium": "[NAME OF YOUR MARKETING MEDIUM e.g. email]", + "utm_name": "[NAME OF YOUR CAMPAIGN]", + "utm_term": "[IDENTIFY PAID KEYWORDS HERE]" + }, + "open_tracking": { + "enable": True, + "substitution_tag": "%opentrack" + }, + "subscription_tracking": { + "enable": True, + "html": "If you would like to unsubscribe and stop receiving these emails <% clickhere %>.", + "substitution_tag": "<%click here%>", + "text": "If you would like to unsubscribe and stop receiveing these emails <% click here %>." + } + } +} +response = sg.client.mail.send.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/mailboxproviders/mailboxproviders.py b/docker/examples/mailboxproviders/mailboxproviders.py new file mode 100644 index 000000000..1b75ecac5 --- /dev/null +++ b/docker/examples/mailboxproviders/mailboxproviders.py @@ -0,0 +1,17 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Retrieve email statistics by mailbox provider. # +# GET /mailbox_providers/stats # + +params = {'end_date': '2016-04-01', 'mailbox_providers': 'test_string', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01'} +response = sg.client.mailbox_providers.stats.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/mailsettings/mailsettings.py b/docker/examples/mailsettings/mailsettings.py new file mode 100644 index 000000000..18c57b960 --- /dev/null +++ b/docker/examples/mailsettings/mailsettings.py @@ -0,0 +1,220 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Retrieve all mail settings # +# GET /mail_settings # + +params = {'limit': 1, 'offset': 1} +response = sg.client.mail_settings.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update address whitelist mail settings # +# PATCH /mail_settings/address_whitelist # + +data = { + "enabled": True, + "list": [ + "email1@example.com", + "example.com" + ] +} +response = sg.client.mail_settings.address_whitelist.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve address whitelist mail settings # +# GET /mail_settings/address_whitelist # + +response = sg.client.mail_settings.address_whitelist.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update BCC mail settings # +# PATCH /mail_settings/bcc # + +data = { + "email": "email@example.com", + "enabled": False +} +response = sg.client.mail_settings.bcc.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all BCC mail settings # +# GET /mail_settings/bcc # + +response = sg.client.mail_settings.bcc.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update bounce purge mail settings # +# PATCH /mail_settings/bounce_purge # + +data = { + "enabled": True, + "hard_bounces": 5, + "soft_bounces": 5 +} +response = sg.client.mail_settings.bounce_purge.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve bounce purge mail settings # +# GET /mail_settings/bounce_purge # + +response = sg.client.mail_settings.bounce_purge.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update footer mail settings # +# PATCH /mail_settings/footer # + +data = { + "enabled": True, + "html_content": "...", + "plain_content": "..." +} +response = sg.client.mail_settings.footer.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve footer mail settings # +# GET /mail_settings/footer # + +response = sg.client.mail_settings.footer.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update forward bounce mail settings # +# PATCH /mail_settings/forward_bounce # + +data = { + "email": "example@example.com", + "enabled": True +} +response = sg.client.mail_settings.forward_bounce.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve forward bounce mail settings # +# GET /mail_settings/forward_bounce # + +response = sg.client.mail_settings.forward_bounce.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update forward spam mail settings # +# PATCH /mail_settings/forward_spam # + +data = { + "email": "", + "enabled": False +} +response = sg.client.mail_settings.forward_spam.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve forward spam mail settings # +# GET /mail_settings/forward_spam # + +response = sg.client.mail_settings.forward_spam.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update plain content mail settings # +# PATCH /mail_settings/plain_content # + +data = { + "enabled": False +} +response = sg.client.mail_settings.plain_content.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve plain content mail settings # +# GET /mail_settings/plain_content # + +response = sg.client.mail_settings.plain_content.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update spam check mail settings # +# PATCH /mail_settings/spam_check # + +data = { + "enabled": True, + "max_score": 5, + "url": "url" +} +response = sg.client.mail_settings.spam_check.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve spam check mail settings # +# GET /mail_settings/spam_check # + +response = sg.client.mail_settings.spam_check.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update template mail settings # +# PATCH /mail_settings/template # + +data = { + "enabled": True, + "html_content": "<% body %>" +} +response = sg.client.mail_settings.template.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve legacy template mail settings # +# GET /mail_settings/template # + +response = sg.client.mail_settings.template.get() +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/partnersettings/partnersettings.py b/docker/examples/partnersettings/partnersettings.py new file mode 100644 index 000000000..37f77f4e6 --- /dev/null +++ b/docker/examples/partnersettings/partnersettings.py @@ -0,0 +1,40 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Returns a list of all partner settings. # +# GET /partner_settings # + +params = {'limit': 1, 'offset': 1} +response = sg.client.partner_settings.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Updates New Relic partner settings. # +# PATCH /partner_settings/new_relic # + +data = { + "enable_subuser_statistics": True, + "enabled": True, + "license_key": "" +} +response = sg.client.partner_settings.new_relic.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Returns all New Relic partner settings. # +# GET /partner_settings/new_relic # + +response = sg.client.partner_settings.new_relic.get() +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/scopes/scopes.py b/docker/examples/scopes/scopes.py new file mode 100644 index 000000000..124f77d39 --- /dev/null +++ b/docker/examples/scopes/scopes.py @@ -0,0 +1,16 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Retrieve a list of scopes for which this user has access. # +# GET /scopes # + +response = sg.client.scopes.get() +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/senders/senders.py b/docker/examples/senders/senders.py new file mode 100644 index 000000000..f21459b71 --- /dev/null +++ b/docker/examples/senders/senders.py @@ -0,0 +1,99 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Create a Sender Identity # +# POST /senders # + +data = { + "address": "123 Elm St.", + "address_2": "Apt. 456", + "city": "Denver", + "country": "United States", + "from": { + "email": "from@example.com", + "name": "Example INC" + }, + "nickname": "My Sender ID", + "reply_to": { + "email": "replyto@example.com", + "name": "Example INC" + }, + "state": "Colorado", + "zip": "80202" +} +response = sg.client.senders.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Get all Sender Identities # +# GET /senders # + +response = sg.client.senders.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update a Sender Identity # +# PATCH /senders/{sender_id} # + +data = { + "address": "123 Elm St.", + "address_2": "Apt. 456", + "city": "Denver", + "country": "United States", + "from": { + "email": "from@example.com", + "name": "Example INC" + }, + "nickname": "My Sender ID", + "reply_to": { + "email": "replyto@example.com", + "name": "Example INC" + }, + "state": "Colorado", + "zip": "80202" +} +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) + +################################################## +# View a Sender Identity # +# GET /senders/{sender_id} # + +sender_id = "test_url_param" +response = sg.client.senders._(sender_id).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete a Sender Identity # +# DELETE /senders/{sender_id} # + +sender_id = "test_url_param" +response = sg.client.senders._(sender_id).delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Resend Sender Identity Verification # +# POST /senders/{sender_id}/resend_verification # + +sender_id = "test_url_param" +response = sg.client.senders._(sender_id).resend_verification.post() +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/stats/stats.py b/docker/examples/stats/stats.py new file mode 100644 index 000000000..a7bf3362e --- /dev/null +++ b/docker/examples/stats/stats.py @@ -0,0 +1,17 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Retrieve global email statistics # +# GET /stats # + +params = {'aggregated_by': 'day', 'limit': 1, 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} +response = sg.client.stats.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/subusers/subusers.py b/docker/examples/subusers/subusers.py new file mode 100644 index 000000000..6aa91e535 --- /dev/null +++ b/docker/examples/subusers/subusers.py @@ -0,0 +1,170 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Create Subuser # +# POST /subusers # + +data = { + "email": "John@example.com", + "ips": [ + "1.1.1.1", + "2.2.2.2" + ], + "password": "johns_password", + "username": "John@example.com" +} +response = sg.client.subusers.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# List all Subusers # +# GET /subusers # + +params = {'username': 'test_string', 'limit': 1, 'offset': 1} +response = sg.client.subusers.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve Subuser Reputations # +# GET /subusers/reputations # + +params = {'usernames': 'test_string'} +response = sg.client.subusers.reputations.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve email statistics for your subusers. # +# GET /subusers/stats # + +params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01', 'subusers': 'test_string'} +response = sg.client.subusers.stats.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve monthly stats for all subusers # +# GET /subusers/stats/monthly # + +params = {'subuser': 'test_string', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'date': 'test_string', 'sort_by_direction': 'asc'} +response = sg.client.subusers.stats.monthly.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve the totals for each email statistic metric for all subusers. # +# GET /subusers/stats/sums # + +params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} +response = sg.client.subusers.stats.sums.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Enable/disable a subuser # +# PATCH /subusers/{subuser_name} # + +data = { + "disabled": False +} +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete a subuser # +# DELETE /subusers/{subuser_name} # + +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update IPs assigned to a subuser # +# PUT /subusers/{subuser_name}/ips # + +data = [ + "127.0.0.1" +] +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).ips.put(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update Monitor Settings for a subuser # +# PUT /subusers/{subuser_name}/monitor # + +data = { + "email": "example@example.com", + "frequency": 500 +} +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.put(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Create monitor settings # +# POST /subusers/{subuser_name}/monitor # + +data = { + "email": "example@example.com", + "frequency": 50000 +} +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve monitor settings for a subuser # +# GET /subusers/{subuser_name}/monitor # + +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete monitor settings # +# DELETE /subusers/{subuser_name}/monitor # + +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve the monthly email statistics for a single subuser # +# GET /subusers/{subuser_name}/stats/monthly # + +params = {'date': 'test_string', 'sort_by_direction': 'asc', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1} +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) + diff --git a/docker/examples/suppression/suppression.py b/docker/examples/suppression/suppression.py new file mode 100644 index 000000000..abdaef76d --- /dev/null +++ b/docker/examples/suppression/suppression.py @@ -0,0 +1,202 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Retrieve all blocks # +# GET /suppression/blocks # + +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) + +################################################## +# Delete blocks # +# DELETE /suppression/blocks # + +data = { + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] +} +response = sg.client.suppression.blocks.delete(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve a specific block # +# GET /suppression/blocks/{email} # + +email = "test_url_param" +response = sg.client.suppression.blocks._(email).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete a specific block # +# DELETE /suppression/blocks/{email} # + +email = "test_url_param" +response = sg.client.suppression.blocks._(email).delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all bounces # +# GET /suppression/bounces # + +params = {'start_time': 1, 'end_time': 1} +response = sg.client.suppression.bounces.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete bounces # +# DELETE /suppression/bounces # + +data = { + "delete_all": True, + "emails": [ + "example@example.com", + "example2@example.com" + ] +} +response = sg.client.suppression.bounces.delete(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve a Bounce # +# GET /suppression/bounces/{email} # + +email = "test_url_param" +response = sg.client.suppression.bounces._(email).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete a bounce # +# DELETE /suppression/bounces/{email} # + +params = {'email_address': 'example@example.com'} +email = "test_url_param" +response = sg.client.suppression.bounces._(email).delete(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all invalid emails # +# GET /suppression/invalid_emails # + +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) + +################################################## +# Delete invalid emails # +# DELETE /suppression/invalid_emails # + +data = { + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] +} +response = sg.client.suppression.invalid_emails.delete(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve a specific invalid email # +# GET /suppression/invalid_emails/{email} # + +email = "test_url_param" +response = sg.client.suppression.invalid_emails._(email).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete a specific invalid email # +# DELETE /suppression/invalid_emails/{email} # + +email = "test_url_param" +response = sg.client.suppression.invalid_emails._(email).delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve a specific spam report # +# GET /suppression/spam_report/{email} # + +email = "test_url_param" +response = sg.client.suppression.spam_report._(email).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete a specific spam report # +# DELETE /suppression/spam_report/{email} # + +email = "test_url_param" +response = sg.client.suppression.spam_report._(email).delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all spam reports # +# GET /suppression/spam_reports # + +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) + +################################################## +# Delete spam reports # +# DELETE /suppression/spam_reports # + +data = { + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] +} +response = sg.client.suppression.spam_reports.delete(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all global suppressions # +# GET /suppression/unsubscribes # + +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) + diff --git a/docker/examples/trackingsettings/trackingsettings.py b/docker/examples/trackingsettings/trackingsettings.py new file mode 100644 index 000000000..80dbe243a --- /dev/null +++ b/docker/examples/trackingsettings/trackingsettings.py @@ -0,0 +1,111 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Retrieve Tracking Settings # +# GET /tracking_settings # + +params = {'limit': 1, 'offset': 1} +response = sg.client.tracking_settings.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update Click Tracking Settings # +# PATCH /tracking_settings/click # + +data = { + "enabled": True +} +response = sg.client.tracking_settings.click.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve Click Track Settings # +# GET /tracking_settings/click # + +response = sg.client.tracking_settings.click.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update Google Analytics Settings # +# PATCH /tracking_settings/google_analytics # + +data = { + "enabled": True, + "utm_campaign": "website", + "utm_content": "", + "utm_medium": "email", + "utm_source": "sendgrid.com", + "utm_term": "" +} +response = sg.client.tracking_settings.google_analytics.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve Google Analytics Settings # +# GET /tracking_settings/google_analytics # + +response = sg.client.tracking_settings.google_analytics.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update Open Tracking Settings # +# PATCH /tracking_settings/open # + +data = { + "enabled": True +} +response = sg.client.tracking_settings.open.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Get Open Tracking Settings # +# GET /tracking_settings/open # + +response = sg.client.tracking_settings.open.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update Subscription Tracking Settings # +# PATCH /tracking_settings/subscription # + +data = { + "enabled": True, + "html_content": "html content", + "landing": "landing page html", + "plain_content": "text content", + "replace": "replacement tag", + "url": "url" +} +response = sg.client.tracking_settings.subscription.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve Subscription Tracking Settings # +# GET /tracking_settings/subscription # + +response = sg.client.tracking_settings.subscription.get() +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/user/user.py b/docker/examples/user/user.py new file mode 100644 index 000000000..9e3f24766 --- /dev/null +++ b/docker/examples/user/user.py @@ -0,0 +1,294 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Get a user's account information. # +# GET /user/account # + +response = sg.client.user.account.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve your credit balance # +# GET /user/credits # + +response = sg.client.user.credits.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update your account email address # +# PUT /user/email # + +data = { + "email": "example@example.com" +} +response = sg.client.user.email.put(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve your account email address # +# GET /user/email # + +response = sg.client.user.email.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update your password # +# PUT /user/password # + +data = { + "new_password": "new_password", + "old_password": "old_password" +} +response = sg.client.user.password.put(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update a user's profile # +# PATCH /user/profile # + +data = { + "city": "Orange", + "first_name": "Example", + "last_name": "User" +} +response = sg.client.user.profile.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Get a user's profile # +# GET /user/profile # + +response = sg.client.user.profile.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Cancel or pause a scheduled send # +# POST /user/scheduled_sends # + +data = { + "batch_id": "YOUR_BATCH_ID", + "status": "pause" +} +response = sg.client.user.scheduled_sends.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all scheduled sends # +# GET /user/scheduled_sends # + +response = sg.client.user.scheduled_sends.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update user scheduled send information # +# PATCH /user/scheduled_sends/{batch_id} # + +data = { + "status": "pause" +} +batch_id = "test_url_param" +response = sg.client.user.scheduled_sends._(batch_id).patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve scheduled send # +# GET /user/scheduled_sends/{batch_id} # + +batch_id = "test_url_param" +response = sg.client.user.scheduled_sends._(batch_id).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete a cancellation or pause of a scheduled send # +# DELETE /user/scheduled_sends/{batch_id} # + +batch_id = "test_url_param" +response = sg.client.user.scheduled_sends._(batch_id).delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update Enforced TLS settings # +# PATCH /user/settings/enforced_tls # + +data = { + "require_tls": True, + "require_valid_cert": False +} +response = sg.client.user.settings.enforced_tls.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve current Enforced TLS settings. # +# GET /user/settings/enforced_tls # + +response = sg.client.user.settings.enforced_tls.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update your username # +# PUT /user/username # + +data = { + "username": "test_username" +} +response = sg.client.user.username.put(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve your username # +# GET /user/username # + +response = sg.client.user.username.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update Event Notification Settings # +# PATCH /user/webhooks/event/settings # + +data = { + "bounce": True, + "click": True, + "deferred": True, + "delivered": True, + "dropped": True, + "enabled": True, + "group_resubscribe": True, + "group_unsubscribe": True, + "open": True, + "processed": True, + "spam_report": True, + "unsubscribe": True, + "url": "url" +} +response = sg.client.user.webhooks.event.settings.patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve Event Webhook settings # +# GET /user/webhooks/event/settings # + +response = sg.client.user.webhooks.event.settings.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Test Event Notification Settings # +# POST /user/webhooks/event/test # + +data = { + "url": "url" +} +response = sg.client.user.webhooks.event.test.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Create a parse setting # +# POST /user/webhooks/parse/settings # + +data = { + "hostname": "myhostname.com", + "send_raw": False, + "spam_check": True, + "url": "http://email.myhosthame.com" +} +response = sg.client.user.webhooks.parse.settings.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all parse settings # +# GET /user/webhooks/parse/settings # + +response = sg.client.user.webhooks.parse.settings.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update a parse setting # +# PATCH /user/webhooks/parse/settings/{hostname} # + +data = { + "send_raw": True, + "spam_check": False, + "url": "http://newdomain.com/parse" +} +hostname = "test_url_param" +response = sg.client.user.webhooks.parse.settings._(hostname).patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve a specific parse setting # +# GET /user/webhooks/parse/settings/{hostname} # + +hostname = "test_url_param" +response = sg.client.user.webhooks.parse.settings._(hostname).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete a parse setting # +# DELETE /user/webhooks/parse/settings/{hostname} # + +hostname = "test_url_param" +response = sg.client.user.webhooks.parse.settings._(hostname).delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieves Inbound Parse Webhook statistics. # +# GET /user/webhooks/parse/stats # + +params = {'aggregated_by': 'day', 'limit': 'test_string', 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 'test_string'} +response = sg.client.user.webhooks.parse.stats.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + diff --git a/docker/examples/whitelabel/whitelabel.py b/docker/examples/whitelabel/whitelabel.py new file mode 100644 index 000000000..f529d3ed2 --- /dev/null +++ b/docker/examples/whitelabel/whitelabel.py @@ -0,0 +1,311 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Create a domain whitelabel. # +# POST /whitelabel/domains # + +data = { + "automatic_security": False, + "custom_spf": True, + "default": True, + "domain": "example.com", + "ips": [ + "192.168.1.1", + "192.168.1.2" + ], + "subdomain": "news", + "username": "john@example.com" +} +response = sg.client.whitelabel.domains.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# List all domain whitelabels. # +# GET /whitelabel/domains # + +params = {'username': 'test_string', 'domain': 'test_string', 'exclude_subusers': 'true', 'limit': 1, 'offset': 1} +response = sg.client.whitelabel.domains.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Get the default domain whitelabel. # +# GET /whitelabel/domains/default # + +response = sg.client.whitelabel.domains.default.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# List the domain whitelabel associated with the given user. # +# GET /whitelabel/domains/subuser # + +response = sg.client.whitelabel.domains.subuser.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Disassociate a domain whitelabel from a given user. # +# DELETE /whitelabel/domains/subuser # + +response = sg.client.whitelabel.domains.subuser.delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update a domain whitelabel. # +# PATCH /whitelabel/domains/{domain_id} # + +data = { + "custom_spf": True, + "default": False +} +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) + +################################################## +# Retrieve a domain whitelabel. # +# GET /whitelabel/domains/{domain_id} # + +domain_id = "test_url_param" +response = sg.client.whitelabel.domains._(domain_id).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete a domain whitelabel. # +# DELETE /whitelabel/domains/{domain_id} # + +domain_id = "test_url_param" +response = sg.client.whitelabel.domains._(domain_id).delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Associate a domain whitelabel with a given user. # +# POST /whitelabel/domains/{domain_id}/subuser # + +data = { + "username": "jane@example.com" +} +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) + +################################################## +# Add an IP to a domain whitelabel. # +# POST /whitelabel/domains/{id}/ips # + +data = { + "ip": "192.168.0.1" +} +id = "test_url_param" +response = sg.client.whitelabel.domains._(id).ips.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Remove an IP from a domain whitelabel. # +# DELETE /whitelabel/domains/{id}/ips/{ip} # + +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) + +################################################## +# Validate a domain whitelabel. # +# POST /whitelabel/domains/{id}/validate # + +id = "test_url_param" +response = sg.client.whitelabel.domains._(id).validate.post() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Create an IP whitelabel # +# POST /whitelabel/ips # + +data = { + "domain": "example.com", + "ip": "192.168.1.1", + "subdomain": "email" +} +response = sg.client.whitelabel.ips.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all IP whitelabels # +# GET /whitelabel/ips # + +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) + +################################################## +# Retrieve an IP whitelabel # +# GET /whitelabel/ips/{id} # + +id = "test_url_param" +response = sg.client.whitelabel.ips._(id).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete an IP whitelabel # +# DELETE /whitelabel/ips/{id} # + +id = "test_url_param" +response = sg.client.whitelabel.ips._(id).delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Validate an IP whitelabel # +# POST /whitelabel/ips/{id}/validate # + +id = "test_url_param" +response = sg.client.whitelabel.ips._(id).validate.post() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Create a Link Whitelabel # +# POST /whitelabel/links # + +data = { + "default": True, + "domain": "example.com", + "subdomain": "mail" +} +params = {'limit': 1, 'offset': 1} +response = sg.client.whitelabel.links.post(request_body=data, query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all link whitelabels # +# GET /whitelabel/links # + +params = {'limit': 1} +response = sg.client.whitelabel.links.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve a Default Link Whitelabel # +# GET /whitelabel/links/default # + +params = {'domain': 'test_string'} +response = sg.client.whitelabel.links.default.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve Associated Link Whitelabel # +# GET /whitelabel/links/subuser # + +params = {'username': 'test_string'} +response = sg.client.whitelabel.links.subuser.get(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Disassociate a Link Whitelabel # +# DELETE /whitelabel/links/subuser # + +params = {'username': 'test_string'} +response = sg.client.whitelabel.links.subuser.delete(query_params=params) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Update a Link Whitelabel # +# PATCH /whitelabel/links/{id} # + +data = { + "default": True +} +id = "test_url_param" +response = sg.client.whitelabel.links._(id).patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve a Link Whitelabel # +# GET /whitelabel/links/{id} # + +id = "test_url_param" +response = sg.client.whitelabel.links._(id).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete a Link Whitelabel # +# DELETE /whitelabel/links/{id} # + +id = "test_url_param" +response = sg.client.whitelabel.links._(id).delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Validate a Link Whitelabel # +# POST /whitelabel/links/{id}/validate # + +id = "test_url_param" +response = sg.client.whitelabel.links._(id).validate.post() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Associate a Link Whitelabel # +# POST /whitelabel/links/{link_id}/subuser # + +data = { + "username": "jane@example.com" +} +link_id = "test_url_param" +response = sg.client.whitelabel.links._(link_id).subuser.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + From b916a6daf669a5c4f68e5133d4a150abcc15d4b3 Mon Sep 17 00:00:00 2001 From: heisendumb Date: Mon, 30 Oct 2017 00:31:16 -0200 Subject: [PATCH 022/462] added args to Dockerfile --- docker/Dockerfile | 7 +++++-- docker/Makefile | 8 -------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 393d91bfe..a0e5f4885 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 \ @@ -30,8 +33,8 @@ RUN python2.7 get-pip.py && \ # set up default sendgrid env WORKDIR /root/sources -RUN git clone https://github.com/sendgrid/sendgrid-python.git --branch && \ - git clone https://github.com/sendgrid/python-http-client.git --branch +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 index 6ec47459d..1afc31189 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -1,11 +1,3 @@ -# import deploy config -deployfile=deploy.env -ifdef dpl -deployfile=$(dpl) -endif -include $(deployfile) -export $(shell sed 's/=.*//' $(deployfile)) - stop: docker-compose stop From af559cfbcc532048eba35468e9c494b9c05c8a1d Mon Sep 17 00:00:00 2001 From: heisendumb Date: Mon, 30 Oct 2017 00:44:00 -0200 Subject: [PATCH 023/462] edit USAGE.md --- docker/USAGE.md | 18 ++++++++++++++++++ docker/docker-compose.yml | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/docker/USAGE.md b/docker/USAGE.md index cd543c402..3c6ff700f 100644 --- a/docker/USAGE.md +++ b/docker/USAGE.md @@ -70,6 +70,24 @@ $ 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 + +## 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 +``` + +# Specifying specific versions + # Testing Testing is easy! Run the container, `cd sendgrid`, and run `tox`. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index d35772fd2..ed78e91ec 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -20,5 +20,5 @@ services: container_name: sendgrid-dev env_file: .env volumes: - - ${PATH_TO_SENDGRID-PYTHON}:/mnt/sendgrid-python - - ${PATH_TO_HTTP-CLIENT}:/mnt/python-http-client + - ${PATH_TO_SENDGRID-PYTHON_DEV}:/mnt/sendgrid-python + - ${PATH_TO_HTTP-CLIENT_DEV}:/mnt/python-http-client From 3c2724c73b62fe2c86a707531794011d619b842b Mon Sep 17 00:00:00 2001 From: heisendumb Date: Mon, 30 Oct 2017 02:11:14 -0200 Subject: [PATCH 024/462] issue #444 done --- docker/Makefile | 2 +- docker/USAGE.md | 45 ++++++++- docker/docker-compose.yml | 29 ++++-- docker/env/python-dev/sendgrid-python | 1 + docker/examples/templates/templates.py | 130 +++++++++++++++++++++++++ docker/sendgrid.env | 8 ++ 6 files changed, 202 insertions(+), 13 deletions(-) create mode 160000 docker/env/python-dev/sendgrid-python create mode 100644 docker/examples/templates/templates.py create mode 100644 docker/sendgrid.env diff --git a/docker/Makefile b/docker/Makefile index 1afc31189..76ccb73af 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -17,4 +17,4 @@ build-build: docker-compose up --build -d up: rm clean build-build - echo "Sendgrid dev environment is alive :D" + echo "Sendgrid-python environment is alive :D" diff --git a/docker/USAGE.md b/docker/USAGE.md index 3c6ff700f..d869a77ce 100644 --- a/docker/USAGE.md +++ b/docker/USAGE.md @@ -72,7 +72,16 @@ Note that the paths you specify in `-v` must be absolute. # Docker Compose -## Using tag's for versions - DockerHub + +# Quickstart + +1. Install docker-compose on your machine. +2. Must copy sendgrid.env to .env file. +3. Edit .env file for yours 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 @@ -83,10 +92,40 @@ $ sed -ie 's/TAG=latest/TAG=choice_a_version/g' ```sh-session $ cd /path/to/sendgrid-python/docker -$ docker-compose up -d +$ 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' ``` -# Specifying specific versions +### 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 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index ed78e91ec..2a435b39f 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -5,20 +5,31 @@ services: image: sendgrid/sendgrid-python:${TAG} restart: unless-stopped container_name: sendgrid-prod - volumes: - - ${PATH_TO_SENDGRID-PYTHON_PROD}:/mnt/sendgrid-python - - ${PATH_TO_HTTP-CLIENT_PROD}:/mnt/python-http-client - env_file: .env + tty: true + env_file: + - .env sendgrid-dev: build: context: . args: - - SENDGRID-PYTHON_VERSION: {SENDGRID-PYTHON_VERSION} - - HTTP-CLIENT_VERSION: {HTTP-CLIENT_VERSION} + - SENDGRID-PYTHON_VERSION=${SENDGRID_PYTHON_VERSION} + - HTTP-CLIENT_VERSION=${HTTP_CLIENT_VERSION} restart: unless-stopped container_name: sendgrid-dev - env_file: .env + tty: true + env_file: + - .env volumes: - - ${PATH_TO_SENDGRID-PYTHON_DEV}:/mnt/sendgrid-python - - ${PATH_TO_HTTP-CLIENT_DEV}:/mnt/python-http-client + - ${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/env/python-dev/sendgrid-python b/docker/env/python-dev/sendgrid-python new file mode 160000 index 000000000..28cf42f6d --- /dev/null +++ b/docker/env/python-dev/sendgrid-python @@ -0,0 +1 @@ +Subproject commit 28cf42f6d590695de7e7ecdedcb67e9d8d4729ac diff --git a/docker/examples/templates/templates.py b/docker/examples/templates/templates.py new file mode 100644 index 000000000..9d3d5dd4b --- /dev/null +++ b/docker/examples/templates/templates.py @@ -0,0 +1,130 @@ +import sendgrid +import json +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +################################################## +# Create a transactional template. # +# POST /templates # + +data = { + "name": "example_name" +} +response = sg.client.templates.post(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve all transactional templates. # +# GET /templates # + +response = sg.client.templates.get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Edit a transactional template. # +# PATCH /templates/{template_id} # + +data = { + "name": "new_example_name" +} +template_id = "test_url_param" +response = sg.client.templates._(template_id).patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve a single transactional template. # +# GET /templates/{template_id} # + +template_id = "test_url_param" +response = sg.client.templates._(template_id).get() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Delete a template. # +# DELETE /templates/{template_id} # + +template_id = "test_url_param" +response = sg.client.templates._(template_id).delete() +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Create a new transactional template version. # +# POST /templates/{template_id}/versions # + +data = { + "active": 1, + "html_content": "<%body%>", + "name": "example_version_name", + "plain_content": "<%body%>", + "subject": "<%subject%>", + "template_id": "ddb96bbc-9b92-425e-8979-99464621b543" +} +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) + +################################################## +# Edit a transactional template version. # +# PATCH /templates/{template_id}/versions/{version_id} # + +data = { + "active": 1, + "html_content": "<%body%>", + "name": "updated_example_name", + "plain_content": "<%body%>", + "subject": "<%subject%>" +} +template_id = "test_url_param" +version_id = "test_url_param" +response = sg.client.templates._(template_id).versions._(version_id).patch(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) + +################################################## +# Retrieve a specific transactional template version. # +# GET /templates/{template_id}/versions/{version_id} # + +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) + +################################################## +# Delete a transactional template version. # +# DELETE /templates/{template_id}/versions/{version_id} # + +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) + +################################################## +# Activate a transactional template version. # +# POST /templates/{template_id}/versions/{version_id}/activate # + +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) + 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 From 3c785653a92dfff5bc7c1f9be06708b1c16e6e2f Mon Sep 17 00:00:00 2001 From: thepriefy Date: Tue, 31 Oct 2017 08:24:40 +0700 Subject: [PATCH 025/462] update CONTRIBUTING.md makes Environmental Variables a sub topic --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e022ec28..bae989c49 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,7 +77,7 @@ git clone https://github.com/sendgrid/sendgrid-python.git cd sendgrid-python ``` -## Environment Variables +### Environment Variables First, get your free SendGrid account [here](https://sendgrid.com/free?source=sendgrid-python). From 7897f8361243e487f1ee33eabb6ba1b9bb46f222 Mon Sep 17 00:00:00 2001 From: Mohd Ali Rizwi Date: Tue, 31 Oct 2017 11:00:48 +0530 Subject: [PATCH 026/462] Updated USAGE.md and CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- USAGE.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e022ec28..e3fda9303 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -139,7 +139,7 @@ The above local "Initial setup" is complete * [tox](https://pypi.python.org/pypi/tox) * [prism](https://github.com/stoplightio/prism) v0.6 - It should be available in your PATH, but unittest script will try to install it locally if not found. Apart from PATH env variable it will check in `~/bin` and `/usr/local/bin`. -You can install by yourself it in user dir by calling `source test/prism.sh`. +You can install it by yourself in user dir by calling `source test/prism.sh`. #### Initial setup: #### diff --git a/USAGE.md b/USAGE.md index 1229ee1b1..09b78609a 100644 --- a/USAGE.md +++ b/USAGE.md @@ -434,7 +434,7 @@ print response.headers **This endpoint allows you to create a new suppression group.** -Suppression groups, or unsubscribe groups, are specific types or categories of email that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. +Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. The **name** and **description** of the unsubscribe group will be visible by recipients when they are managing their subscriptions. @@ -478,7 +478,7 @@ print response.headers **This endpoint allows you to update or change a suppression group.** -Suppression groups, or unsubscribe groups, are specific types or categories of email that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. +Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. The **name** and **description** of the unsubscribe group will be visible by recipients when they are managing their subscriptions. @@ -503,7 +503,7 @@ print response.headers **This endpoint allows you to retrieve a single suppression group.** -Suppression groups, or unsubscribe groups, are specific types or categories of email that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. +Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. The **name** and **description** of the unsubscribe group will be visible by recipients when they are managing their subscriptions. @@ -525,7 +525,7 @@ print response.headers You can only delete groups that have not been attached to sent mail in the last 60 days. If a recipient uses the "one-click unsubscribe" option on an email associated with a deleted group, that recipient will be added to the global suppression list. -Suppression groups, or unsubscribe groups, are specific types or categories of email that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. +Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. The **name** and **description** of the unsubscribe group will be visible by recipients when they are managing their subscriptions. From a91a46910c834399610bb74f63f03c67a33fc761 Mon Sep 17 00:00:00 2001 From: Aashishgaba097 Date: Tue, 31 Oct 2017 20:42:29 +0530 Subject: [PATCH 027/462] Corrected grammatical error in CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e022ec28..bbac665ee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -113,7 +113,7 @@ The Web API v3 client is `sendgrid.py`, the other files are legacy code for our ## Testing -All PRs require passing tests before the PR will be reviewed. +The PR must pass all the tests before it is reviewed. All test files are in the [`test`](https://github.com/sendgrid/sendgrid-python/test) directory. @@ -129,7 +129,7 @@ For Python 2.7.* and up: ### Testing Multiple Versions of Python -All PRs require passing tests before the PR will be reviewed. +The PR must pass all the tests before it is reviewed. #### Prerequisites: #### From 6ffde817c61ef7235e8743345092fd22c5f0f3a0 Mon Sep 17 00:00:00 2001 From: Abhishek J Date: Tue, 31 Oct 2017 22:13:26 +0530 Subject: [PATCH 028/462] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e022ec28..5808b9d5f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ Hello! Thank you for choosing to help contribute to one of the SendGrid open sou - [Creating a Pull Request](#creating-a-pull-request) -We use [Milestones](https://github.com/sendgrid/sendgrid-python/milestones) to help define current roadmaps, please feel free to grab an issue from the current milestone. Please indicate that you have begun work on it to avoid collisions. Once a PR is made, community review, comments, suggestions and additional PRs are welcomed and encouraged. +We use [Milestones](https://github.com/sendgrid/sendgrid-python/milestones) to help define current roadmaps, please feel free to grab an issue from the current milestone. Please indicate that you have begun work on it to avoid collisions. Once a PR is made, community reviews, comments, suggestions and additional PRs are welcomed and encouraged. ## CLAs and CCLAs From 4559f07f287efe3edbdf65f5ec905e962892d78c Mon Sep 17 00:00:00 2001 From: Steven Mirabito Date: Tue, 31 Oct 2017 17:10:17 -0400 Subject: [PATCH 029/462] Create a Use Cases Directory --- USE_CASES.md | 302 ---------------------------- use_cases/README.md | 17 ++ use_cases/asynchronous_mail_send.md | 83 ++++++++ use_cases/attachment.md | 45 +++++ use_cases/domain_whitelabel.md | 5 + use_cases/email_stats.md | 5 + use_cases/error_handling.md | 26 +++ use_cases/transational_templates.md | 116 +++++++++++ 8 files changed, 297 insertions(+), 302 deletions(-) delete mode 100644 USE_CASES.md create mode 100644 use_cases/README.md create mode 100644 use_cases/asynchronous_mail_send.md create mode 100644 use_cases/attachment.md create mode 100644 use_cases/domain_whitelabel.md create mode 100644 use_cases/email_stats.md create mode 100644 use_cases/error_handling.md create mode 100644 use_cases/transational_templates.md diff --git a/USE_CASES.md b/USE_CASES.md deleted file mode 100644 index 11dd594b0..000000000 --- a/USE_CASES.md +++ /dev/null @@ -1,302 +0,0 @@ -This documentation provides examples for specific use cases. Please [open an issue](https://github.com/sendgrid/sendgrid-python/issues) or make a pull request for any use cases you would like us to document here. Thank you! - -# Table of Contents - -* [Transactional Templates](#transactional-templates) -* [Attachment](#attachment) -* [How to Setup a Domain Whitelabel](#domain_whitelabel) -* [How to View Email Statistics](#email_stats) -* [Asynchronous Mail Send](#asynchronous-mail-send) -* [Error Handling](#error-handling) - - -# Transactional Templates - -For this example, we assume you have created a [transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). Following is the template content we used for testing. - -Template ID (replace with your own): - -```text -13b8f94f-bcae-4ec6-b752-70d6cb59f932 -``` - -Email Subject: - -```text -<%subject%> -``` - -Template Body: - -```html - - - - - -Hello -name-, -

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

-<%body%> -

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

- - -``` - -## With Mail Helper Class - -```python -import sendgrid -import os -from sendgrid.helpers.mail import Email, Content, Substitution, Mail -try: - # Python 3 - import urllib.request as urllib -except ImportError: - # Python 2 - import urllib2 as urllib - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) -from_email = Email("test@example.com") -subject = "I'm replacing the subject tag" -to_email = Email("test@example.com") -content = Content("text/html", "I'm replacing the body tag") -mail = Mail(from_email, subject, to_email, content) -mail.personalizations[0].add_substitution(Substitution("-name-", "Example User")) -mail.personalizations[0].add_substitution(Substitution("-city-", "Denver")) -mail.template_id = "13b8f94f-bcae-4ec6-b752-70d6cb59f932" -try: - response = sg.client.mail.send.post(request_body=mail.get()) -except urllib.HTTPError as e: - print (e.read()) - exit() -print(response.status_code) -print(response.body) -print(response.headers) -``` - -## Without Mail Helper Class - -```python -import sendgrid -import os -try: - # Python 3 - import urllib.request as urllib -except ImportError: - # Python 2 - import urllib2 as urllib - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) -data = { - "personalizations": [ - { - "to": [ - { - "email": "test@example.com" - } - ], - "substitutions": { - "-name-": "Example User", - "-city-": "Denver" - }, - "subject": "I'm replacing the subject tag" - }, - ], - "from": { - "email": "test@example.com" - }, - "content": [ - { - "type": "text/html", - "value": "I'm replacing the body tag" - } - ], - "template_id": "13b8f94f-bcae-4ec6-b752-70d6cb59f932" -} -try: - response = sg.client.mail.send.post(request_body=data) -except urllib.HTTPError as e: - print (e.read()) - exit() -print(response.status_code) -print(response.body) -print(response.headers) -``` - - -# Attachment - -```python -import base64 -import sendgrid -import os -from sendgrid.helpers.mail import Email, Content, Mail, Attachment -try: - # Python 3 - import urllib.request as urllib -except ImportError: - # Python 2 - import urllib2 as urllib - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) -from_email = Email("test@example.com") -subject = "subject" -to_email = Email("to_email@example.com") -content = Content("text/html", "I'm a content example") - -file_path = "file_path.pdf" -with open(file_path,'rb') as f: - data = f.read() - f.close() -encoded = base64.b64encode(data).decode() - -attachment = Attachment() -attachment.content = encoded -attachment.type = "application/pdf" -attachment.filename = "test.pdf" -attachment.disposition = "attachment" -attachment.content_id = "Example Content ID" - -mail = Mail(from_email, subject, to_email, content) -mail.add_attachment(attachment) -try: - response = sg.client.mail.send.post(request_body=mail.get()) -except urllib.HTTPError as e: - print(e.read()) - exit() - -print(response.status_code) -print(response.body) -print(response.headers) -``` - - -# How to Setup a Domain Whitelabel - -You can find documentation for how to setup a domain whitelabel via the UI [here](https://sendgrid.com/docs/Classroom/Basics/Whitelabel/setup_domain_whitelabel.html) and via API [here](https://github.com/sendgrid/sendgrid-python/blob/master/USAGE.md#whitelabel). - -Find more information about all of SendGrid's whitelabeling related documentation [here](https://sendgrid.com/docs/Classroom/Basics/Whitelabel/index.html). - - -# How to View Email Statistics - -You can find documentation for how to view your email statistics via the UI [here](https://app.sendgrid.com/statistics) and via API [here](https://github.com/sendgrid/sendgrid-python/blob/master/USAGE.md#stats). - -Alternatively, we can post events to a URL of your choice via our [Event Webhook](https://sendgrid.com/docs/API_Reference/Webhooks/event.html) about events that occur as SendGrid processes your email. - - -# Asynchronous Mail Send - -## Using `asyncio` (3.5+) - -The built-in `asyncio` library can be used to send email in a non-blocking manner. `asyncio` helps us execute mail sending in a separate context, allowing us to continue execution of business logic without waiting for all our emails to send first. - -```python -import sendgrid -from sendgrid.helpers.mail import * -import os -import asyncio - - -sg = sendgrid.SendGridAPIClient( - apikey=os.getenv("SENDGRID_API_KEY") -) - -from_email = Email("test@example.com") -to_email = Email("test1@example.com") - -content = Content("text/plain", "This is asynchronous sending test.") - -# instantiate `sendgrid.helpers.mail.Mail` objects -em1 = Mail(from_email, "Message #1", to_email, content) -em2 = Mail(from_email, "Message #2", to_email, content) -em3 = Mail(from_email, "Message #3", to_email, content) -em4 = Mail(from_email, "Message #4", to_email, content) -em5 = Mail(from_email, "Message #5", to_email, content) -em6 = Mail(from_email, "Message #6", to_email, content) -em7 = Mail(from_email, "Message #7", to_email, content) -em8 = Mail(from_email, "Message #8", to_email, content) -em9 = Mail(from_email, "Message #9", to_email, content) -em10 = Mail(from_email, "Message #10", to_email, content) - - -ems = [em1, em2, em3, em4, em5, em6, em7, em8, em9, em10] - - -async def send_email(n, email): - ''' - send_mail wraps SendGrid's API client, and makes a POST request to - the api/v3/mail/send endpoint with `email`. - Args: - email: single mail object. - ''' - try: - response = sg.client.mail.send.post(request_body=email.get()) - if response.status_code < 300: - print("Email #{} processed".format(n), response.body, response.status_code) - except urllib.error.HTTPError as e: - e.read() - - -@asyncio.coroutine -def send_many(emails, cb): - ''' - send_many creates a number of non-blocking tasks (to send email) - that will run on the existing event loop. Due to non-blocking nature, - you can include a callback that will run after all tasks have been queued. - - Args: - emails: contains any # of `sendgrid.helpers.mail.Mail`. - cb: a function that will execute immediately. - ''' - print("START - sending emails ...") - for n, em in enumerate(emails): - asyncio.async(send_email(n, em)) - print("END - returning control...") - cb() - - -def sample_cb(): - print("Executing callback now...") - for i in range(0, 100): - print(i) - return - - -if __name__ == "__main__": - loop = asyncio.get_event_loop() - task = asyncio.async(send_many(ems, sample_cb)) - loop.run_until_complete(task) -``` - - -# Error Handling -[Custom exceptions](https://github.com/sendgrid/python-http-client/blob/master/python_http_client/exceptions.py) for `python_http_client` are now supported, which can be imported by consuming libraries. - -Please see [here](https://github.com/sendgrid/python-http-client/blob/master/python_http_client/exceptions.py) for a list of supported exceptions. - -```python - import sendgrid - import os - from sendgrid.helpers.mail import * - from python_http_client import exceptions - - sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - from_email = Email("dx@sendgrid.com") - to_email = Email("elmer.thomas@sendgrid.com") - subject = "Sending with SendGrid is Fun" - content = Content("text/plain", "and easy to do anywhere, even with Python") - mail = Mail(from_email, subject, to_email, content) - try: - response = sg.client.mail.send.post(request_body=mail.get()) - except exceptions.BadRequestsError as e: - print(e.body) - exit() - print(response.status_code) - print(response.body) - print(response.headers) -``` diff --git a/use_cases/README.md b/use_cases/README.md new file mode 100644 index 000000000..ca898d648 --- /dev/null +++ b/use_cases/README.md @@ -0,0 +1,17 @@ +# Use Cases + +This directory provides examples for specific use cases of this library. Please [open an issue](https://github.com/sendgrid/sendgrid-python/issues) or make a pull request for any use cases you would like us to document here. Thank you! + +## Table of Contents + +### How-Tos +* [How to Setup a Domain Whitelabel](domain_whitelabel.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 new file mode 100644 index 000000000..57dd61b2a --- /dev/null +++ b/use_cases/asynchronous_mail_send.md @@ -0,0 +1,83 @@ +# Asynchronous Mail Send + +## Using `asyncio` (3.5+) + +The built-in `asyncio` library can be used to send email in a non-blocking manner. `asyncio` helps us execute mail sending in a separate context, allowing us to continue execution of business logic without waiting for all our emails to send first. + +```python +import sendgrid +from sendgrid.helpers.mail import * +import os +import asyncio + + +sg = sendgrid.SendGridAPIClient( + apikey=os.getenv("SENDGRID_API_KEY") +) + +from_email = Email("test@example.com") +to_email = Email("test1@example.com") + +content = Content("text/plain", "This is asynchronous sending test.") + +# instantiate `sendgrid.helpers.mail.Mail` objects +em1 = Mail(from_email, "Message #1", to_email, content) +em2 = Mail(from_email, "Message #2", to_email, content) +em3 = Mail(from_email, "Message #3", to_email, content) +em4 = Mail(from_email, "Message #4", to_email, content) +em5 = Mail(from_email, "Message #5", to_email, content) +em6 = Mail(from_email, "Message #6", to_email, content) +em7 = Mail(from_email, "Message #7", to_email, content) +em8 = Mail(from_email, "Message #8", to_email, content) +em9 = Mail(from_email, "Message #9", to_email, content) +em10 = Mail(from_email, "Message #10", to_email, content) + + +ems = [em1, em2, em3, em4, em5, em6, em7, em8, em9, em10] + + +async def send_email(n, email): + ''' + send_mail wraps SendGrid's API client, and makes a POST request to + the api/v3/mail/send endpoint with `email`. + Args: + email: single mail object. + ''' + try: + response = sg.client.mail.send.post(request_body=email.get()) + if response.status_code < 300: + print("Email #{} processed".format(n), response.body, response.status_code) + except urllib.error.HTTPError as e: + e.read() + + +@asyncio.coroutine +def send_many(emails, cb): + ''' + send_many creates a number of non-blocking tasks (to send email) + that will run on the existing event loop. Due to non-blocking nature, + you can include a callback that will run after all tasks have been queued. + + Args: + emails: contains any # of `sendgrid.helpers.mail.Mail`. + cb: a function that will execute immediately. + ''' + print("START - sending emails ...") + for n, em in enumerate(emails): + asyncio.async(send_email(n, em)) + print("END - returning control...") + cb() + + +def sample_cb(): + print("Executing callback now...") + for i in range(0, 100): + print(i) + return + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + task = asyncio.async(send_many(ems, sample_cb)) + loop.run_until_complete(task) +``` \ No newline at end of file diff --git a/use_cases/attachment.md b/use_cases/attachment.md new file mode 100644 index 000000000..86614a009 --- /dev/null +++ b/use_cases/attachment.md @@ -0,0 +1,45 @@ +# Attachment + +```python +import base64 +import sendgrid +import os +from sendgrid.helpers.mail import Email, Content, Mail, Attachment +try: + # Python 3 + import urllib.request as urllib +except ImportError: + # Python 2 + import urllib2 as urllib + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +from_email = Email("test@example.com") +subject = "subject" +to_email = Email("to_email@example.com") +content = Content("text/html", "I'm a content example") + +file_path = "file_path.pdf" +with open(file_path,'rb') as f: + data = f.read() + f.close() +encoded = base64.b64encode(data).decode() + +attachment = Attachment() +attachment.content = encoded +attachment.type = "application/pdf" +attachment.filename = "test.pdf" +attachment.disposition = "attachment" +attachment.content_id = "Example Content ID" + +mail = Mail(from_email, subject, to_email, content) +mail.add_attachment(attachment) +try: + response = sg.client.mail.send.post(request_body=mail.get()) +except urllib.HTTPError as e: + print(e.read()) + exit() + +print(response.status_code) +print(response.body) +print(response.headers) +``` \ No newline at end of file diff --git a/use_cases/domain_whitelabel.md b/use_cases/domain_whitelabel.md new file mode 100644 index 000000000..c28ad055d --- /dev/null +++ b/use_cases/domain_whitelabel.md @@ -0,0 +1,5 @@ +# 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/email_stats.md b/use_cases/email_stats.md new file mode 100644 index 000000000..7968a8a9e --- /dev/null +++ b/use_cases/email_stats.md @@ -0,0 +1,5 @@ +# How to View Email Statistics + +You can find documentation for how to view your email statistics via the UI [here](https://app.sendgrid.com/statistics) and via API [here](https://github.com/sendgrid/sendgrid-python/blob/master/USAGE.md#stats). + +Alternatively, we can post events to a URL of your choice via our [Event Webhook](https://sendgrid.com/docs/API_Reference/Webhooks/event.html) about events that occur as SendGrid processes your email. \ No newline at end of file diff --git a/use_cases/error_handling.md b/use_cases/error_handling.md new file mode 100644 index 000000000..1d667d1cd --- /dev/null +++ b/use_cases/error_handling.md @@ -0,0 +1,26 @@ +# Error Handling +[Custom exceptions](https://github.com/sendgrid/python-http-client/blob/master/python_http_client/exceptions.py) for `python_http_client` are now supported, which can be imported by consuming libraries. + +Please see [here](https://github.com/sendgrid/python-http-client/blob/master/python_http_client/exceptions.py) for a list of supported exceptions. + +```python + import sendgrid + import os + from sendgrid.helpers.mail import * + from python_http_client import exceptions + + sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + from_email = Email("dx@sendgrid.com") + to_email = Email("elmer.thomas@sendgrid.com") + subject = "Sending with SendGrid is Fun" + content = Content("text/plain", "and easy to do anywhere, even with Python") + mail = Mail(from_email, subject, to_email, content) + try: + response = sg.client.mail.send.post(request_body=mail.get()) + except exceptions.BadRequestsError as e: + print(e.body) + exit() + print(response.status_code) + print(response.body) + print(response.headers) +``` \ No newline at end of file diff --git a/use_cases/transational_templates.md b/use_cases/transational_templates.md new file mode 100644 index 000000000..d3e3a005d --- /dev/null +++ b/use_cases/transational_templates.md @@ -0,0 +1,116 @@ +# Transactional Templates + +For this example, we assume you have created a [transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). Following is the template content we used for testing. + +Template ID (replace with your own): + +```text +13b8f94f-bcae-4ec6-b752-70d6cb59f932 +``` + +Email Subject: + +```text +<%subject%> +``` + +Template Body: + +```html + + + + + +Hello -name-, +

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

+<%body%> +

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

+ + +``` + +## With Mail Helper Class + +```python +import sendgrid +import os +from sendgrid.helpers.mail import Email, Content, Substitution, Mail +try: + # Python 3 + import urllib.request as urllib +except ImportError: + # Python 2 + import urllib2 as urllib + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +from_email = Email("test@example.com") +subject = "I'm replacing the subject tag" +to_email = Email("test@example.com") +content = Content("text/html", "I'm replacing the body tag") +mail = Mail(from_email, subject, to_email, content) +mail.personalizations[0].add_substitution(Substitution("-name-", "Example User")) +mail.personalizations[0].add_substitution(Substitution("-city-", "Denver")) +mail.template_id = "13b8f94f-bcae-4ec6-b752-70d6cb59f932" +try: + response = sg.client.mail.send.post(request_body=mail.get()) +except urllib.HTTPError as e: + print (e.read()) + exit() +print(response.status_code) +print(response.body) +print(response.headers) +``` + +## Without Mail Helper Class + +```python +import sendgrid +import os +try: + # Python 3 + import urllib.request as urllib +except ImportError: + # Python 2 + import urllib2 as urllib + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +data = { + "personalizations": [ + { + "to": [ + { + "email": "test@example.com" + } + ], + "substitutions": { + "-name-": "Example User", + "-city-": "Denver" + }, + "subject": "I'm replacing the subject tag" + }, + ], + "from": { + "email": "test@example.com" + }, + "content": [ + { + "type": "text/html", + "value": "I'm replacing the body tag" + } + ], + "template_id": "13b8f94f-bcae-4ec6-b752-70d6cb59f932" +} +try: + response = sg.client.mail.send.post(request_body=data) +except urllib.HTTPError as e: + print (e.read()) + exit() +print(response.status_code) +print(response.body) +print(response.headers) +``` \ No newline at end of file From 89ee9a6b754b69eeef525024a68b80958aeef0a2 Mon Sep 17 00:00:00 2001 From: Delirious Lettuce Date: Tue, 31 Oct 2017 18:23:22 -0600 Subject: [PATCH 030/462] Rename docstring parameter to match function argument --- sendgrid/helpers/mail/mail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 5243dcd59..95394a7e4 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -329,7 +329,7 @@ def sections(self): def add_section(self, section): """Add a Section to this Mail. - :type attachment: Section + :type section: Section """ if self._sections is None: self._sections = [] From c206553050438b1777da4f66d72407bcd704de20 Mon Sep 17 00:00:00 2001 From: Delirious Lettuce Date: Tue, 31 Oct 2017 18:45:13 -0600 Subject: [PATCH 031/462] Append a trailing underscore to fix shadowed builtin (id) --- examples/whitelabel/whitelabel.py | 40 +++++++++++++++---------------- test/test_sendgrid.py | 40 +++++++++++++++---------------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/examples/whitelabel/whitelabel.py b/examples/whitelabel/whitelabel.py index f529d3ed2..8515d45e3 100644 --- a/examples/whitelabel/whitelabel.py +++ b/examples/whitelabel/whitelabel.py @@ -117,8 +117,8 @@ data = { "ip": "192.168.0.1" } -id = "test_url_param" -response = sg.client.whitelabel.domains._(id).ips.post(request_body=data) +id_ = "test_url_param" +response = sg.client.whitelabel.domains._(id_).ips.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) @@ -127,9 +127,9 @@ # Remove an IP from a domain whitelabel. # # DELETE /whitelabel/domains/{id}/ips/{ip} # -id = "test_url_param" +id_ = "test_url_param" ip = "test_url_param" -response = sg.client.whitelabel.domains._(id).ips._(ip).delete() +response = sg.client.whitelabel.domains._(id_).ips._(ip).delete() print(response.status_code) print(response.body) print(response.headers) @@ -138,8 +138,8 @@ # Validate a domain whitelabel. # # POST /whitelabel/domains/{id}/validate # -id = "test_url_param" -response = sg.client.whitelabel.domains._(id).validate.post() +id_ = "test_url_param" +response = sg.client.whitelabel.domains._(id_).validate.post() print(response.status_code) print(response.body) print(response.headers) @@ -172,8 +172,8 @@ # Retrieve an IP whitelabel # # GET /whitelabel/ips/{id} # -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).get() +id_ = "test_url_param" +response = sg.client.whitelabel.ips._(id_).get() print(response.status_code) print(response.body) print(response.headers) @@ -182,8 +182,8 @@ # Delete an IP whitelabel # # DELETE /whitelabel/ips/{id} # -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).delete() +id_ = "test_url_param" +response = sg.client.whitelabel.ips._(id_).delete() print(response.status_code) print(response.body) print(response.headers) @@ -192,8 +192,8 @@ # Validate an IP whitelabel # # POST /whitelabel/ips/{id}/validate # -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).validate.post() +id_ = "test_url_param" +response = sg.client.whitelabel.ips._(id_).validate.post() print(response.status_code) print(response.body) print(response.headers) @@ -260,8 +260,8 @@ data = { "default": True } -id = "test_url_param" -response = sg.client.whitelabel.links._(id).patch(request_body=data) +id_ = "test_url_param" +response = sg.client.whitelabel.links._(id_).patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) @@ -270,8 +270,8 @@ # Retrieve a Link Whitelabel # # GET /whitelabel/links/{id} # -id = "test_url_param" -response = sg.client.whitelabel.links._(id).get() +id_ = "test_url_param" +response = sg.client.whitelabel.links._(id_).get() print(response.status_code) print(response.body) print(response.headers) @@ -280,8 +280,8 @@ # Delete a Link Whitelabel # # DELETE /whitelabel/links/{id} # -id = "test_url_param" -response = sg.client.whitelabel.links._(id).delete() +id_ = "test_url_param" +response = sg.client.whitelabel.links._(id_).delete() print(response.status_code) print(response.body) print(response.headers) @@ -290,8 +290,8 @@ # Validate a Link Whitelabel # # POST /whitelabel/links/{id}/validate # -id = "test_url_param" -response = sg.client.whitelabel.links._(id).validate.post() +id_ = "test_url_param" +response = sg.client.whitelabel.links._(id_).validate.post() print(response.status_code) print(response.body) print(response.headers) diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index c73c44bbf..6d706421f 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -2227,25 +2227,25 @@ def test_whitelabel_domains__id__ips_post(self): data = { "ip": "192.168.0.1" } - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 200} response = self.sg.client.whitelabel.domains._( - id).ips.post(request_body=data, request_headers=headers) + id_).ips.post(request_body=data, request_headers=headers) self.assertEqual(response.status_code, 200) def test_whitelabel_domains__id__ips__ip__delete(self): - id = "test_url_param" + id_ = "test_url_param" ip = "test_url_param" headers = {'X-Mock': 200} response = self.sg.client.whitelabel.domains._( - id).ips._(ip).delete(request_headers=headers) + id_).ips._(ip).delete(request_headers=headers) self.assertEqual(response.status_code, 200) def test_whitelabel_domains__id__validate_post(self): - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 200} response = self.sg.client.whitelabel.domains._( - id).validate.post(request_headers=headers) + id_).validate.post(request_headers=headers) self.assertEqual(response.status_code, 200) def test_whitelabel_ips_post(self): @@ -2267,24 +2267,24 @@ def test_whitelabel_ips_get(self): self.assertEqual(response.status_code, 200) def test_whitelabel_ips__id__get(self): - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 200} response = self.sg.client.whitelabel.ips._( - id).get(request_headers=headers) + id_).get(request_headers=headers) self.assertEqual(response.status_code, 200) def test_whitelabel_ips__id__delete(self): - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 204} response = self.sg.client.whitelabel.ips._( - id).delete(request_headers=headers) + id_).delete(request_headers=headers) self.assertEqual(response.status_code, 204) def test_whitelabel_ips__id__validate_post(self): - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 200} response = self.sg.client.whitelabel.ips._( - id).validate.post(request_headers=headers) + id_).validate.post(request_headers=headers) self.assertEqual(response.status_code, 200) def test_whitelabel_links_post(self): @@ -2331,31 +2331,31 @@ def test_whitelabel_links__id__patch(self): data = { "default": True } - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 200} - response = self.sg.client.whitelabel.links._(id).patch( + response = self.sg.client.whitelabel.links._(id_).patch( request_body=data, request_headers=headers) self.assertEqual(response.status_code, 200) def test_whitelabel_links__id__get(self): - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 200} response = self.sg.client.whitelabel.links._( - id).get(request_headers=headers) + id_).get(request_headers=headers) self.assertEqual(response.status_code, 200) def test_whitelabel_links__id__delete(self): - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 204} response = self.sg.client.whitelabel.links._( - id).delete(request_headers=headers) + id_).delete(request_headers=headers) self.assertEqual(response.status_code, 204) def test_whitelabel_links__id__validate_post(self): - id = "test_url_param" + id_ = "test_url_param" headers = {'X-Mock': 200} response = self.sg.client.whitelabel.links._( - id).validate.post(request_headers=headers) + id_).validate.post(request_headers=headers) self.assertEqual(response.status_code, 200) def test_whitelabel_links__link_id__subuser_post(self): From 77e079d60d80e7b0dde4a6d508ddacb1bf5c1c38 Mon Sep 17 00:00:00 2001 From: Ryan D'souza Date: Wed, 1 Nov 2017 02:39:16 +0100 Subject: [PATCH 032/462] Added multiple Exceptions including base class --- sendgrid/helpers/mail/exceptions.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 sendgrid/helpers/mail/exceptions.py diff --git a/sendgrid/helpers/mail/exceptions.py b/sendgrid/helpers/mail/exceptions.py new file mode 100644 index 000000000..ab4dd9c0c --- /dev/null +++ b/sendgrid/helpers/mail/exceptions.py @@ -0,0 +1,22 @@ +################################################################ +# 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 + """ + + def __init__(self, + expression="Email body", + message="SendGrid API Key detected"): + self.expression = expression + self.message = message + From 0a5ab7d1e1a586e128741cd2d4d97a8104215c24 Mon Sep 17 00:00:00 2001 From: Ryan D'souza Date: Wed, 1 Nov 2017 02:40:13 +0100 Subject: [PATCH 033/462] Added a Validator to check against API key being included --- sendgrid/helpers/mail/validators.py | 68 +++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 sendgrid/helpers/mail/validators.py diff --git a/sendgrid/helpers/mail/validators.py b/sendgrid/helpers/mail/validators.py new file mode 100644 index 000000000..061727c39 --- /dev/null +++ b/sendgrid/helpers/mail/validators.py @@ -0,0 +1,68 @@ +################################################################ +# Various types of Validators +################################################################ + +class ValidateAPIKey(object): + """Validates content to ensure SendGrid API key is not present""" + + regexes = None + + def __init__(self, regex_strings=list(), use_default=True): + """Constructor + Args: + regex_strings (list): list of regex strings + use_default (bool): Whether or not to include default regex + """ + + import re + self.regexes = set() + + #Compile the regex strings into patterns, add them to our set + for regex_string in regex_strings: + self.regexes.add(re.compile(regex_string)) + + if use_default: + default_regex_string = '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 + """ + + #Handle string in edge-case + if isinstance(request_body, str): + self.validate_message_text(request_body) + + #Default param + elif isinstance(request_body, dict): + if "content" in request_body: + contents = request_body["content"] + + for content in contents: + if "value" in content and "type" in content: + if content["type"] == "text/html" or isinstance(content["value"], str): + message_text = content["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 + """ + + if isinstance(message_string, str): + for regex in self.regexes: + if regex_pattern.match(message_string) is not None: + raise APIKeyIncludedException() + From 61f4370edf9136a7a625288f53c2b595df2c7094 Mon Sep 17 00:00:00 2001 From: Ryan D'souza Date: Wed, 1 Nov 2017 02:40:48 +0100 Subject: [PATCH 034/462] Added Validators and SendGrid-related Exceptions --- sendgrid/helpers/mail/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sendgrid/helpers/mail/__init__.py b/sendgrid/helpers/mail/__init__.py index b9e908560..48b329086 100644 --- a/sendgrid/helpers/mail/__init__.py +++ b/sendgrid/helpers/mail/__init__.py @@ -7,6 +7,8 @@ from .content import Content from .custom_arg import CustomArg from .email import Email +from .exceptions import SendGridException +from .exceptions import APIKeyIncludedException from .footer_settings import FooterSettings from .ganalytics import Ganalytics from .header import Header @@ -20,3 +22,4 @@ from .subscription_tracking import SubscriptionTracking from .substitution import Substitution from .tracking_settings import TrackingSettings +from .validators import ValidateAPIKey From c30e82921e669e7d073d0e46d9e9bb7f49d3d3ef Mon Sep 17 00:00:00 2001 From: Ryan D'souza Date: Wed, 1 Nov 2017 02:43:38 +0100 Subject: [PATCH 035/462] Added validations for SendGrid API key --- sendgrid/helpers/mail/content.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sendgrid/helpers/mail/content.py b/sendgrid/helpers/mail/content.py index f6cefa7ea..2a6094ce2 100644 --- a/sendgrid/helpers/mail/content.py +++ b/sendgrid/helpers/mail/content.py @@ -14,6 +14,7 @@ def __init__(self, type_=None, value=None): """ self._type = None self._value = None + self._validator = ValidateAPIKey() if type_ is not None: self.type = type_ @@ -45,6 +46,7 @@ def value(self): @value.setter def value(self, value): + self._validator.validate_message_dict(value) self._value = value def get(self): From 4fa0d2f5518a3ba463d0cc50b6b0d646980e53f9 Mon Sep 17 00:00:00 2001 From: Ryan D'souza Date: Wed, 1 Nov 2017 02:43:41 +0100 Subject: [PATCH 036/462] Added validations for SendGrid API key --- sendgrid/helpers/mail/header.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sendgrid/helpers/mail/header.py b/sendgrid/helpers/mail/header.py index 7c031465d..27f98563d 100644 --- a/sendgrid/helpers/mail/header.py +++ b/sendgrid/helpers/mail/header.py @@ -17,6 +17,7 @@ def __init__(self, key=None, value=None): """ self._key = None self._value = None + self._validator.validate_message_dict(value) if key is not None: self.key = key @@ -45,6 +46,7 @@ def value(self): @value.setter def value(self, value): + self._validator.validate_message_dict(value) self._value = value def get(self): From e0606c60bd49eb3f443be26230166355efa5383e Mon Sep 17 00:00:00 2001 From: Ryan D'souza Date: Wed, 1 Nov 2017 02:43:50 +0100 Subject: [PATCH 037/462] Added validations for SendGrid API key --- sendgrid/helpers/mail/mail.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 5243dcd59..dd94182af 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -8,8 +8,11 @@ class Mail(object): Use get() to get the request body. """ - def __init__( - self, from_email=None, subject=None, to_email=None, content=None): + def __init__(self, + from_email=None, + subject=None, + to_email=None, + content=None): """Create a Mail object. If parameters are supplied, all parameters must be present. @@ -39,6 +42,7 @@ def __init__( self._headers = None self._categories = None self._custom_args = None + self._validator = ValidateAPIKey() # Minimum required to send an email if from_email and subject and to_email and content: @@ -149,6 +153,7 @@ def subject(self): @subject.setter def subject(self, value): + self._validator.validate_message_text(value) self._subject = value @property From 3c68438f39e815b63ec92919e5baf25eaea39936 Mon Sep 17 00:00:00 2001 From: Ryan D'souza Date: Wed, 1 Nov 2017 02:46:31 +0100 Subject: [PATCH 038/462] Added test cases for SendGrid API key --- test/test_mail.py | 49 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/test/test_mail.py b/test/test_mail.py index 8b88f5b14..4543456a8 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -3,6 +3,7 @@ from sendgrid.helpers.mail import ( ASM, + APIKeyIncludedException, Attachment, BCCSettings, BypassListManagement, @@ -20,10 +21,12 @@ Personalization, SandBoxMode, Section, + SendGridException, SpamCheck, SubscriptionTracking, Substitution, - TrackingSettings + TrackingSettings, + ValidateAPIKey ) try: @@ -34,6 +37,50 @@ class UnitTests(unittest.TestCase): + def test_sendgridAPIKey(self): + """Tests if including SendGrid API will throw an Exception""" + + #Minimum required to send an email + self.maxDiff = 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.maxDiff = None From fd8075623ffb3caee25abc1b4f6e58f095400e46 Mon Sep 17 00:00:00 2001 From: Prashu Chaudhary Date: Wed, 1 Nov 2017 14:27:27 +0530 Subject: [PATCH 039/462] Add unittesting support to .codeclimate.yml --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4aceaa352..ad91ea885 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,9 @@ python: - '3.4' - '3.5' - '3.6' +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 @@ -23,10 +26,14 @@ addons: before_script: - . ./test/prism.sh - prism version +- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter +- 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 after_script: - codecov +- ./cc-test-reporter after-build --exit-code $? before_deploy: - python ./register.py deploy: From d4ad82d3e3715d3d1e59ae92ffe52d5e5ec5906a Mon Sep 17 00:00:00 2001 From: Ruslan Shestopalyuk Date: Mon, 23 Oct 2017 08:49:10 +0200 Subject: [PATCH 040/462] Provide utf-8 as encoding explicitly when opening text files --- register.py | 7 ++++--- sendgrid/helpers/inbound/send.py | 3 ++- setup.py | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/register.py b/register.py index f7c0b9be0..4eb612387 100644 --- a/register.py +++ b/register.py @@ -1,10 +1,11 @@ import pypandoc +import io output = pypandoc.convert('README.md', 'rst') with open('README.txt' 'w+') as f: f.write(str(output.encode('utf-8'))) -readme_rst = open('./README.txt').read() +readme_rst = io.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 @@ -15,5 +16,5 @@ \n :target: https://www.sendgrid.com ''' final_text = readme_rst.replace(replace, replacement) -with open('./README.txt', 'w') as f: - f.write(final_text) +with io.open('./README.txt', 'w', encoding='utf-8') as f: + f.write(final_text) diff --git a/sendgrid/helpers/inbound/send.py b/sendgrid/helpers/inbound/send.py index d786f678a..a7bbe01b8 100644 --- a/sendgrid/helpers/inbound/send.py +++ b/sendgrid/helpers/inbound/send.py @@ -2,6 +2,7 @@ Usage: ./send.py [path to file containing test data]""" import argparse import sys +import io try: from config import Config except ImportError: @@ -26,7 +27,7 @@ def test_payload(self, payload_filepath): "Content-Type": "multipart/form-data; boundary=xYzZY" } client = Client(host=self.url, request_headers=headers) - f = open(payload_filepath, 'r') + f = io.open(payload_filepath, 'r', encoding='utf-8') data = f.read() return client.post(request_body=data) diff --git a/setup.py b/setup.py index f95e4ee40..8826acb0c 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ import sys import os +import io from setuptools import setup, find_packages __version__ = None @@ -8,7 +9,7 @@ long_description = 'Please see our GitHub README' if os.path.exists('README.txt'): - long_description = open('README.txt').read() + long_description = io.open('README.txt', 'r', encoding='utf-8').read() def getRequires(): From 7dcccec86ba6551ac163cb4d33a407f03b477dee Mon Sep 17 00:00:00 2001 From: Ruslan Shestopalyuk Date: Tue, 31 Oct 2017 19:26:49 +0100 Subject: [PATCH 041/462] Fix ASM unit test (also use setter in constructor) --- sendgrid/helpers/mail/asm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sendgrid/helpers/mail/asm.py b/sendgrid/helpers/mail/asm.py index 59e49eee5..333f3e98c 100644 --- a/sendgrid/helpers/mail/asm.py +++ b/sendgrid/helpers/mail/asm.py @@ -13,10 +13,10 @@ def __init__(self, group_id=None, groups_to_display=None): self._groups_to_display = None if group_id is not None: - self._group_id = group_id + self.group_id = group_id if groups_to_display is not None: - self._groups_to_display = groups_to_display + self.groups_to_display = groups_to_display @property def group_id(self): From d6d83b5e1cf9be3a9128f33b6ea2bd171daff4b3 Mon Sep 17 00:00:00 2001 From: Ruslan Shestopalyuk Date: Wed, 1 Nov 2017 09:52:36 +0100 Subject: [PATCH 042/462] Change io.open to open, since regular open in Python 3 already supports encoding --- register.py | 6 +++--- sendgrid/helpers/inbound/send.py | 4 ++-- setup.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/register.py b/register.py index 4eb612387..b71341a34 100644 --- a/register.py +++ b/register.py @@ -1,11 +1,11 @@ import pypandoc -import io +from io import open output = pypandoc.convert('README.md', 'rst') with open('README.txt' 'w+') as f: f.write(str(output.encode('utf-8'))) -readme_rst = io.open('./README.txt', 'r', encoding='utf-8').read() +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 @@ -16,5 +16,5 @@ \n :target: https://www.sendgrid.com ''' final_text = readme_rst.replace(replace, replacement) -with io.open('./README.txt', 'w', encoding='utf-8') as f: +with open('./README.txt', 'w', encoding='utf-8') as f: f.write(final_text) diff --git a/sendgrid/helpers/inbound/send.py b/sendgrid/helpers/inbound/send.py index a7bbe01b8..053764cbb 100644 --- a/sendgrid/helpers/inbound/send.py +++ b/sendgrid/helpers/inbound/send.py @@ -2,7 +2,7 @@ Usage: ./send.py [path to file containing test data]""" import argparse import sys -import io +from io import open try: from config import Config except ImportError: @@ -27,7 +27,7 @@ def test_payload(self, payload_filepath): "Content-Type": "multipart/form-data; boundary=xYzZY" } client = Client(host=self.url, request_headers=headers) - f = io.open(payload_filepath, 'r', encoding='utf-8') + f = open(payload_filepath, 'r', encoding='utf-8') data = f.read() return client.post(request_body=data) diff --git a/setup.py b/setup.py index 8826acb0c..014691b61 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ import sys import os -import io +from io import open from setuptools import setup, find_packages __version__ = None @@ -9,7 +9,7 @@ long_description = 'Please see our GitHub README' if os.path.exists('README.txt'): - long_description = io.open('README.txt', 'r', encoding='utf-8').read() + long_description = open('README.txt', 'r', encoding='utf-8').read() def getRequires(): From 01cec5ef59e630c5c85f9b8fd05ea659763fdadd Mon Sep 17 00:00:00 2001 From: Ryan D'souza Date: Wed, 1 Nov 2017 12:53:31 +0100 Subject: [PATCH 043/462] Removed datastructure from default variable Based on feedback in comments and what I read in [this Python-guide article](http://docs.python-guide.org/en/latest/writing/gotchas/#mutable-default-arguments), I removed the datastructure from a default parameter --- sendgrid/helpers/mail/validators.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sendgrid/helpers/mail/validators.py b/sendgrid/helpers/mail/validators.py index 061727c39..c5cb2d29b 100644 --- a/sendgrid/helpers/mail/validators.py +++ b/sendgrid/helpers/mail/validators.py @@ -7,7 +7,7 @@ class ValidateAPIKey(object): regexes = None - def __init__(self, regex_strings=list(), use_default=True): + def __init__(self, regex_strings=None, use_default=True): """Constructor Args: regex_strings (list): list of regex strings @@ -18,8 +18,9 @@ def __init__(self, regex_strings=list(), use_default=True): self.regexes = set() #Compile the regex strings into patterns, add them to our set - for regex_string in regex_strings: - self.regexes.add(re.compile(regex_string)) + if regex_strings is not None: + for regex_string in regex_strings: + self.regexes.add(re.compile(regex_string)) if use_default: default_regex_string = 'SG\.[0-9a-zA-Z]+\.[0-9a-zA-Z]+' From fcd3f8d49f16c631a8acc37405e22f449d8b5aa1 Mon Sep 17 00:00:00 2001 From: Ryan D'souza Date: Wed, 1 Nov 2017 12:55:50 +0100 Subject: [PATCH 044/462] More PEP-8 appropriate Added needed space `#h` --> `# h` --- sendgrid/helpers/mail/validators.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sendgrid/helpers/mail/validators.py b/sendgrid/helpers/mail/validators.py index c5cb2d29b..48e101452 100644 --- a/sendgrid/helpers/mail/validators.py +++ b/sendgrid/helpers/mail/validators.py @@ -17,7 +17,7 @@ def __init__(self, regex_strings=None, use_default=True): import re self.regexes = set() - #Compile the regex strings into patterns, add them to our set + # Compile the regex strings into patterns, add them to our set if regex_strings is not None: for regex_string in regex_strings: self.regexes.add(re.compile(regex_string)) @@ -37,11 +37,11 @@ def validate_message_dict(self, request_body): APIKeyIncludedException: If any content in request_body matches regex """ - #Handle string in edge-case + # Handle string in edge-case if isinstance(request_body, str): self.validate_message_text(request_body) - #Default param + # Default param elif isinstance(request_body, dict): if "content" in request_body: contents = request_body["content"] From ef162c43830e827cbf36617397d446165d153f39 Mon Sep 17 00:00:00 2001 From: Ryan D'souza Date: Wed, 1 Nov 2017 13:15:15 +0100 Subject: [PATCH 045/462] Reformatted structure to use ".get" --> less width Fixed structure so that it's under the 79 character maximum width for PEP8 --- sendgrid/helpers/mail/validators.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/sendgrid/helpers/mail/validators.py b/sendgrid/helpers/mail/validators.py index 48e101452..855c01f28 100644 --- a/sendgrid/helpers/mail/validators.py +++ b/sendgrid/helpers/mail/validators.py @@ -43,16 +43,17 @@ def validate_message_dict(self, request_body): # Default param elif isinstance(request_body, dict): - if "content" in request_body: - contents = request_body["content"] - - for content in contents: - if "value" in content and "type" in content: - if content["type"] == "text/html" or isinstance(content["value"], str): - message_text = content["value"] - self.validate_message_text(message_text) - + + contents = request_body.get("content", list()) + + for content in contents: + if content is not None: + if (content.get("type") == "text/html" or + isinstance(content.get("value"), str)): + message_text = content.get("value", "") + self.validate_message_text(message_text) + def validate_message_text(self, message_string): """With a message string, check to see if it contains a SendGrid API Key If a key is found, throw an exception From 7c5491e295ce69deace7d0cf769ed6472c78dadf Mon Sep 17 00:00:00 2001 From: Ryan D'souza Date: Wed, 1 Nov 2017 13:17:47 +0100 Subject: [PATCH 046/462] Changed "maxDiff" to "max_diff" --- test/test_mail.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test_mail.py b/test/test_mail.py index 4543456a8..fce4ecfb7 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -40,8 +40,8 @@ class UnitTests(unittest.TestCase): def test_sendgridAPIKey(self): """Tests if including SendGrid API will throw an Exception""" - #Minimum required to send an email - self.maxDiff = None + # Minimum required to send an email + self.max_diff = None mail = Mail() mail.from_email = Email("test@example.com") @@ -82,7 +82,7 @@ def test_sendgridAPIKey(self): def test_helloEmail(self): - self.maxDiff = None + self.max_diff = None """Minimum required to send an email""" mail = Mail() @@ -116,7 +116,7 @@ def test_helloEmail(self): self.assertTrue(isinstance(str(mail), str)) def test_kitchenSink(self): - self.maxDiff = None + self.max_diff = None """All settings set""" mail = Mail() @@ -461,7 +461,7 @@ def test_unicode_values_in_substitutions_helper(self): """ Test that the Substitutions helper accepts unicode values """ - self.maxDiff = None + self.max_diff = None """Minimum required to send an email""" mail = Mail() From d4cc814363649f3971067d55b37126b62417509d Mon Sep 17 00:00:00 2001 From: Ryan D'souza Date: Wed, 1 Nov 2017 17:49:11 +0100 Subject: [PATCH 047/462] Removed double `from` import --- sendgrid/helpers/mail/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sendgrid/helpers/mail/__init__.py b/sendgrid/helpers/mail/__init__.py index 48b329086..1dd769e99 100644 --- a/sendgrid/helpers/mail/__init__.py +++ b/sendgrid/helpers/mail/__init__.py @@ -7,8 +7,7 @@ from .content import Content from .custom_arg import CustomArg from .email import Email -from .exceptions import SendGridException -from .exceptions import APIKeyIncludedException +from .exceptions import SendGridException, APIKeyIncludedException from .footer_settings import FooterSettings from .ganalytics import Ganalytics from .header import Header From 30d93b9eb2021e0f8a1ffb5d8d05ab09acee0822 Mon Sep 17 00:00:00 2001 From: Chetan Kumar Date: Tue, 31 Oct 2017 08:08:45 +0530 Subject: [PATCH 048/462] Fix method_complexity issue in sendgrid/helpers/mail/ganalytics.py #498 --- sendgrid/helpers/mail/ganalytics.py | 50 +++++++++++++++-------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/sendgrid/helpers/mail/ganalytics.py b/sendgrid/helpers/mail/ganalytics.py index 666c2852e..b955b7241 100644 --- a/sendgrid/helpers/mail/ganalytics.py +++ b/sendgrid/helpers/mail/ganalytics.py @@ -30,18 +30,23 @@ def __init__(self, self._utm_content = None self._utm_campaign = None - if enable is not None: - self.enable = enable - if utm_source is not None: - self.utm_source = utm_source - if utm_medium is not None: - self.utm_medium = utm_medium - if utm_term is not None: - self.utm_term = utm_term - if utm_content is not None: - self.utm_content = utm_content - if utm_campaign is not None: - self.utm_campaign = utm_campaign + self.__set_field("enable", enable) + self.__set_field("utm_source", utm_source) + self.__set_field("utm_medium", utm_medium) + self.__set_field("utm_term", utm_term) + self.__set_field("utm_content", utm_content) + self.__set_field("utm_campaign", utm_campaign) + + def __set_field(self, field, value): + """ Sets a field to the provided value if value is not None + + :param field: Name of the field + :type field: String + :param value: value to be set, ignored if None + :type value: Any + """ + if value is not None: + setattr(self, field, value) @property def enable(self): @@ -123,17 +128,14 @@ def get(self): :returns: This Ganalytics, ready for use in a request body. :rtype: dict """ + keys = ["enable", "utm_source", "utm_medium", "utm_term", + "utm_content", "utm_campaign"] + ganalytics = {} - if self.enable is not None: - ganalytics["enable"] = self.enable - if self.utm_source is not None: - ganalytics["utm_source"] = self.utm_source - if self.utm_medium is not None: - ganalytics["utm_medium"] = self.utm_medium - if self.utm_term is not None: - ganalytics["utm_term"] = self.utm_term - if self.utm_content is not None: - ganalytics["utm_content"] = self.utm_content - if self.utm_campaign is not None: - ganalytics["utm_campaign"] = self.utm_campaign + + for key in keys: + value = getattr(self, key, None) + if value is not None: + ganalytics[key] = value + return ganalytics From 2a9b4040ba8ac41c7cf48f3e1e995d85f40d5256 Mon Sep 17 00:00:00 2001 From: d grossman Date: Fri, 3 Nov 2017 14:55:02 -0700 Subject: [PATCH 049/462] moved file, updated import --- {sendgrid/helpers/endpoints/ip => test}/test_unassigned.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename {sendgrid/helpers/endpoints/ip => test}/test_unassigned.py (96%) diff --git a/sendgrid/helpers/endpoints/ip/test_unassigned.py b/test/test_unassigned.py similarity index 96% rename from sendgrid/helpers/endpoints/ip/test_unassigned.py rename to test/test_unassigned.py index be5904018..d13451277 100644 --- a/sendgrid/helpers/endpoints/ip/test_unassigned.py +++ b/test/test_unassigned.py @@ -1,7 +1,8 @@ import json import pytest -from .unassigned import unassigned +from sendgrid.helpers.endpoints.ip.unassigned import unassigned + ret_json = '''[ { "ip": "167.89.21.3", From d8967ae317fc7173c8cc7d2b11673ed22851eef5 Mon Sep 17 00:00:00 2001 From: Krista LaFentres Date: Sun, 5 Nov 2017 15:29:32 -0600 Subject: [PATCH 050/462] Update CONTRIBUTING.md Adding clarification to docker vs local install and running python setup.py install command --- CONTRIBUTING.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1d41c2248..b273251f8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,11 +59,13 @@ In order to make the process easier, we've included a [sample bug report templat We welcome direct contributions to the sendgrid-python code base. Thank you! ### Development Environment ### - -#### Using Docker #### +#### There are two ways to get set up: #### +#### 1. Using Docker #### +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). -#### Install and Run Locally #### +#### - OR - #### +#### 2. Install and Run Locally #### ##### Prerequisites ##### @@ -150,7 +152,12 @@ pyenv install 2.6.9 pyenv install 2.7.11 pyenv install 3.4.3 pyenv install 3.5.0 +``` +Make sure to change the current working directory to your local version of the repo before running the following command: +``` python setup.py install +``` +``` pyenv local 3.5.0 3.4.3 2.7.11 2.6.9 pyenv rehash ``` From f5577bb7e2f356a7a967a00d36fa177ef6e331d5 Mon Sep 17 00:00:00 2001 From: PierreMonico Date: Wed, 15 Nov 2017 20:21:35 +0100 Subject: [PATCH 051/462] Fix bug in get_mock_personalization_dict() --- examples/helpers/mail/mail_example.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/helpers/mail/mail_example.py b/examples/helpers/mail/mail_example.py index bfd8ea718..402d295ea 100644 --- a/examples/helpers/mail/mail_example.py +++ b/examples/helpers/mail/mail_example.py @@ -24,25 +24,25 @@ def build_personalization(personalization): """Build personalization mock instance from a mock dict""" mock_personalization = Personalization() for to_addr in personalization['to_list']: - personalization.add_to(to_addr) + mock_personalization.add_to(to_addr) for cc_addr in personalization['cc_list']: - personalization.add_to(cc_addr) + mock_personalization.add_to(cc_addr) for bcc_addr in personalization['bcc_list']: - personalization.add_bc(bcc_addr) + mock_personalization.add_bc(bcc_addr) for header in personalization['headers']: - personalization.add_header(header) + mock_personalization.add_header(header) for substitution in personalization['substitutions']: - personalization.add_substitution(substitution) + mock_personalization.add_substitution(substitution) for arg in personalization['custom_args']: - personalization.add_custom_arg(arg) + mock_personalization.add_custom_arg(arg) - personalization.subject = personalization['subject'] - personalization.send_at = personalization['send_at'] + mock_personalization.subject = personalization['subject'] + mock_personalization.send_at = personalization['send_at'] return mock_personalization From cc4a2dea4de6e08d02f0f555949fad8d5b20da12 Mon Sep 17 00:00:00 2001 From: mbonnefoy Date: Mon, 11 Dec 2017 22:13:57 +1300 Subject: [PATCH 052/462] Fix typo in test_env --- test/test_project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_project.py b/test/test_project.py index 861f1ffe0..2d982b1ef 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -17,7 +17,7 @@ def test_docker_compose(self): # ./.env_sample def test_env(self): - self.assertTrue(os.path.isfile('./env_sample')) + self.assertTrue(os.path.isfile('./.env_sample')) # ./.gitignore def test_gitignore(self): From 955cac6889c6d7e113f48a3f6a9286cc215cb114 Mon Sep 17 00:00:00 2001 From: mbernier Date: Tue, 12 Dec 2017 08:20:58 -0700 Subject: [PATCH 053/462] removed extra examples dir --- .../examples/accesssettings/accesssettings.py | 84 ---- docker/examples/alerts/alerts.py | 63 --- docker/examples/apikeys/apikeys.py | 85 ---- docker/examples/asm/asm.py | 174 -------- docker/examples/browsers/browsers.py | 17 - docker/examples/campaigns/campaigns.py | 154 ------- docker/examples/categories/categories.py | 37 -- docker/examples/clients/clients.py | 28 -- docker/examples/contactdb/contactdb.py | 396 ------------------ docker/examples/devices/devices.py | 17 - docker/examples/geo/geo.py | 17 - docker/examples/helpers/mail/mail_example.py | 219 ---------- docker/examples/ips/ips.py | 155 ------- docker/examples/mail/mail.py | 174 -------- .../mailboxproviders/mailboxproviders.py | 17 - docker/examples/mailsettings/mailsettings.py | 220 ---------- .../partnersettings/partnersettings.py | 40 -- docker/examples/scopes/scopes.py | 16 - docker/examples/senders/senders.py | 99 ----- docker/examples/stats/stats.py | 17 - docker/examples/subusers/subusers.py | 170 -------- docker/examples/suppression/suppression.py | 202 --------- docker/examples/templates/templates.py | 130 ------ .../trackingsettings/trackingsettings.py | 111 ----- docker/examples/user/user.py | 294 ------------- docker/examples/whitelabel/whitelabel.py | 311 -------------- 26 files changed, 3247 deletions(-) delete mode 100644 docker/examples/accesssettings/accesssettings.py delete mode 100644 docker/examples/alerts/alerts.py delete mode 100644 docker/examples/apikeys/apikeys.py delete mode 100644 docker/examples/asm/asm.py delete mode 100644 docker/examples/browsers/browsers.py delete mode 100644 docker/examples/campaigns/campaigns.py delete mode 100644 docker/examples/categories/categories.py delete mode 100644 docker/examples/clients/clients.py delete mode 100644 docker/examples/contactdb/contactdb.py delete mode 100644 docker/examples/devices/devices.py delete mode 100644 docker/examples/geo/geo.py delete mode 100644 docker/examples/helpers/mail/mail_example.py delete mode 100644 docker/examples/ips/ips.py delete mode 100644 docker/examples/mail/mail.py delete mode 100644 docker/examples/mailboxproviders/mailboxproviders.py delete mode 100644 docker/examples/mailsettings/mailsettings.py delete mode 100644 docker/examples/partnersettings/partnersettings.py delete mode 100644 docker/examples/scopes/scopes.py delete mode 100644 docker/examples/senders/senders.py delete mode 100644 docker/examples/stats/stats.py delete mode 100644 docker/examples/subusers/subusers.py delete mode 100644 docker/examples/suppression/suppression.py delete mode 100644 docker/examples/templates/templates.py delete mode 100644 docker/examples/trackingsettings/trackingsettings.py delete mode 100644 docker/examples/user/user.py delete mode 100644 docker/examples/whitelabel/whitelabel.py diff --git a/docker/examples/accesssettings/accesssettings.py b/docker/examples/accesssettings/accesssettings.py deleted file mode 100644 index aac0e4a54..000000000 --- a/docker/examples/accesssettings/accesssettings.py +++ /dev/null @@ -1,84 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Retrieve all recent access attempts # -# GET /access_settings/activity # - -params = {'limit': 1} -response = sg.client.access_settings.activity.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Add one or more IPs to the whitelist # -# POST /access_settings/whitelist # - -data = { - "ips": [ - { - "ip": "192.168.1.1" - }, - { - "ip": "192.*.*.*" - }, - { - "ip": "192.168.1.3/32" - } - ] -} -response = sg.client.access_settings.whitelist.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve a list of currently whitelisted IPs # -# GET /access_settings/whitelist # - -response = sg.client.access_settings.whitelist.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Remove one or more IPs from the whitelist # -# DELETE /access_settings/whitelist # - -data = { - "ids": [ - 1, - 2, - 3 - ] -} -response = sg.client.access_settings.whitelist.delete(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve a specific whitelisted IP # -# GET /access_settings/whitelist/{rule_id} # - -rule_id = "test_url_param" -response = sg.client.access_settings.whitelist._(rule_id).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Remove a specific IP from the whitelist # -# DELETE /access_settings/whitelist/{rule_id} # - -rule_id = "test_url_param" -response = sg.client.access_settings.whitelist._(rule_id).delete() -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/alerts/alerts.py b/docker/examples/alerts/alerts.py deleted file mode 100644 index e30d48748..000000000 --- a/docker/examples/alerts/alerts.py +++ /dev/null @@ -1,63 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Create a new Alert # -# POST /alerts # - -data = { - "email_to": "example@example.com", - "frequency": "daily", - "type": "stats_notification" -} -response = sg.client.alerts.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all alerts # -# GET /alerts # - -response = sg.client.alerts.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update an alert # -# PATCH /alerts/{alert_id} # - -data = { - "email_to": "example@example.com" -} -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) - -################################################## -# Retrieve a specific alert # -# GET /alerts/{alert_id} # - -alert_id = "test_url_param" -response = sg.client.alerts._(alert_id).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete an alert # -# DELETE /alerts/{alert_id} # - -alert_id = "test_url_param" -response = sg.client.alerts._(alert_id).delete() -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/apikeys/apikeys.py b/docker/examples/apikeys/apikeys.py deleted file mode 100644 index 42c3afa10..000000000 --- a/docker/examples/apikeys/apikeys.py +++ /dev/null @@ -1,85 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Create API keys # -# POST /api_keys # - -data = { - "name": "My API Key", - "sample": "data", - "scopes": [ - "mail.send", - "alerts.create", - "alerts.read" - ] -} -response = sg.client.api_keys.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all API Keys belonging to the authenticated user # -# GET /api_keys # - -params = {'limit': 1} -response = sg.client.api_keys.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update the name & scopes of an API Key # -# PUT /api_keys/{api_key_id} # - -data = { - "name": "A New Hope", - "scopes": [ - "user.profile.read", - "user.profile.update" - ] -} -api_key_id = "test_url_param" -response = sg.client.api_keys._(api_key_id).put(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update API keys # -# PATCH /api_keys/{api_key_id} # - -data = { - "name": "A New Hope" -} -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) - -################################################## -# Retrieve an existing API Key # -# GET /api_keys/{api_key_id} # - -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) - -################################################## -# Delete API keys # -# DELETE /api_keys/{api_key_id} # - -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) - diff --git a/docker/examples/asm/asm.py b/docker/examples/asm/asm.py deleted file mode 100644 index 43130cf06..000000000 --- a/docker/examples/asm/asm.py +++ /dev/null @@ -1,174 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Create a new suppression group # -# POST /asm/groups # - -data = { - "description": "Suggestions for products our users might like.", - "is_default": True, - "name": "Product Suggestions" -} -response = sg.client.asm.groups.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve information about multiple suppression groups # -# GET /asm/groups # - -params = {'id': 1} -response = sg.client.asm.groups.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update a suppression group. # -# PATCH /asm/groups/{group_id} # - -data = { - "description": "Suggestions for items our users might like.", - "id": 103, - "name": "Item Suggestions" -} -group_id = "test_url_param" -response = sg.client.asm.groups._(group_id).patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Get information on a single suppression group. # -# GET /asm/groups/{group_id} # - -group_id = "test_url_param" -response = sg.client.asm.groups._(group_id).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete a suppression group. # -# DELETE /asm/groups/{group_id} # - -group_id = "test_url_param" -response = sg.client.asm.groups._(group_id).delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Add suppressions to a suppression group # -# POST /asm/groups/{group_id}/suppressions # - -data = { - "recipient_emails": [ - "test1@example.com", - "test2@example.com" - ] -} -group_id = "test_url_param" -response = sg.client.asm.groups._(group_id).suppressions.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all suppressions for a suppression group # -# GET /asm/groups/{group_id}/suppressions # - -group_id = "test_url_param" -response = sg.client.asm.groups._(group_id).suppressions.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Search for suppressions within a group # -# POST /asm/groups/{group_id}/suppressions/search # - -data = { - "recipient_emails": [ - "exists1@example.com", - "exists2@example.com", - "doesnotexists@example.com" - ] -} -group_id = "test_url_param" -response = sg.client.asm.groups._(group_id).suppressions.search.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete a suppression from a suppression group # -# DELETE /asm/groups/{group_id}/suppressions/{email} # - -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) - -################################################## -# Retrieve all suppressions # -# GET /asm/suppressions # - -response = sg.client.asm.suppressions.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Add recipient addresses to the global suppression group. # -# POST /asm/suppressions/global # - -data = { - "recipient_emails": [ - "test1@example.com", - "test2@example.com" - ] -} -response = sg.client.asm.suppressions._("global").post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve a Global Suppression # -# GET /asm/suppressions/global/{email} # - -email = "test_url_param" -response = sg.client.asm.suppressions._("global")._(email).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete a Global Suppression # -# DELETE /asm/suppressions/global/{email} # - -email = "test_url_param" -response = sg.client.asm.suppressions._("global")._(email).delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all suppression groups for an email address # -# GET /asm/suppressions/{email} # - -email = "test_url_param" -response = sg.client.asm.suppressions._(email).get() -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/browsers/browsers.py b/docker/examples/browsers/browsers.py deleted file mode 100644 index c123c12e5..000000000 --- a/docker/examples/browsers/browsers.py +++ /dev/null @@ -1,17 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Retrieve email statistics by browser. # -# GET /browsers/stats # - -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'browsers': 'test_string', 'limit': 'test_string', 'offset': 'test_string', 'start_date': '2016-01-01'} -response = sg.client.browsers.stats.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/campaigns/campaigns.py b/docker/examples/campaigns/campaigns.py deleted file mode 100644 index c77fc878b..000000000 --- a/docker/examples/campaigns/campaigns.py +++ /dev/null @@ -1,154 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Create a Campaign # -# POST /campaigns # - -data = { - "categories": [ - "spring line" - ], - "custom_unsubscribe_url": "", - "html_content": "

Check out our spring line!

", - "ip_pool": "marketing", - "list_ids": [ - 110, - 124 - ], - "plain_content": "Check out our spring line!", - "segment_ids": [ - 110 - ], - "sender_id": 124451, - "subject": "New Products for Spring!", - "suppression_group_id": 42, - "title": "March Newsletter" -} -response = sg.client.campaigns.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all Campaigns # -# GET /campaigns # - -params = {'limit': 1, 'offset': 1} -response = sg.client.campaigns.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update a Campaign # -# PATCH /campaigns/{campaign_id} # - -data = { - "categories": [ - "summer line" - ], - "html_content": "

Check out our summer line!

", - "plain_content": "Check out our summer line!", - "subject": "New Products for Summer!", - "title": "May Newsletter" -} -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) - -################################################## -# Retrieve a single campaign # -# GET /campaigns/{campaign_id} # - -campaign_id = "test_url_param" -response = sg.client.campaigns._(campaign_id).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete a Campaign # -# DELETE /campaigns/{campaign_id} # - -campaign_id = "test_url_param" -response = sg.client.campaigns._(campaign_id).delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update a Scheduled Campaign # -# PATCH /campaigns/{campaign_id}/schedules # - -data = { - "send_at": 1489451436 -} -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) - -################################################## -# Schedule a Campaign # -# POST /campaigns/{campaign_id}/schedules # - -data = { - "send_at": 1489771528 -} -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) - -################################################## -# View Scheduled Time of a Campaign # -# GET /campaigns/{campaign_id}/schedules # - -campaign_id = "test_url_param" -response = sg.client.campaigns._(campaign_id).schedules.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Unschedule a Scheduled Campaign # -# DELETE /campaigns/{campaign_id}/schedules # - -campaign_id = "test_url_param" -response = sg.client.campaigns._(campaign_id).schedules.delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Send a Campaign # -# POST /campaigns/{campaign_id}/schedules/now # - -campaign_id = "test_url_param" -response = sg.client.campaigns._(campaign_id).schedules.now.post() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Send a Test Campaign # -# POST /campaigns/{campaign_id}/schedules/test # - -data = { - "to": "your.email@example.com" -} -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) - diff --git a/docker/examples/categories/categories.py b/docker/examples/categories/categories.py deleted file mode 100644 index 7984f0fe0..000000000 --- a/docker/examples/categories/categories.py +++ /dev/null @@ -1,37 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Retrieve all categories # -# GET /categories # - -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) - -################################################## -# Retrieve Email Statistics for Categories # -# GET /categories/stats # - -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01', 'categories': 'test_string'} -response = sg.client.categories.stats.get(query_params=params) -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?] # -# GET /categories/stats/sums # - -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} -response = sg.client.categories.stats.sums.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/clients/clients.py b/docker/examples/clients/clients.py deleted file mode 100644 index 7831ef78f..000000000 --- a/docker/examples/clients/clients.py +++ /dev/null @@ -1,28 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Retrieve email statistics by client type. # -# GET /clients/stats # - -params = {'aggregated_by': 'day', 'start_date': '2016-01-01', 'end_date': '2016-04-01'} -response = sg.client.clients.stats.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve stats by a specific client type. # -# GET /clients/{client_type}/stats # - -params = {'aggregated_by': 'day', 'start_date': '2016-01-01', 'end_date': '2016-04-01'} -client_type = "test_url_param" -response = sg.client.clients._(client_type).stats.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/contactdb/contactdb.py b/docker/examples/contactdb/contactdb.py deleted file mode 100644 index c234d7724..000000000 --- a/docker/examples/contactdb/contactdb.py +++ /dev/null @@ -1,396 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Create a Custom Field # -# POST /contactdb/custom_fields # - -data = { - "name": "pet", - "type": "text" -} -response = sg.client.contactdb.custom_fields.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all custom fields # -# GET /contactdb/custom_fields # - -response = sg.client.contactdb.custom_fields.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve a Custom Field # -# GET /contactdb/custom_fields/{custom_field_id} # - -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) - -################################################## -# Delete a Custom Field # -# DELETE /contactdb/custom_fields/{custom_field_id} # - -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) - -################################################## -# Create a List # -# POST /contactdb/lists # - -data = { - "name": "your list name" -} -response = sg.client.contactdb.lists.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all lists # -# GET /contactdb/lists # - -response = sg.client.contactdb.lists.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete Multiple lists # -# DELETE /contactdb/lists # - -data = [ - 1, - 2, - 3, - 4 -] -response = sg.client.contactdb.lists.delete(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update a List # -# PATCH /contactdb/lists/{list_id} # - -data = { - "name": "newlistname" -} -params = {'list_id': 1} -list_id = "test_url_param" -response = sg.client.contactdb.lists._(list_id).patch(request_body=data, query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve a single list # -# GET /contactdb/lists/{list_id} # - -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) - -################################################## -# Delete a List # -# DELETE /contactdb/lists/{list_id} # - -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) - -################################################## -# Add Multiple Recipients to a List # -# POST /contactdb/lists/{list_id}/recipients # - -data = [ - "recipient_id1", - "recipient_id2" -] -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) - -################################################## -# Retrieve all recipients on a List # -# GET /contactdb/lists/{list_id}/recipients # - -params = {'page': 1, 'page_size': 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) - -################################################## -# Add a Single Recipient to a List # -# POST /contactdb/lists/{list_id}/recipients/{recipient_id} # - -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) - -################################################## -# Delete a Single Recipient from a Single List # -# DELETE /contactdb/lists/{list_id}/recipients/{recipient_id} # - -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) - -################################################## -# Update Recipient # -# PATCH /contactdb/recipients # - -data = [ - { - "email": "jones@example.com", - "first_name": "Guy", - "last_name": "Jones" - } -] -response = sg.client.contactdb.recipients.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Add recipients # -# POST /contactdb/recipients # - -data = [ - { - "age": 25, - "email": "example@example.com", - "first_name": "", - "last_name": "User" - }, - { - "age": 25, - "email": "example2@example.com", - "first_name": "Example", - "last_name": "User" - } -] -response = sg.client.contactdb.recipients.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve recipients # -# GET /contactdb/recipients # - -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) - -################################################## -# Delete Recipient # -# DELETE /contactdb/recipients # - -data = [ - "recipient_id1", - "recipient_id2" -] -response = sg.client.contactdb.recipients.delete(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve the count of billable recipients # -# GET /contactdb/recipients/billable_count # - -response = sg.client.contactdb.recipients.billable_count.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve a Count of Recipients # -# GET /contactdb/recipients/count # - -response = sg.client.contactdb.recipients.count.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve recipients matching search criteria # -# GET /contactdb/recipients/search # - -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) - -################################################## -# Retrieve a single recipient # -# GET /contactdb/recipients/{recipient_id} # - -recipient_id = "test_url_param" -response = sg.client.contactdb.recipients._(recipient_id).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete a Recipient # -# DELETE /contactdb/recipients/{recipient_id} # - -recipient_id = "test_url_param" -response = sg.client.contactdb.recipients._(recipient_id).delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve the lists that a recipient is on # -# GET /contactdb/recipients/{recipient_id}/lists # - -recipient_id = "test_url_param" -response = sg.client.contactdb.recipients._(recipient_id).lists.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve reserved fields # -# GET /contactdb/reserved_fields # - -response = sg.client.contactdb.reserved_fields.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Create a Segment # -# POST /contactdb/segments # - -data = { - "conditions": [ - { - "and_or": "", - "field": "last_name", - "operator": "eq", - "value": "Miller" - }, - { - "and_or": "and", - "field": "last_clicked", - "operator": "gt", - "value": "01/02/2015" - }, - { - "and_or": "or", - "field": "clicks.campaign_identifier", - "operator": "eq", - "value": "513" - } - ], - "list_id": 4, - "name": "Last Name Miller" -} -response = sg.client.contactdb.segments.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all segments # -# GET /contactdb/segments # - -response = sg.client.contactdb.segments.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update a segment # -# PATCH /contactdb/segments/{segment_id} # - -data = { - "conditions": [ - { - "and_or": "", - "field": "last_name", - "operator": "eq", - "value": "Miller" - } - ], - "list_id": 5, - "name": "The Millers" -} -params = {'segment_id': 'test_string'} -segment_id = "test_url_param" -response = sg.client.contactdb.segments._(segment_id).patch(request_body=data, query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve a segment # -# GET /contactdb/segments/{segment_id} # - -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) - -################################################## -# Delete a segment # -# DELETE /contactdb/segments/{segment_id} # - -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) - -################################################## -# Retrieve recipients on a segment # -# GET /contactdb/segments/{segment_id}/recipients # - -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) - diff --git a/docker/examples/devices/devices.py b/docker/examples/devices/devices.py deleted file mode 100644 index 108e98452..000000000 --- a/docker/examples/devices/devices.py +++ /dev/null @@ -1,17 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Retrieve email statistics by device type. # -# GET /devices/stats # - -params = {'aggregated_by': 'day', 'limit': 1, 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} -response = sg.client.devices.stats.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/geo/geo.py b/docker/examples/geo/geo.py deleted file mode 100644 index 7d58ec085..000000000 --- a/docker/examples/geo/geo.py +++ /dev/null @@ -1,17 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Retrieve email statistics by country and state/province. # -# GET /geo/stats # - -params = {'end_date': '2016-04-01', 'country': 'US', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01'} -response = sg.client.geo.stats.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/helpers/mail/mail_example.py b/docker/examples/helpers/mail/mail_example.py deleted file mode 100644 index bfd8ea718..000000000 --- a/docker/examples/helpers/mail/mail_example.py +++ /dev/null @@ -1,219 +0,0 @@ -import json -import os -import urllib2 -from sendgrid.helpers.mail import * -from sendgrid import * - -# NOTE: you will need move this file to the root -# directory of this project to execute properly. - - -def build_hello_email(): - """Minimum required to send an email""" - from_email = Email("test@example.com") - subject = "Hello World from the SendGrid Python Library" - to_email = Email("test@example.com") - content = Content("text/plain", "some text here") - mail = Mail(from_email, subject, to_email, content) - mail.personalizations[0].add_to(Email("test2@example.com")) - - return mail.get() - - -def build_personalization(personalization): - """Build personalization mock instance from a mock dict""" - mock_personalization = Personalization() - for to_addr in personalization['to_list']: - personalization.add_to(to_addr) - - for cc_addr in personalization['cc_list']: - personalization.add_to(cc_addr) - - for bcc_addr in personalization['bcc_list']: - personalization.add_bc(bcc_addr) - - for header in personalization['headers']: - personalization.add_header(header) - - for substitution in personalization['substitutions']: - personalization.add_substitution(substitution) - - for arg in personalization['custom_args']: - personalization.add_custom_arg(arg) - - personalization.subject = personalization['subject'] - personalization.send_at = personalization['send_at'] - return mock_personalization - - -def get_mock_personalization_dict(): - """Get a dict of personalization mock.""" - mock_pers = dict() - - mock_pers['to_list'] = [Email("test1@example.com", - "Example User"), - Email("test2@example.com", - "Example User")] - - mock_pers['cc_list'] = [Email("test3@example.com", - "Example User"), - Email("test4@example.com", - "Example User")] - - mock_pers['bcc_list'] = [Email("test5@example.com"), - Email("test6@example.com")] - - mock_pers['subject'] = ("Hello World from the Personalized " - "SendGrid Python Library") - - mock_pers['headers'] = [Header("X-Test", "test"), - Header("X-Mock", "true")] - - mock_pers['substitutions'] = [Substitution("%name%", "Example User"), - Substitution("%city%", "Denver")] - - mock_pers['custom_args'] = [CustomArg("user_id", "343"), - CustomArg("type", "marketing")] - - mock_pers['send_at'] = 1443636843 - return mock_pers - - -def build_attachment1(): - """Build attachment mock.""" - attachment = Attachment() - attachment.content = ("TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNl" - "Y3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12") - attachment.type = "application/pdf" - attachment.filename = "balance_001.pdf" - attachment.disposition = "attachment" - attachment.content_id = "Balance Sheet" - return attachment - - -def build_attachment2(): - """Build attachment mock.""" - attachment = Attachment() - attachment.content = "BwdW" - attachment.type = "image/png" - attachment.filename = "banner.png" - attachment.disposition = "inline" - attachment.content_id = "Banner" - return attachment - - -def build_mail_settings(): - """Build mail settings mock.""" - mail_settings = MailSettings() - mail_settings.bcc_settings = BCCSettings(True, Email("test@example.com")) - mail_settings.bypass_list_management = BypassListManagement(True) - mail_settings.footer_settings = FooterSettings(True, "Footer Text", - ("Footer " - "Text")) - mail_settings.sandbox_mode = SandBoxMode(True) - mail_settings.spam_check = SpamCheck(True, 1, - "https://spamcatcher.sendgrid.com") - return mail_settings - - -def build_tracking_settings(): - """Build tracking settings mock.""" - tracking_settings = TrackingSettings() - tracking_settings.click_tracking = ClickTracking(True, True) - tracking_settings.open_tracking = OpenTracking(True, - ("Optional tag to " - "replace with the" - "open image in the " - "body of the message")) - - subs_track = SubscriptionTracking(True, - ("text to insert into the " - "text/plain portion of the" - " message"), - ("html to insert " - "into the text/html portion of " - "the message"), - ("Optional tag to replace with " - "the open image in the body of " - "the message")) - - tracking_settings.subscription_tracking = subs_track - tracking_settings.ganalytics = Ganalytics(True, "some source", - "some medium", "some term", - "some_content", "some_campaign") - return tracking_settings - - -def build_kitchen_sink(): - """All settings set""" - mail = Mail() - - mail.from_email = Email("test@example.com", "Example User") - mail.subject = "Hello World from the SendGrid Python Library" - - personalization = get_mock_personalization_dict() - mail.add_personalization(build_personalization(personalization)) - mail.add_personalization(build_personalization(personalization)) - - mail.add_content(Content("text/plain", "some text here")) - mail.add_content(Content("text/html", ("some text " - "here"))) - - mail.add_attachment(build_attachment1()) - mail.add_attachment(build_attachment2()) - - mail.template_id = "13b8f94f-bcae-4ec6-b752-70d6cb59f932" - - mail.add_section(Section("%section1%", "Substitution Text for Section 1")) - mail.add_section(Section("%section2%", "Substitution Text for Section 2")) - - mail.add_header(Header("X-Test1", "test1")) - mail.add_header(Header("X-Test3", "test2")) - - mail.add_category(Category("May")) - mail.add_category(Category("2016")) - - mail.add_custom_arg(CustomArg("campaign", "welcome")) - mail.add_custom_arg(CustomArg("weekday", "morning")) - - mail.send_at = 1443636842 - - # This must be a valid [batch ID] - # (https://sendgrid.com/docs/API_Reference/SMTP_API/scheduling_parameters.html) to work - # mail.set_batch_id("N2VkYjBjYWItMGU4OC0xMWU2LWJhMzYtZjQ1Yzg5OTBkNzkxLWM5ZTUyZjNhOA") - mail.asm = ASM(99, [4, 5, 6, 7, 8]) - mail.ip_pool_name = "24" - mail.mail_settings = build_mail_settings() - mail.tracking_settings = build_tracking_settings() - mail.reply_to = Email("test@example.com") - - return mail.get() - - -def send_hello_email(): - # Assumes you set your environment variable: - # https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key - sg = SendGridAPIClient() - data = build_hello_email() - response = sg.client.mail.send.post(request_body=data) - print(response.status_code) - print(response.headers) - print(response.body) - - -def send_kitchen_sink(): - # Assumes you set your environment variable: - # https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key - sg = SendGridAPIClient() - data = build_kitchen_sink() - response = sg.client.mail.send.post(request_body=data) - print(response.status_code) - print(response.headers) - print(response.body) - - -# this will actually send an email -send_hello_email() - -# this will only send an email if you set SandBox Mode to False -send_kitchen_sink() diff --git a/docker/examples/ips/ips.py b/docker/examples/ips/ips.py deleted file mode 100644 index 6c48ae306..000000000 --- a/docker/examples/ips/ips.py +++ /dev/null @@ -1,155 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Retrieve all IP addresses # -# GET /ips # - -params = {'subuser': 'test_string', 'ip': 'test_string', 'limit': 1, 'exclude_whitelabels': 'true', 'offset': 1} -response = sg.client.ips.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all assigned IPs # -# GET /ips/assigned # - -response = sg.client.ips.assigned.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Create an IP pool. # -# POST /ips/pools # - -data = { - "name": "marketing" -} -response = sg.client.ips.pools.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all IP pools. # -# GET /ips/pools # - -response = sg.client.ips.pools.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update an IP pools name. # -# PUT /ips/pools/{pool_name} # - -data = { - "name": "new_pool_name" -} -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) - -################################################## -# Retrieve all IPs in a specified pool. # -# GET /ips/pools/{pool_name} # - -pool_name = "test_url_param" -response = sg.client.ips.pools._(pool_name).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete an IP pool. # -# DELETE /ips/pools/{pool_name} # - -pool_name = "test_url_param" -response = sg.client.ips.pools._(pool_name).delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Add an IP address to a pool # -# POST /ips/pools/{pool_name}/ips # - -data = { - "ip": "0.0.0.0" -} -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) - -################################################## -# Remove an IP address from a pool. # -# DELETE /ips/pools/{pool_name}/ips/{ip} # - -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) - -################################################## -# Add an IP to warmup # -# POST /ips/warmup # - -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) - -################################################## -# Retrieve all IPs currently in warmup # -# GET /ips/warmup # - -response = sg.client.ips.warmup.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve warmup status for a specific IP address # -# GET /ips/warmup/{ip_address} # - -ip_address = "test_url_param" -response = sg.client.ips.warmup._(ip_address).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Remove an IP from warmup # -# DELETE /ips/warmup/{ip_address} # - -ip_address = "test_url_param" -response = sg.client.ips.warmup._(ip_address).delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all IP pools an IP address belongs to # -# GET /ips/{ip_address} # - -ip_address = "test_url_param" -response = sg.client.ips._(ip_address).get() -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/mail/mail.py b/docker/examples/mail/mail.py deleted file mode 100644 index fef420e87..000000000 --- a/docker/examples/mail/mail.py +++ /dev/null @@ -1,174 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Create a batch ID # -# POST /mail/batch # - -response = sg.client.mail.batch.post() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Validate batch ID # -# GET /mail/batch/{batch_id} # - -batch_id = "test_url_param" -response = sg.client.mail.batch._(batch_id).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# 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). - -data = { - "asm": { - "group_id": 1, - "groups_to_display": [ - 1, - 2, - 3 - ] - }, - "attachments": [ - { - "content": "[BASE64 encoded content block here]", - "content_id": "ii_139db99fdb5c3704", - "disposition": "inline", - "filename": "file1.jpg", - "name": "file1", - "type": "jpg" - } - ], - "batch_id": "[YOUR BATCH ID GOES HERE]", - "categories": [ - "category1", - "category2" - ], - "content": [ - { - "type": "text/html", - "value": "

Hello, world!

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

Thanks
The SendGrid Team

", - "text": "Thanks,/n The SendGrid Team" - }, - "sandbox_mode": { - "enable": False - }, - "spam_check": { - "enable": True, - "post_to_url": "http://example.com/compliance", - "threshold": 3 - } - }, - "personalizations": [ - { - "bcc": [ - { - "email": "sam.doe@example.com", - "name": "Sam Doe" - } - ], - "cc": [ - { - "email": "jane.doe@example.com", - "name": "Jane Doe" - } - ], - "custom_args": { - "New Argument 1": "New Value 1", - "activationAttempt": "1", - "customerAccountNumber": "[CUSTOMER ACCOUNT NUMBER GOES HERE]" - }, - "headers": { - "X-Accept-Language": "en", - "X-Mailer": "MyApp" - }, - "send_at": 1409348513, - "subject": "Hello, World!", - "substitutions": { - "id": "substitutions", - "type": "object" - }, - "to": [ - { - "email": "john.doe@example.com", - "name": "John Doe" - } - ] - } - ], - "reply_to": { - "email": "sam.smith@example.com", - "name": "Sam Smith" - }, - "sections": { - "section": { - ":sectionName1": "section 1 text", - ":sectionName2": "section 2 text" - } - }, - "send_at": 1409348513, - "subject": "Hello, World!", - "template_id": "[YOUR TEMPLATE ID GOES HERE]", - "tracking_settings": { - "click_tracking": { - "enable": True, - "enable_text": True - }, - "ganalytics": { - "enable": True, - "utm_campaign": "[NAME OF YOUR REFERRER SOURCE]", - "utm_content": "[USE THIS SPACE TO DIFFERENTIATE YOUR EMAIL FROM ADS]", - "utm_medium": "[NAME OF YOUR MARKETING MEDIUM e.g. email]", - "utm_name": "[NAME OF YOUR CAMPAIGN]", - "utm_term": "[IDENTIFY PAID KEYWORDS HERE]" - }, - "open_tracking": { - "enable": True, - "substitution_tag": "%opentrack" - }, - "subscription_tracking": { - "enable": True, - "html": "If you would like to unsubscribe and stop receiving these emails <% clickhere %>.", - "substitution_tag": "<%click here%>", - "text": "If you would like to unsubscribe and stop receiveing these emails <% click here %>." - } - } -} -response = sg.client.mail.send.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/mailboxproviders/mailboxproviders.py b/docker/examples/mailboxproviders/mailboxproviders.py deleted file mode 100644 index 1b75ecac5..000000000 --- a/docker/examples/mailboxproviders/mailboxproviders.py +++ /dev/null @@ -1,17 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Retrieve email statistics by mailbox provider. # -# GET /mailbox_providers/stats # - -params = {'end_date': '2016-04-01', 'mailbox_providers': 'test_string', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01'} -response = sg.client.mailbox_providers.stats.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/mailsettings/mailsettings.py b/docker/examples/mailsettings/mailsettings.py deleted file mode 100644 index 18c57b960..000000000 --- a/docker/examples/mailsettings/mailsettings.py +++ /dev/null @@ -1,220 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Retrieve all mail settings # -# GET /mail_settings # - -params = {'limit': 1, 'offset': 1} -response = sg.client.mail_settings.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update address whitelist mail settings # -# PATCH /mail_settings/address_whitelist # - -data = { - "enabled": True, - "list": [ - "email1@example.com", - "example.com" - ] -} -response = sg.client.mail_settings.address_whitelist.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve address whitelist mail settings # -# GET /mail_settings/address_whitelist # - -response = sg.client.mail_settings.address_whitelist.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update BCC mail settings # -# PATCH /mail_settings/bcc # - -data = { - "email": "email@example.com", - "enabled": False -} -response = sg.client.mail_settings.bcc.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all BCC mail settings # -# GET /mail_settings/bcc # - -response = sg.client.mail_settings.bcc.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update bounce purge mail settings # -# PATCH /mail_settings/bounce_purge # - -data = { - "enabled": True, - "hard_bounces": 5, - "soft_bounces": 5 -} -response = sg.client.mail_settings.bounce_purge.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve bounce purge mail settings # -# GET /mail_settings/bounce_purge # - -response = sg.client.mail_settings.bounce_purge.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update footer mail settings # -# PATCH /mail_settings/footer # - -data = { - "enabled": True, - "html_content": "...", - "plain_content": "..." -} -response = sg.client.mail_settings.footer.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve footer mail settings # -# GET /mail_settings/footer # - -response = sg.client.mail_settings.footer.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update forward bounce mail settings # -# PATCH /mail_settings/forward_bounce # - -data = { - "email": "example@example.com", - "enabled": True -} -response = sg.client.mail_settings.forward_bounce.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve forward bounce mail settings # -# GET /mail_settings/forward_bounce # - -response = sg.client.mail_settings.forward_bounce.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update forward spam mail settings # -# PATCH /mail_settings/forward_spam # - -data = { - "email": "", - "enabled": False -} -response = sg.client.mail_settings.forward_spam.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve forward spam mail settings # -# GET /mail_settings/forward_spam # - -response = sg.client.mail_settings.forward_spam.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update plain content mail settings # -# PATCH /mail_settings/plain_content # - -data = { - "enabled": False -} -response = sg.client.mail_settings.plain_content.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve plain content mail settings # -# GET /mail_settings/plain_content # - -response = sg.client.mail_settings.plain_content.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update spam check mail settings # -# PATCH /mail_settings/spam_check # - -data = { - "enabled": True, - "max_score": 5, - "url": "url" -} -response = sg.client.mail_settings.spam_check.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve spam check mail settings # -# GET /mail_settings/spam_check # - -response = sg.client.mail_settings.spam_check.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update template mail settings # -# PATCH /mail_settings/template # - -data = { - "enabled": True, - "html_content": "<% body %>" -} -response = sg.client.mail_settings.template.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve legacy template mail settings # -# GET /mail_settings/template # - -response = sg.client.mail_settings.template.get() -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/partnersettings/partnersettings.py b/docker/examples/partnersettings/partnersettings.py deleted file mode 100644 index 37f77f4e6..000000000 --- a/docker/examples/partnersettings/partnersettings.py +++ /dev/null @@ -1,40 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Returns a list of all partner settings. # -# GET /partner_settings # - -params = {'limit': 1, 'offset': 1} -response = sg.client.partner_settings.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Updates New Relic partner settings. # -# PATCH /partner_settings/new_relic # - -data = { - "enable_subuser_statistics": True, - "enabled": True, - "license_key": "" -} -response = sg.client.partner_settings.new_relic.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Returns all New Relic partner settings. # -# GET /partner_settings/new_relic # - -response = sg.client.partner_settings.new_relic.get() -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/scopes/scopes.py b/docker/examples/scopes/scopes.py deleted file mode 100644 index 124f77d39..000000000 --- a/docker/examples/scopes/scopes.py +++ /dev/null @@ -1,16 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Retrieve a list of scopes for which this user has access. # -# GET /scopes # - -response = sg.client.scopes.get() -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/senders/senders.py b/docker/examples/senders/senders.py deleted file mode 100644 index f21459b71..000000000 --- a/docker/examples/senders/senders.py +++ /dev/null @@ -1,99 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Create a Sender Identity # -# POST /senders # - -data = { - "address": "123 Elm St.", - "address_2": "Apt. 456", - "city": "Denver", - "country": "United States", - "from": { - "email": "from@example.com", - "name": "Example INC" - }, - "nickname": "My Sender ID", - "reply_to": { - "email": "replyto@example.com", - "name": "Example INC" - }, - "state": "Colorado", - "zip": "80202" -} -response = sg.client.senders.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Get all Sender Identities # -# GET /senders # - -response = sg.client.senders.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update a Sender Identity # -# PATCH /senders/{sender_id} # - -data = { - "address": "123 Elm St.", - "address_2": "Apt. 456", - "city": "Denver", - "country": "United States", - "from": { - "email": "from@example.com", - "name": "Example INC" - }, - "nickname": "My Sender ID", - "reply_to": { - "email": "replyto@example.com", - "name": "Example INC" - }, - "state": "Colorado", - "zip": "80202" -} -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) - -################################################## -# View a Sender Identity # -# GET /senders/{sender_id} # - -sender_id = "test_url_param" -response = sg.client.senders._(sender_id).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete a Sender Identity # -# DELETE /senders/{sender_id} # - -sender_id = "test_url_param" -response = sg.client.senders._(sender_id).delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Resend Sender Identity Verification # -# POST /senders/{sender_id}/resend_verification # - -sender_id = "test_url_param" -response = sg.client.senders._(sender_id).resend_verification.post() -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/stats/stats.py b/docker/examples/stats/stats.py deleted file mode 100644 index a7bf3362e..000000000 --- a/docker/examples/stats/stats.py +++ /dev/null @@ -1,17 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Retrieve global email statistics # -# GET /stats # - -params = {'aggregated_by': 'day', 'limit': 1, 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} -response = sg.client.stats.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/subusers/subusers.py b/docker/examples/subusers/subusers.py deleted file mode 100644 index 6aa91e535..000000000 --- a/docker/examples/subusers/subusers.py +++ /dev/null @@ -1,170 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Create Subuser # -# POST /subusers # - -data = { - "email": "John@example.com", - "ips": [ - "1.1.1.1", - "2.2.2.2" - ], - "password": "johns_password", - "username": "John@example.com" -} -response = sg.client.subusers.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# List all Subusers # -# GET /subusers # - -params = {'username': 'test_string', 'limit': 1, 'offset': 1} -response = sg.client.subusers.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve Subuser Reputations # -# GET /subusers/reputations # - -params = {'usernames': 'test_string'} -response = sg.client.subusers.reputations.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve email statistics for your subusers. # -# GET /subusers/stats # - -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01', 'subusers': 'test_string'} -response = sg.client.subusers.stats.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve monthly stats for all subusers # -# GET /subusers/stats/monthly # - -params = {'subuser': 'test_string', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'date': 'test_string', 'sort_by_direction': 'asc'} -response = sg.client.subusers.stats.monthly.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve the totals for each email statistic metric for all subusers. # -# GET /subusers/stats/sums # - -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} -response = sg.client.subusers.stats.sums.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Enable/disable a subuser # -# PATCH /subusers/{subuser_name} # - -data = { - "disabled": False -} -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete a subuser # -# DELETE /subusers/{subuser_name} # - -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update IPs assigned to a subuser # -# PUT /subusers/{subuser_name}/ips # - -data = [ - "127.0.0.1" -] -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).ips.put(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update Monitor Settings for a subuser # -# PUT /subusers/{subuser_name}/monitor # - -data = { - "email": "example@example.com", - "frequency": 500 -} -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.put(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Create monitor settings # -# POST /subusers/{subuser_name}/monitor # - -data = { - "email": "example@example.com", - "frequency": 50000 -} -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve monitor settings for a subuser # -# GET /subusers/{subuser_name}/monitor # - -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete monitor settings # -# DELETE /subusers/{subuser_name}/monitor # - -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve the monthly email statistics for a single subuser # -# GET /subusers/{subuser_name}/stats/monthly # - -params = {'date': 'test_string', 'sort_by_direction': 'asc', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1} -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) - diff --git a/docker/examples/suppression/suppression.py b/docker/examples/suppression/suppression.py deleted file mode 100644 index abdaef76d..000000000 --- a/docker/examples/suppression/suppression.py +++ /dev/null @@ -1,202 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Retrieve all blocks # -# GET /suppression/blocks # - -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) - -################################################## -# Delete blocks # -# DELETE /suppression/blocks # - -data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] -} -response = sg.client.suppression.blocks.delete(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve a specific block # -# GET /suppression/blocks/{email} # - -email = "test_url_param" -response = sg.client.suppression.blocks._(email).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete a specific block # -# DELETE /suppression/blocks/{email} # - -email = "test_url_param" -response = sg.client.suppression.blocks._(email).delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all bounces # -# GET /suppression/bounces # - -params = {'start_time': 1, 'end_time': 1} -response = sg.client.suppression.bounces.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete bounces # -# DELETE /suppression/bounces # - -data = { - "delete_all": True, - "emails": [ - "example@example.com", - "example2@example.com" - ] -} -response = sg.client.suppression.bounces.delete(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve a Bounce # -# GET /suppression/bounces/{email} # - -email = "test_url_param" -response = sg.client.suppression.bounces._(email).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete a bounce # -# DELETE /suppression/bounces/{email} # - -params = {'email_address': 'example@example.com'} -email = "test_url_param" -response = sg.client.suppression.bounces._(email).delete(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all invalid emails # -# GET /suppression/invalid_emails # - -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) - -################################################## -# Delete invalid emails # -# DELETE /suppression/invalid_emails # - -data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] -} -response = sg.client.suppression.invalid_emails.delete(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve a specific invalid email # -# GET /suppression/invalid_emails/{email} # - -email = "test_url_param" -response = sg.client.suppression.invalid_emails._(email).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete a specific invalid email # -# DELETE /suppression/invalid_emails/{email} # - -email = "test_url_param" -response = sg.client.suppression.invalid_emails._(email).delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve a specific spam report # -# GET /suppression/spam_report/{email} # - -email = "test_url_param" -response = sg.client.suppression.spam_report._(email).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete a specific spam report # -# DELETE /suppression/spam_report/{email} # - -email = "test_url_param" -response = sg.client.suppression.spam_report._(email).delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all spam reports # -# GET /suppression/spam_reports # - -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) - -################################################## -# Delete spam reports # -# DELETE /suppression/spam_reports # - -data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] -} -response = sg.client.suppression.spam_reports.delete(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all global suppressions # -# GET /suppression/unsubscribes # - -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) - diff --git a/docker/examples/templates/templates.py b/docker/examples/templates/templates.py deleted file mode 100644 index 9d3d5dd4b..000000000 --- a/docker/examples/templates/templates.py +++ /dev/null @@ -1,130 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Create a transactional template. # -# POST /templates # - -data = { - "name": "example_name" -} -response = sg.client.templates.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all transactional templates. # -# GET /templates # - -response = sg.client.templates.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Edit a transactional template. # -# PATCH /templates/{template_id} # - -data = { - "name": "new_example_name" -} -template_id = "test_url_param" -response = sg.client.templates._(template_id).patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve a single transactional template. # -# GET /templates/{template_id} # - -template_id = "test_url_param" -response = sg.client.templates._(template_id).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete a template. # -# DELETE /templates/{template_id} # - -template_id = "test_url_param" -response = sg.client.templates._(template_id).delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Create a new transactional template version. # -# POST /templates/{template_id}/versions # - -data = { - "active": 1, - "html_content": "<%body%>", - "name": "example_version_name", - "plain_content": "<%body%>", - "subject": "<%subject%>", - "template_id": "ddb96bbc-9b92-425e-8979-99464621b543" -} -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) - -################################################## -# Edit a transactional template version. # -# PATCH /templates/{template_id}/versions/{version_id} # - -data = { - "active": 1, - "html_content": "<%body%>", - "name": "updated_example_name", - "plain_content": "<%body%>", - "subject": "<%subject%>" -} -template_id = "test_url_param" -version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve a specific transactional template version. # -# GET /templates/{template_id}/versions/{version_id} # - -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) - -################################################## -# Delete a transactional template version. # -# DELETE /templates/{template_id}/versions/{version_id} # - -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) - -################################################## -# Activate a transactional template version. # -# POST /templates/{template_id}/versions/{version_id}/activate # - -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) - diff --git a/docker/examples/trackingsettings/trackingsettings.py b/docker/examples/trackingsettings/trackingsettings.py deleted file mode 100644 index 80dbe243a..000000000 --- a/docker/examples/trackingsettings/trackingsettings.py +++ /dev/null @@ -1,111 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Retrieve Tracking Settings # -# GET /tracking_settings # - -params = {'limit': 1, 'offset': 1} -response = sg.client.tracking_settings.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update Click Tracking Settings # -# PATCH /tracking_settings/click # - -data = { - "enabled": True -} -response = sg.client.tracking_settings.click.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve Click Track Settings # -# GET /tracking_settings/click # - -response = sg.client.tracking_settings.click.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update Google Analytics Settings # -# PATCH /tracking_settings/google_analytics # - -data = { - "enabled": True, - "utm_campaign": "website", - "utm_content": "", - "utm_medium": "email", - "utm_source": "sendgrid.com", - "utm_term": "" -} -response = sg.client.tracking_settings.google_analytics.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve Google Analytics Settings # -# GET /tracking_settings/google_analytics # - -response = sg.client.tracking_settings.google_analytics.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update Open Tracking Settings # -# PATCH /tracking_settings/open # - -data = { - "enabled": True -} -response = sg.client.tracking_settings.open.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Get Open Tracking Settings # -# GET /tracking_settings/open # - -response = sg.client.tracking_settings.open.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update Subscription Tracking Settings # -# PATCH /tracking_settings/subscription # - -data = { - "enabled": True, - "html_content": "html content", - "landing": "landing page html", - "plain_content": "text content", - "replace": "replacement tag", - "url": "url" -} -response = sg.client.tracking_settings.subscription.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve Subscription Tracking Settings # -# GET /tracking_settings/subscription # - -response = sg.client.tracking_settings.subscription.get() -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/user/user.py b/docker/examples/user/user.py deleted file mode 100644 index 9e3f24766..000000000 --- a/docker/examples/user/user.py +++ /dev/null @@ -1,294 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Get a user's account information. # -# GET /user/account # - -response = sg.client.user.account.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve your credit balance # -# GET /user/credits # - -response = sg.client.user.credits.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update your account email address # -# PUT /user/email # - -data = { - "email": "example@example.com" -} -response = sg.client.user.email.put(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve your account email address # -# GET /user/email # - -response = sg.client.user.email.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update your password # -# PUT /user/password # - -data = { - "new_password": "new_password", - "old_password": "old_password" -} -response = sg.client.user.password.put(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update a user's profile # -# PATCH /user/profile # - -data = { - "city": "Orange", - "first_name": "Example", - "last_name": "User" -} -response = sg.client.user.profile.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Get a user's profile # -# GET /user/profile # - -response = sg.client.user.profile.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Cancel or pause a scheduled send # -# POST /user/scheduled_sends # - -data = { - "batch_id": "YOUR_BATCH_ID", - "status": "pause" -} -response = sg.client.user.scheduled_sends.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all scheduled sends # -# GET /user/scheduled_sends # - -response = sg.client.user.scheduled_sends.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update user scheduled send information # -# PATCH /user/scheduled_sends/{batch_id} # - -data = { - "status": "pause" -} -batch_id = "test_url_param" -response = sg.client.user.scheduled_sends._(batch_id).patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve scheduled send # -# GET /user/scheduled_sends/{batch_id} # - -batch_id = "test_url_param" -response = sg.client.user.scheduled_sends._(batch_id).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete a cancellation or pause of a scheduled send # -# DELETE /user/scheduled_sends/{batch_id} # - -batch_id = "test_url_param" -response = sg.client.user.scheduled_sends._(batch_id).delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update Enforced TLS settings # -# PATCH /user/settings/enforced_tls # - -data = { - "require_tls": True, - "require_valid_cert": False -} -response = sg.client.user.settings.enforced_tls.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve current Enforced TLS settings. # -# GET /user/settings/enforced_tls # - -response = sg.client.user.settings.enforced_tls.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update your username # -# PUT /user/username # - -data = { - "username": "test_username" -} -response = sg.client.user.username.put(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve your username # -# GET /user/username # - -response = sg.client.user.username.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update Event Notification Settings # -# PATCH /user/webhooks/event/settings # - -data = { - "bounce": True, - "click": True, - "deferred": True, - "delivered": True, - "dropped": True, - "enabled": True, - "group_resubscribe": True, - "group_unsubscribe": True, - "open": True, - "processed": True, - "spam_report": True, - "unsubscribe": True, - "url": "url" -} -response = sg.client.user.webhooks.event.settings.patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve Event Webhook settings # -# GET /user/webhooks/event/settings # - -response = sg.client.user.webhooks.event.settings.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Test Event Notification Settings # -# POST /user/webhooks/event/test # - -data = { - "url": "url" -} -response = sg.client.user.webhooks.event.test.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Create a parse setting # -# POST /user/webhooks/parse/settings # - -data = { - "hostname": "myhostname.com", - "send_raw": False, - "spam_check": True, - "url": "http://email.myhosthame.com" -} -response = sg.client.user.webhooks.parse.settings.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all parse settings # -# GET /user/webhooks/parse/settings # - -response = sg.client.user.webhooks.parse.settings.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update a parse setting # -# PATCH /user/webhooks/parse/settings/{hostname} # - -data = { - "send_raw": True, - "spam_check": False, - "url": "http://newdomain.com/parse" -} -hostname = "test_url_param" -response = sg.client.user.webhooks.parse.settings._(hostname).patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve a specific parse setting # -# GET /user/webhooks/parse/settings/{hostname} # - -hostname = "test_url_param" -response = sg.client.user.webhooks.parse.settings._(hostname).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete a parse setting # -# DELETE /user/webhooks/parse/settings/{hostname} # - -hostname = "test_url_param" -response = sg.client.user.webhooks.parse.settings._(hostname).delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieves Inbound Parse Webhook statistics. # -# GET /user/webhooks/parse/stats # - -params = {'aggregated_by': 'day', 'limit': 'test_string', 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 'test_string'} -response = sg.client.user.webhooks.parse.stats.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - diff --git a/docker/examples/whitelabel/whitelabel.py b/docker/examples/whitelabel/whitelabel.py deleted file mode 100644 index f529d3ed2..000000000 --- a/docker/examples/whitelabel/whitelabel.py +++ /dev/null @@ -1,311 +0,0 @@ -import sendgrid -import json -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - -################################################## -# Create a domain whitelabel. # -# POST /whitelabel/domains # - -data = { - "automatic_security": False, - "custom_spf": True, - "default": True, - "domain": "example.com", - "ips": [ - "192.168.1.1", - "192.168.1.2" - ], - "subdomain": "news", - "username": "john@example.com" -} -response = sg.client.whitelabel.domains.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# List all domain whitelabels. # -# GET /whitelabel/domains # - -params = {'username': 'test_string', 'domain': 'test_string', 'exclude_subusers': 'true', 'limit': 1, 'offset': 1} -response = sg.client.whitelabel.domains.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Get the default domain whitelabel. # -# GET /whitelabel/domains/default # - -response = sg.client.whitelabel.domains.default.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# List the domain whitelabel associated with the given user. # -# GET /whitelabel/domains/subuser # - -response = sg.client.whitelabel.domains.subuser.get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Disassociate a domain whitelabel from a given user. # -# DELETE /whitelabel/domains/subuser # - -response = sg.client.whitelabel.domains.subuser.delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update a domain whitelabel. # -# PATCH /whitelabel/domains/{domain_id} # - -data = { - "custom_spf": True, - "default": False -} -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) - -################################################## -# Retrieve a domain whitelabel. # -# GET /whitelabel/domains/{domain_id} # - -domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete a domain whitelabel. # -# DELETE /whitelabel/domains/{domain_id} # - -domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Associate a domain whitelabel with a given user. # -# POST /whitelabel/domains/{domain_id}/subuser # - -data = { - "username": "jane@example.com" -} -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) - -################################################## -# Add an IP to a domain whitelabel. # -# POST /whitelabel/domains/{id}/ips # - -data = { - "ip": "192.168.0.1" -} -id = "test_url_param" -response = sg.client.whitelabel.domains._(id).ips.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Remove an IP from a domain whitelabel. # -# DELETE /whitelabel/domains/{id}/ips/{ip} # - -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) - -################################################## -# Validate a domain whitelabel. # -# POST /whitelabel/domains/{id}/validate # - -id = "test_url_param" -response = sg.client.whitelabel.domains._(id).validate.post() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Create an IP whitelabel # -# POST /whitelabel/ips # - -data = { - "domain": "example.com", - "ip": "192.168.1.1", - "subdomain": "email" -} -response = sg.client.whitelabel.ips.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all IP whitelabels # -# GET /whitelabel/ips # - -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) - -################################################## -# Retrieve an IP whitelabel # -# GET /whitelabel/ips/{id} # - -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete an IP whitelabel # -# DELETE /whitelabel/ips/{id} # - -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Validate an IP whitelabel # -# POST /whitelabel/ips/{id}/validate # - -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).validate.post() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Create a Link Whitelabel # -# POST /whitelabel/links # - -data = { - "default": True, - "domain": "example.com", - "subdomain": "mail" -} -params = {'limit': 1, 'offset': 1} -response = sg.client.whitelabel.links.post(request_body=data, query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve all link whitelabels # -# GET /whitelabel/links # - -params = {'limit': 1} -response = sg.client.whitelabel.links.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve a Default Link Whitelabel # -# GET /whitelabel/links/default # - -params = {'domain': 'test_string'} -response = sg.client.whitelabel.links.default.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve Associated Link Whitelabel # -# GET /whitelabel/links/subuser # - -params = {'username': 'test_string'} -response = sg.client.whitelabel.links.subuser.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Disassociate a Link Whitelabel # -# DELETE /whitelabel/links/subuser # - -params = {'username': 'test_string'} -response = sg.client.whitelabel.links.subuser.delete(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Update a Link Whitelabel # -# PATCH /whitelabel/links/{id} # - -data = { - "default": True -} -id = "test_url_param" -response = sg.client.whitelabel.links._(id).patch(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Retrieve a Link Whitelabel # -# GET /whitelabel/links/{id} # - -id = "test_url_param" -response = sg.client.whitelabel.links._(id).get() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Delete a Link Whitelabel # -# DELETE /whitelabel/links/{id} # - -id = "test_url_param" -response = sg.client.whitelabel.links._(id).delete() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Validate a Link Whitelabel # -# POST /whitelabel/links/{id}/validate # - -id = "test_url_param" -response = sg.client.whitelabel.links._(id).validate.post() -print(response.status_code) -print(response.body) -print(response.headers) - -################################################## -# Associate a Link Whitelabel # -# POST /whitelabel/links/{link_id}/subuser # - -data = { - "username": "jane@example.com" -} -link_id = "test_url_param" -response = sg.client.whitelabel.links._(link_id).subuser.post(request_body=data) -print(response.status_code) -print(response.body) -print(response.headers) - From 7bde27aed7845908bb2b6a799aa366d4f0c64a32 Mon Sep 17 00:00:00 2001 From: mbernier Date: Thu, 21 Dec 2017 14:14:38 -0700 Subject: [PATCH 054/462] fixed tests for docker files --- test/test_project.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_project.py b/test/test_project.py index 861f1ffe0..e5f05de36 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -13,11 +13,11 @@ def test_docker_dir(self): # ./docker-compose.yml or ./docker/docker-compose.yml def test_docker_compose(self): - self.assertTrue(os.path.isfile('docker-compose.yml')) + self.assertTrue(os.path.isfile('./docker/docker-compose.yml')) # ./.env_sample def test_env(self): - self.assertTrue(os.path.isfile('./env_sample')) + self.assertTrue(os.path.isfile('./.env_sample')) # ./.gitignore def test_gitignore(self): From 0a41ea458a46cb2f5dd487a8e879303bc2fa2a85 Mon Sep 17 00:00:00 2001 From: Nino Date: Mon, 19 Feb 2018 00:04:50 +0100 Subject: [PATCH 055/462] Ensure params are applied independently --- sendgrid/helpers/mail/mail.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 116afb46e..7e28fc965 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -41,12 +41,18 @@ def __init__( self._custom_args = [] # Minimum required to send an email - if from_email and subject and to_email and content: + if from_email: self.from_email = from_email + + if subject: self.subject = subject + + if to_email: personalization = Personalization() personalization.add_to(to_email) self.add_personalization(personalization) + + if content: self.add_content(content) def __str__(self): From 299ad1b2a08332053b6ef39f039fb66003de6640 Mon Sep 17 00:00:00 2001 From: Viktor Kharkovets <3lnc.slam@gmail.com> Date: Tue, 20 Feb 2018 12:22:03 +0200 Subject: [PATCH 056/462] Bump up year --- LICENSE.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 73b58cfa9..69511d70c 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2012-2017 SendGrid, Inc. +Copyright (c) 2012-2018 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 @@ -12,4 +12,4 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. \ No newline at end of file +DEALINGS IN THE SOFTWARE. From f2f0d1a0911e69e93d7e29ff045ffe3b2dacbba9 Mon Sep 17 00:00:00 2001 From: Viktor Kharkovets <3lnc.slam@gmail.com> Date: Fri, 23 Feb 2018 16:49:01 +0200 Subject: [PATCH 057/462] Adds explicit signature --- sendgrid/sendgrid.py | 29 ++++++++++++++--------------- test/test_sendgrid.py | 6 +++--- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index b6c731b94..831da6e3e 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -32,7 +32,13 @@ class SendGridAPIClient(object): https://github.com/sendgrid/sendgrid-python """ - def __init__(self, **opts): + def __init__( + self, + apikey=None, + api_key=None, + impersonate_subuser=None, + host='https://api.sendgrid.com', + **opts): """ Construct SendGrid v3 API object. @@ -41,25 +47,18 @@ def __init__(self, **opts): :params apikey: SendGrid API key to use. Defaults to environment var. :type apikey: string """ - self.path = opts.get( - 'path', os.path.abspath(os.path.dirname(__file__))) - self._apikey = opts.get('apikey', os.environ.get('SENDGRID_API_KEY')) - # Support v2 api_key naming - self._apikey = opts.get('api_key', self._apikey) - self._api_key = self._apikey - # Support impersonation of subusers - self._impersonate_subuser = opts.get('impersonate_subuser', None) + self._apikey = apikey or 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.host = opts.get('host', 'https://api.sendgrid.com') self.version = __version__ - headers = self._get_default_headers() - self.client = python_http_client.Client(host=self.host, - request_headers=headers, + request_headers=self._default_headers, version=3) - def _get_default_headers(self): + @property + def _default_headers(self): headers = { "Authorization": 'Bearer {0}'.format(self._apikey), "User-agent": self.useragent, @@ -71,7 +70,7 @@ def _get_default_headers(self): return headers def reset_request_headers(self): - self.client.request_headers = self._get_default_headers() + self.client.request_headers = self._default_headers @property def apikey(self): diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index d095d8be2..2f412e696 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -110,14 +110,14 @@ def test_host(self): self.assertEqual(self.sg.host, self.host) def test_get_default_headers(self): - headers = self.sg._get_default_headers() + headers = self.sg._default_headers self.assertIn('Authorization', headers) self.assertIn('User-agent', headers) self.assertIn('Accept', headers) self.assertNotIn('On-Behalf-Of', headers) self.sg._impersonate_subuser = 'ladida@testsubuser.sendgrid' - headers = self.sg._get_default_headers() + headers = self.sg._default_headers self.assertIn('Authorization', headers) self.assertIn('User-agent', headers) self.assertIn('Accept', headers) @@ -136,7 +136,7 @@ def test_reset_request_headers(self): self.assertNotIn('blah', self.sg.client.request_headers) self.assertNotIn('blah2x', self.sg.client.request_headers) - for k,v in self.sg._get_default_headers().items(): + for k,v in self.sg._default_headers.items(): self.assertEqual(v, self.sg.client.request_headers[k]) def test_hello_world(self): From ba890b21d5dd48b46c68c39207c5376bf3ba4c64 Mon Sep 17 00:00:00 2001 From: Viktor Kharkovets <3lnc.slam@gmail.com> Date: Fri, 23 Feb 2018 17:48:36 +0200 Subject: [PATCH 058/462] Adds docstring --- sendgrid/sendgrid.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index 831da6e3e..e572cde48 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -37,15 +37,23 @@ def __init__( apikey=None, api_key=None, impersonate_subuser=None, - host='https://api.sendgrid.com', - **opts): + host='https://api.sendgrid.com'): """ Construct SendGrid v3 API object. - :params host: Base URL for the API call - :type host: string - :params apikey: SendGrid API key to use. Defaults to environment var. - :type apikey: string + :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 + :param host: base URL for API calls + :type host: basestring """ self._apikey = apikey or api_key or os.environ.get('SENDGRID_API_KEY') self._impersonate_subuser = impersonate_subuser From 7e507013bf424937cff8425c4faff702937cf032 Mon Sep 17 00:00:00 2001 From: Viktor Kharkovets <3lnc.slam@gmail.com> Date: Fri, 23 Feb 2018 18:16:03 +0200 Subject: [PATCH 059/462] Makes various attrs public --- sendgrid/sendgrid.py | 40 ++++++++++++++-------------------------- test/test_sendgrid.py | 9 +++------ 2 files changed, 17 insertions(+), 32 deletions(-) diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index e572cde48..d6b0c12d4 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -40,6 +40,8 @@ def __init__( host='https://api.sendgrid.com'): """ 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" @@ -55,8 +57,8 @@ def __init__( :param host: base URL for API calls :type host: basestring """ - self._apikey = apikey or api_key or os.environ.get('SENDGRID_API_KEY') - self._impersonate_subuser = impersonate_subuser + self.apikey = apikey or 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__ @@ -68,41 +70,27 @@ def __init__( @property def _default_headers(self): headers = { - "Authorization": 'Bearer {0}'.format(self._apikey), + "Authorization": 'Bearer {0}'.format(self.apikey), "User-agent": self.useragent, "Accept": 'application/json' } - if self._impersonate_subuser: - headers['On-Behalf-Of'] = self._impersonate_subuser + if self.impersonate_subuser: + headers['On-Behalf-Of'] = self.impersonate_subuser return headers def reset_request_headers(self): self.client.request_headers = self._default_headers - @property - def apikey(self): - """The API key (also accessible as api_key).""" - return self._apikey - - @apikey.setter - def apikey(self, value): - self._apikey = value - @property def api_key(self): - """The API key (also accessible as apikey).""" - return self._apikey + """ + Alias for reading API key + .. deprecated:: 5.3 + Use apikey instead + """ + return self.apikey @api_key.setter def api_key(self, value): - self._apikey = value - - @property - def impersonate_subuser(self): - """ - The subuser you are impersonating. - - If present, this is the value of the "On-Behalf-Of" header. - """ - return self._impersonate_subuser + self.apikey = value diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index 2f412e696..67c0ec6be 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -22,9 +22,7 @@ def setUpClass(cls): cls.path = '{0}{1}'.format( os.path.abspath( os.path.dirname(__file__)), '/..') - cls.sg = sendgrid.SendGridAPIClient( - host=host, path=cls.path, - api_key=os.environ.get('SENDGRID_API_KEY')) + cls.sg = sendgrid.SendGridAPIClient(host=host) cls.devnull = open(os.devnull, 'w') prism_cmd = None try: @@ -97,8 +95,7 @@ def test_api_key_setter(self): def test_impersonate_subuser_init(self): temp_subuser = 'abcxyz@this.is.a.test.subuser' sg_impersonate = sendgrid.SendGridAPIClient( - host=host, path=self.path, - api_key=os.environ.get('SENDGRID_API_KEY'), + host=host, impersonate_subuser=temp_subuser) self.assertEqual(sg_impersonate.impersonate_subuser, temp_subuser) @@ -116,7 +113,7 @@ def test_get_default_headers(self): self.assertIn('Accept', headers) self.assertNotIn('On-Behalf-Of', headers) - self.sg._impersonate_subuser = 'ladida@testsubuser.sendgrid' + self.sg.impersonate_subuser = 'ladida@testsubuser.sendgrid' headers = self.sg._default_headers self.assertIn('Authorization', headers) self.assertIn('User-agent', headers) From 840a0343c11004704925f127b3aa765ec63c676f Mon Sep 17 00:00:00 2001 From: Viktor Kharkovets <3lnc.slam@gmail.com> Date: Wed, 28 Feb 2018 14:44:24 +0200 Subject: [PATCH 060/462] Adds backward-compatible **kw dispatching with DeprecationWarning --- sendgrid/sendgrid.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index d6b0c12d4..0f09bd542 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -14,6 +14,8 @@ import os +import warnings + import python_http_client from .version import __version__ @@ -37,7 +39,8 @@ def __init__( apikey=None, api_key=None, impersonate_subuser=None, - host='https://api.sendgrid.com'): + host='https://api.sendgrid.com', + **opts): # TODO: remove **opts for 6.x release """ Construct SendGrid v3 API object. Note that underlying client being set up during initialization, therefore changing @@ -56,7 +59,13 @@ def __init__( :type impersonate_subuser: basestring :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 """ + 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') self.impersonate_subuser = impersonate_subuser self.host = host From e5c55ab3ac901a478e7fc4afe1034ce0fc7c227a Mon Sep 17 00:00:00 2001 From: Chao Date: Sun, 25 Mar 2018 16:01:24 -0400 Subject: [PATCH 061/462] fix examples to use the correct method --- examples/helpers/mail/mail_example.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/helpers/mail/mail_example.py b/examples/helpers/mail/mail_example.py index bfd8ea718..a36cefbf6 100644 --- a/examples/helpers/mail/mail_example.py +++ b/examples/helpers/mail/mail_example.py @@ -27,10 +27,10 @@ def build_personalization(personalization): personalization.add_to(to_addr) for cc_addr in personalization['cc_list']: - personalization.add_to(cc_addr) + personalization.add_cc(cc_addr) for bcc_addr in personalization['bcc_list']: - personalization.add_bc(bcc_addr) + personalization.add_bcc(bcc_addr) for header in personalization['headers']: personalization.add_header(header) From 734dce070dd7406d6c4314e31fa2df6533786136 Mon Sep 17 00:00:00 2001 From: Ian Beck Date: Mon, 16 Apr 2018 13:53:30 -0700 Subject: [PATCH 062/462] Make all Mail helper parameters truly optional Closes #569. This is a stop-gap measure prior to the Mail helper being rewritten for a future update; the API ultimately returns errors if an element isn't specified regardless, so this makes the initialization a little more flexible (allows specifying a `to_email` at initialization to take advantage of automatic Personalization creation while still specifying the content later, for instance). --- sendgrid/helpers/mail/mail.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 116afb46e..6024fd05b 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -12,7 +12,7 @@ def __init__( self, from_email=None, subject=None, to_email=None, content=None): """Create a Mail object. - If parameters are supplied, all parameters must be present. + 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. @@ -40,13 +40,15 @@ def __init__( self._categories = [] self._custom_args = [] - # Minimum required to send an email - if from_email and subject and to_email and content: + if from_email: self.from_email = from_email + if subject: self.subject = subject + if to_email: personalization = Personalization() personalization.add_to(to_email) self.add_personalization(personalization) + if content: self.add_content(content) def __str__(self): From 5266e7bda9ddc8cc1736c524aaa5738ed3edc182 Mon Sep 17 00:00:00 2001 From: Audrey Date: Mon, 14 May 2018 15:44:33 -0400 Subject: [PATCH 063/462] Add link to additional resource Expand on instructions to create API key by linking to SendGrid documentation. --- USE_CASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/USE_CASES.md b/USE_CASES.md index dd42994b0..781c09b8f 100644 --- a/USE_CASES.md +++ b/USE_CASES.md @@ -541,7 +541,7 @@ Go ahead and clone the Git repository link after it is created. You may need to 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. +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 On the next menu, you have the option to choose what programming language you'll be using. The obvious choice for this tutorial will be Python. From 213052b81a540364110c6368afd3bb1bf7de9371 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Fri, 25 May 2018 09:58:31 -0700 Subject: [PATCH 064/462] Fix Tests --- .gitignore | 1 + .travis.yml | 4 +- LICENSE.txt | 2 +- docker-test/Dockerfile | 23 +++++++++ docker-test/README.md | 47 +++++++++++++++++++ docker-test/entrypoint.sh | 17 +++++++ docker/Dockerfile | 6 +++ test/test_project.py | 16 ++++--- test/test_sendgrid.py | 98 +++++++++++++++++++-------------------- 9 files changed, 156 insertions(+), 58 deletions(-) create mode 100644 docker-test/Dockerfile create mode 100644 docker-test/README.md create mode 100644 docker-test/entrypoint.sh diff --git a/.gitignore b/.gitignore index de9602419..96232a5bf 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,5 @@ coverage.xml htmlcov temp*.py sendgrid.env +.vscode diff --git a/.travis.yml b/.travis.yml index 4aceaa352..b52edb9c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,8 +21,8 @@ addons: apt_packages: - pandoc before_script: -- . ./test/prism.sh -- prism version +- "./test/prism.sh &" +- sleep 20 script: - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then coverage run -m unittest2 discover; else coverage run -m unittest discover; fi after_script: diff --git a/LICENSE.txt b/LICENSE.txt index 73b58cfa9..8fc986624 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2012-2017 SendGrid, Inc. +Copyright (c) 2012-2018 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/docker-test/Dockerfile b/docker-test/Dockerfile new file mode 100644 index 000000000..c17d790d6 --- /dev/null +++ b/docker-test/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.6-alpine + +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 + +# set up default sendgrid env +WORKDIR /root + +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 new file mode 100644 index 000000000..d44ebf759 --- /dev/null +++ b/docker-test/README.md @@ -0,0 +1,47 @@ +Use Docker to easily try out or contribute to the sendgrid-python library. + +This Docker image contains: + - Python 3.6 + - A running instance of [Stoplight.io's Prism](https://stoplight.io/platform/prism/), which lets you try out the SendGrid API without actually sending email + - A mirrored copy of sendgrid-php so that you may develop locally and then run the tests within the Docker container. + +# Table of Contents + +* [Quick Start](#quick-start) +* [Testing](#testing) +* [Contributing](#contributing) + + +# Quick Start + +1. Clone the sendgrid-python repo + - `git clone https://github.com/sendgrid/sendgrid-python.git` + - `cd sendgrid-python` + - `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 + - `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 + - `sudo docker exec -it sendgrid_python3.6 /bin/bash -c 'cd sendgrid-python; python3.6 -m unittest discover -v; exec "${SHELL:-sh}"'` + +Now you can continue development locally, and run `python3.6 -m unittest discover -v` inside of the container to test. + +To clean up the container: `docker stop sendgrid_python3.6 && docker rm sendgrid_python3.6`. + +Happy Hacking! + + +# For Testing the Library (Kick the Tires) + +- After step 5 in the QuickStart, within the Docker container: + - `cd ../` + - `python sendmail.py` + + +# For Contributors + +- Develop per usual locally, but before pushing up to GitHub, you can run the tests locally in the Docker container per step 5 of the quickstart. +- To run all the tests: `python3.6 -m unittest discover -v` +- To run an individual test: `python3.6 -m unittest [Filename].[Class].[TestName]` diff --git a/docker-test/entrypoint.sh b/docker-test/entrypoint.sh new file mode 100644 index 000000000..f64d0cccb --- /dev/null +++ b/docker-test/entrypoint.sh @@ -0,0 +1,17 @@ +#! /bin/bash +clear + +if [ "$1" != "--no-mock" ] +then + echo "Starting Prism in mock mode. Calls made to Prism will not actually send emails." + echo "Disable this by running this container with --no-mock." + prism run --mock --spec $OAI_SPEC_URL 2> /dev/null & +else + echo "Starting Prism in live (--no-mock) mode. Calls made to Prism will send emails." + prism run --spec $OAI_SPEC_URL 2> /dev/null & +fi + +cd sendgrid-python +python3.6 setup.py install +pip install pyyaml six werkzeug flask +exec $SHELL diff --git a/docker/Dockerfile b/docker/Dockerfile index bc4ce8e79..e891e497c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -28,6 +28,12 @@ RUN python2.7 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 + # set up default sendgrid env WORKDIR /root/sources RUN git clone https://github.com/sendgrid/sendgrid-python.git && \ diff --git a/test/test_project.py b/test/test_project.py index 861f1ffe0..8d719354e 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -7,17 +7,21 @@ class ProjectTests(unittest.TestCase): - # ./Docker or docker/Docker + # ./docker def test_docker_dir(self): - self.assertTrue(os.path.isdir("./docker/Dockerfile")) + self.assertTrue(os.path.isdir("./docker")) - # ./docker-compose.yml or ./docker/docker-compose.yml - def test_docker_compose(self): - self.assertTrue(os.path.isfile('docker-compose.yml')) + # ./docker-test + def test_docker_test_dir(self): + self.assertTrue(os.path.isdir("./docker-test")) + + # # ./docker-compose.yml or ./docker/docker-compose.yml + # def test_docker_compose(self): + # self.assertTrue(os.path.isfile('docker-compose.yml')) # ./.env_sample def test_env(self): - self.assertTrue(os.path.isfile('./env_sample')) + self.assertTrue(os.path.isfile('./.env_sample')) # ./.gitignore def test_gitignore(self): diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index d095d8be2..3b3e8e743 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -27,51 +27,51 @@ def setUpClass(cls): api_key=os.environ.get('SENDGRID_API_KEY')) 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") + # 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')) @@ -2375,7 +2375,7 @@ def test_license_year(self): copyright_line = f.readline().rstrip() self.assertEqual('Copyright (c) 2012-%s SendGrid, Inc.' % datetime.datetime.now().year, copyright_line) - @classmethod - def tearDownClass(cls): - cls.p.kill() - print("Prism Shut Down") + # @classmethod + # def tearDownClass(cls): + # cls.p.kill() + # print("Prism Shut Down") From 544fab77cefcab3c3dfb21ee23c648570389b750 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Fri, 25 May 2018 10:03:46 -0700 Subject: [PATCH 065/462] Travis Fix --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b52edb9c8..9705a57e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,8 +21,8 @@ addons: apt_packages: - pandoc before_script: -- "./test/prism.sh &" -- sleep 20 +- . ./test/prism.sh +- prism version script: - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then coverage run -m unittest2 discover; else coverage run -m unittest discover; fi after_script: @@ -46,7 +46,7 @@ notifications: template: - '%{repository} Build %{build_number} on branch %{branch} by %{author}: %{message} - View on + View on GitHub' format: html notify: false From 93fc8ce35f32124694536969033550216aaa6244 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Fri, 25 May 2018 10:07:47 -0700 Subject: [PATCH 066/462] Update Prism --- .travis.yml | 4 ++-- test/prism.sh | 27 ++++++++++++++++++--------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9705a57e9..ab4375d30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,8 +21,8 @@ addons: apt_packages: - pandoc before_script: -- . ./test/prism.sh -- prism version +- "./test/prism.sh &" +- sleep 20 script: - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then coverage run -m unittest2 discover; else coverage run -m unittest discover; fi after_script: diff --git a/test/prism.sh b/test/prism.sh index 9a37f7299..cc6a0bb15 100755 --- a/test/prism.sh +++ b/test/prism.sh @@ -1,8 +1,10 @@ #!/bin/bash +set -eu + install () { -set -eu +echo "Installing Prism..." UNAME=$(uname) ARCH=$(uname -m) @@ -25,25 +27,32 @@ elif [ "$UNAME" = "Linux" ] ; then 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" -DESTDIR=~/bin -DEST=$DESTDIR/prism +DEST=../prism/bin/prism if [ -z $LATEST ] ; then echo "Error requesting. Download binary from ${URL}" exit 1 else - mkdir -p $DESTDIR curl -L $URL -o $DEST chmod +x $DEST - export PATH=$PATH:$DESTDIR - prism version fi } -install +run () { + echo "Running prism..." + cd ../prism/bin + ./prism run --mock --spec https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/oai_stoplight.json +} -# this is needed for travis internal scripts -set +u \ No newline at end of file +if [ -f ../prism/bin/prism ]; then + echo "Prism is already installed." + run +else + echo "Prism is not installed." + install + run +fi \ No newline at end of file From b7108660caba1b734b8fa84775cc030950feca65 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 30 May 2018 14:50:39 -0700 Subject: [PATCH 067/462] Fix Tests --- test/test_project.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_project.py b/test/test_project.py index 8d719354e..7a4dc2171 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -71,9 +71,9 @@ def test_troubleshooting(self): def test_usage(self): self.assertTrue(os.path.isfile('./USAGE.md')) - # ./USE_CASES.md + # ./use-cases/README.md def test_use_cases(self): - self.assertTrue(os.path.isfile('./USE_CASES.md')) + self.assertTrue(os.path.isfile('./use-cases/README.md')) if __name__ == '__main__': unittest.main() From 13a7d7f01c941ca6e1db6df1f04a25b315b1ba81 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 30 May 2018 14:56:27 -0700 Subject: [PATCH 068/462] Typo --- test/test_project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_project.py b/test/test_project.py index 7a4dc2171..a762474ec 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -73,7 +73,7 @@ def test_usage(self): # ./use-cases/README.md def test_use_cases(self): - self.assertTrue(os.path.isfile('./use-cases/README.md')) + self.assertTrue(os.path.isfile('./use_cases/README.md')) if __name__ == '__main__': unittest.main() From b8285045c0bc25372db2240fccd3180ebf61d916 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 31 May 2018 11:03:30 -0700 Subject: [PATCH 069/462] Preparing for Merge --- sendgrid/helpers/mail/content.py | 2 ++ sendgrid/helpers/mail/header.py | 2 -- sendgrid/helpers/mail/mail.py | 2 -- sendgrid/helpers/mail/validators.py | 3 ++- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/sendgrid/helpers/mail/content.py b/sendgrid/helpers/mail/content.py index 2a6094ce2..cff8ac498 100644 --- a/sendgrid/helpers/mail/content.py +++ b/sendgrid/helpers/mail/content.py @@ -1,3 +1,5 @@ +from .validators import ValidateAPIKey + class Content(object): """Content to be included in your email. diff --git a/sendgrid/helpers/mail/header.py b/sendgrid/helpers/mail/header.py index 27f98563d..7c031465d 100644 --- a/sendgrid/helpers/mail/header.py +++ b/sendgrid/helpers/mail/header.py @@ -17,7 +17,6 @@ def __init__(self, key=None, value=None): """ self._key = None self._value = None - self._validator.validate_message_dict(value) if key is not None: self.key = key @@ -46,7 +45,6 @@ def value(self): @value.setter def value(self, value): - self._validator.validate_message_dict(value) self._value = value def get(self): diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 08a2d22c8..f43c8f9c8 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -35,7 +35,6 @@ def __init__(self, self._mail_settings = None self._tracking_settings = None self._reply_to = None - self._validator = ValidateAPIKey() self._personalizations = [] self._contents = [] self._attachments = [] @@ -153,7 +152,6 @@ def subject(self): @subject.setter def subject(self, value): - self._validator.validate_message_text(value) self._subject = value @property diff --git a/sendgrid/helpers/mail/validators.py b/sendgrid/helpers/mail/validators.py index 855c01f28..b4a69f697 100644 --- a/sendgrid/helpers/mail/validators.py +++ b/sendgrid/helpers/mail/validators.py @@ -1,3 +1,4 @@ +from .exceptions import APIKeyIncludedException ################################################################ # Various types of Validators ################################################################ @@ -65,6 +66,6 @@ def validate_message_text(self, message_string): if isinstance(message_string, str): for regex in self.regexes: - if regex_pattern.match(message_string) is not None: + if regex.match(message_string) is not None: raise APIKeyIncludedException() From ddae3c622310de87bc08ffc5ddb8488a01d3c288 Mon Sep 17 00:00:00 2001 From: silviabotros Date: Sat, 2 Jun 2018 17:54:58 -0700 Subject: [PATCH 070/462] fixing link to use cases readme --- README.md | 4 ++-- TROUBLESHOOTING.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f1366eae8..6ff8e11cf 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![Twitter Follow](https://img.shields.io/twitter/follow/sendgrid.svg?style=social&label=Follow)](https://twitter.com/sendgrid) [![GitHub contributors](https://img.shields.io/github/contributors/sendgrid/sendgrid-python.svg)](https://github.com/sendgrid/sendgrid-python/graphs/contributors) -**NEW:** +**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). @@ -175,7 +175,7 @@ Please see [our helper](https://github.com/sendgrid/sendgrid-python/tree/master/ # Use Cases -[Examples of common API use cases](https://github.com/sendgrid/sendgrid-python/blob/master/USE_CASES.md), such as how to send an email with a transactional template. +[Examples of common API use cases](https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/README.md), such as how to send an email with a transactional template. # Announcements diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index a68431ae5..29c0c8e89 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -111,4 +111,4 @@ print mail.get() # Error Handling -Please review [our use_cases](https://github.com/sendgrid/sendgrid-python/USE_CASES.md) for examples of error handling. +Please review [our use_cases](https://github.com/sendgrid/sendgrid-python/use_cases/README.md) for examples of error handling. From 24add1f3c4a3a1aa86e19f9894516c577615af29 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 6 Jun 2018 08:25:12 -0700 Subject: [PATCH 071/462] Patch --- examples/mail/mail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mail/mail.py b/examples/mail/mail.py index 253c2558a..e853d422c 100644 --- a/examples/mail/mail.py +++ b/examples/mail/mail.py @@ -164,7 +164,7 @@ "enable": True, "html": "If you would like to unsubscribe and stop receiving these emails <% clickhere %>.", "substitution_tag": "<%click here%>", - "text": "If you would like to unsubscribe and stop receiveing these emails <% click here %>." + "text": "If you would like to unsubscribe and stop receiving these emails <% click here %>." } } } From b75a74620b72614d563198dacc4d09d916680f61 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 6 Jun 2018 08:28:37 -0700 Subject: [PATCH 072/462] Typos --- README.md | 2 +- sendgrid/helpers/inbound/app.py | 2 +- sendgrid/helpers/inbound/templates/index.html | 2 +- test/test_sendgrid.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6ff8e11cf..dbcebafdc 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ echo "sendgrid.env" >> .gitignore source ./sendgrid.env ``` -Sendgrid also supports local enviroment file `.env`. Copy or rename `.env_sample` into `.env` and update [SENDGRID_API_KEY](https://app.sendgrid.com/settings/api_keys) with your key. +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 diff --git a/sendgrid/helpers/inbound/app.py b/sendgrid/helpers/inbound/app.py index 605c4497c..0d4435907 100755 --- a/sendgrid/helpers/inbound/app.py +++ b/sendgrid/helpers/inbound/app.py @@ -30,7 +30,7 @@ def index(): def inbound_parse(): """Process POST from Inbound Parse and print received data.""" parse = Parse(config, request) - # Sample proccessing action + # Sample processing action print(parse.key_values()) # Tell SendGrid's Inbound Parse to stop sending POSTs # Everything is 200 OK :) diff --git a/sendgrid/helpers/inbound/templates/index.html b/sendgrid/helpers/inbound/templates/index.html index b0f0954db..0de3f44f3 100644 --- a/sendgrid/helpers/inbound/templates/index.html +++ b/sendgrid/helpers/inbound/templates/index.html @@ -3,7 +3,7 @@ SendGrid Incoming Parse -

You have successfuly launched the server!

+

You have successfully launched the server!

Check out the documentation on how to use this software to utilize the SendGrid Inbound Parse webhook. diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index 73a080b8f..f71381c64 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -1182,7 +1182,7 @@ def test_mail_send_post(self): "receiving these emails <% clickhere %>.", "substitution_tag": "<%click here%>", "text": "If you would like to unsubscribe and stop " - "receiveing these emails <% click here %>." + "receiving these emails <% click here %>." } } } From 86708c7908102b171a9dbc92d31c5740aa218423 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 7 Jun 2018 12:20:49 -0700 Subject: [PATCH 073/462] Version Bump v5.4.0: Hacktoberfest Release --- CHANGELOG.md | 64 ++++++++++++++++++++++++++++++++++++++++++++- docker/README.md | 3 ++- sendgrid/version.py | 2 +- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ec462196..7ba760400 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,68 @@ # Change Log All notable changes to this project will be documented in this file. +## [5.4.0] - 2018-06-07 ## +### Added +- [PR #384](https://github.com/sendgrid/sendgrid-python/pull/384): Adds how to set up domain whitelabel and how to view email statistics. Big thanks to [Aditya Tandon](https://github.com/adityatandon007) for the PR! +- [PR #427](https://github.com/sendgrid/sendgrid-python/pull/427): Increase config.py coverage. Big thanks to [Jeferson Daniel](https://github.com/jefersondaniel) for the PR! +- [PR #423](https://github.com/sendgrid/sendgrid-python/pull/423): Update config.py with better file handling. Big thanks to [Ajitesh Rai](https://github.com/ajiteshr7) for the PR! +- [PR #449](https://github.com/sendgrid/sendgrid-python/pull/449): Add a .env_sample file and Update README.md. Big thanks to [trangttt](https://github.com/trangttt) for the PR! +- [PR #463](https://github.com/sendgrid/sendgrid-python/pull/449): Add code climate. +- [PR #455](https://github.com/sendgrid/sendgrid-python/pull/455): Use with context manager and a few PEP8 changes. Big thanks to [Tim](https://github.com/The-White-Wolf) for the PR! +- [PR #470](https://github.com/sendgrid/sendgrid-python/pull/470): Modularize lengthy method. Big thanks to [Suprith Kumar Suvarneshwar](https://github.com/suprithIUB) for the PR! +- [PR #425](https://github.com/sendgrid/sendgrid-python/pull/425): Add tests for sendgrid.py apikey and api_key setters. Big thanks to [Krista LaFentres](https://github.com/lafentres) for the PR! +- [PR #446](https://github.com/sendgrid/sendgrid-python/pull/446): Added PULL_REQUEST_TEMPLATE. Big thanks to [Aleksandr Sobolev](https://github.com/s0b0lev) for the PR! +- [PR #472](https://github.com/sendgrid/sendgrid-python/pull/472): Moved mail helper classes into separate files. Big thanks to [Milos Pejanovic](https://github.com/runz0rd) for the PR! +- [PR #481](https://github.com/sendgrid/sendgrid-python/pull/481): Documented the new error handling functionality from python-http-client. Big thanks to [Manjiri Tapaswi](https://github.com/mptap) for the PR! +- [PR #418](https://github.com/sendgrid/sendgrid-python/pull/418): Add test for apps.py. Big thanks to [Sinan Comert](https://github.com/scomert) for the PR! +- [PR #438](https://github.com/sendgrid/sendgrid-python/pull/438): Update docstrings/pydoc/help. Big thanks to [Gabriel Krell](https://github.com/gabrielkrell) for the PR! +- [PR #413](https://github.com/sendgrid/sendgrid-python/pull/413): Error-checking in Mail helper/ASM. Big thanks to [Gabriel Krell](https://github.com/gabrielkrell) for the PR! +- [PR #518](https://github.com/sendgrid/sendgrid-python/pull/518): Announcement about Data Platform Engineer posting. Big thanks to [Marghodk](https://github.com/Marghodk) for the PR! +- [PR #479](https://github.com/sendgrid/sendgrid-python/pull/479): Add Project tests. Big thanks to [Peter Hampton](https://github.com/pjhampton) for the PR! +- [PR #480](https://github.com/sendgrid/sendgrid-python/pull/480): Test to check year in LICENSE.txt. Big thanks to [Navin Pai](https://github.com/navinpai) for the PR! +- [PR #476](https://github.com/sendgrid/sendgrid-python/pull/476): Add tests for Send.py. Big thanks to [Artiem K.](https://github.com/artiemq) for the PR! +- [PR #366](https://github.com/sendgrid/sendgrid-python/pull/366): Add AWS app tutorial to USE_CASES.md. Big thanks to [Mike Vanbuskirk](https://github.com/codevbus) for the PR! +- [PR #365](https://github.com/sendgrid/sendgrid-python/pull/365): Write tutorial to deploy simple Django app on Heroku. Big thanks to [Kan Ouivirach](https://github.com/zkan) for the PR! +- [PR #526](https://github.com/sendgrid/sendgrid-python/pull/526): Include code reviews section. Big thanks to [Jared Scott](https://github.com/jlax47) for the PR! +- [PR #414](https://github.com/sendgrid/sendgrid-python/pull/414): Provide utf-8 as encoding explicitly when opening text files. Big thanks to [Ruslan Shestopalyuk](https://github.com/rshest) for the PR! +- [PR #537](https://github.com/sendgrid/sendgrid-python/pull/537): Add unittesting support to .codeclimate.yml. Big thanks to [Prashu Chaudhary](https://github.com/prashuchaudhary) for the PR! +- [PR #554](https://github.com/sendgrid/sendgrid-python/pull/554): Ensure params are applied independently. Big thanks to [Nino Milenovic](https://github.com/rubyengineer) for the PR! +- [PR #557](https://github.com/sendgrid/sendgrid-python/pull/557): Client cleanup. Big thanks to [Slam](https://github.com/3lnc) for the PR! +- [PR #569](https://github.com/sendgrid/sendgrid-python/pull/569): Make Mail helper parameters truly optional. Big thanks to [Ian Beck](https://github.com/onecrayon) for the PR! + +### Fixed +- [PR #415](https://github.com/sendgrid/sendgrid-python/pull/415): Typos. Big thanks to [Mohd Huzaifa Faruqui](https://github.com/huzaifafaruqui) for the PR! +- [PR #421](https://github.com/sendgrid/sendgrid-python/pull/421): Typos. Big thanks to [Abhishek Bhatt](https://github.com/ab-bh) for the PR! +- [PR #432](https://github.com/sendgrid/sendgrid-python/pull/432): Typos. Big thanks to [Gaurav Arora](https://github.com/gaurav61) for the PR! +- [PR #431](https://github.com/sendgrid/sendgrid-python/pull/431): Typos. Big thanks to [Gaurav Arora](https://github.com/gaurav61) for the PR! +- [PR #430](https://github.com/sendgrid/sendgrid-python/pull/430): Attempt to sync before executing 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 #494](https://github.com/sendgrid/sendgrid-python/pull/494): Remove unused import in register.py. Big thanks to [Alexis Rivera De La Torre](https://github.com/gardlt) for the PR! +- [PR #469](https://github.com/sendgrid/sendgrid-python/pull/469): +Removed the trailing white spaces. Big thanks to [Siddaram Halli](https://github.com/SidduH) for the PR! +- [PR #484](https://github.com/sendgrid/sendgrid-python/pull/484): Python style fixes. Big thanks to [Gabriel Krell](https://github.com/gabrielkrell) for the PR! +- [PR #508](https://github.com/sendgrid/sendgrid-python/pull/508): Typos. Big thanks to [Saksham Gupta](https://github.com/shucon) for the PR! +- [PR #353](https://github.com/sendgrid/sendgrid-python/pull/353): Typos. Big thanks to [Yothin M](https://github.com/yothinix) for the PR! +- [PR #564](https://github.com/sendgrid/sendgrid-python/pull/564): Typos. Big thanks to [Chao](https://github.com/chaoranxie) for the PR! +- [PR #424](https://github.com/sendgrid/sendgrid-python/pull/424): Updating version 2.7.8 to 2.7.11 to match version in pyenv install instruction. Big thanks to [Krista LaFentres](https://github.com/lafentres) for the PR! +- [PR #454](https://github.com/sendgrid/sendgrid-python/pull/454): Requests to send mail with both plain text and HTML content fail if the HTML content is specified first. Big thanks to [Ryan D'souza](https://github.com/dsouzarc) for the PR! +- [PR #466](https://github.com/sendgrid/sendgrid-python/pull/466): Fixed PEP8 issues. Big thanks to [Piotr Szwarc](https://github.com/blackpioter) for the PR! +- [PR #522](https://github.com/sendgrid/sendgrid-python/pull/522): Typos. Big thanks to [Abhishek J](https://github.com/slashstar) for the PR! +- [PR #514](https://github.com/sendgrid/sendgrid-python/pull/514): Fix method_complexity issue in sendgrid/helpers/mail/ganalytics.py. Big thanks to [Chetan Kumar](https://github.com/chetankm-cs) for the PR! +- [PR #515](https://github.com/sendgrid/sendgrid-python/pull/515): Typos. Big thanks to [Mohd Ali Rizwi](https://github.com/alirizwi) for the PR! +- [PR #519](https://github.com/sendgrid/sendgrid-python/pull/519): Typos. Big thanks to [Aashish Gaba](https://github.com/ishucr7) for the PR! +- [PR #532](https://github.com/sendgrid/sendgrid-python/pull/532): Typos. Big thanks to [~](https://github.com/delirious-lettuce) for the PR! +- [PR #533](https://github.com/sendgrid/sendgrid-python/pull/533): Fix shadowed builtins, `id` -> `id_`. Big thanks to [~](https://github.com/delirious-lettuce) for the PR! +- [PR #581](https://github.com/sendgrid/sendgrid-python/pull/581): Typos. Big thanks to [Silvia Botros](https://github.com/silviabotros) for the PR! +- [PR #513](https://github.com/sendgrid/sendgrid-python/pull/513): Typos. Big thanks to [thepriefy](https://github.com/thepriefy) for the PR! +- [PR #538](https://github.com/sendgrid/sendgrid-python/pull/538): Fix bug in get_mock_personalization_dict(). Big thanks to [PierreMonico](https://github.com/PierreMonico) for the PR! +- [PR #543](https://github.com/sendgrid/sendgrid-python/pull/543): Typos. Big thanks to [Matthieu Bonnefoy](https://github.com/mbonnefoy) for the PR! + ## [5.3.0] - 2017-10-23 ## ### Added - Pull #348: Allows users to submit rfc822 formatted email addresses @@ -23,7 +85,7 @@ All notable changes to this project will be documented in this file. - Big thanks to [belfazt](https://github.com/belfazt) for the pull request! ## [5.0.1] - 2017-08-29 -### Fix +### Fixed - Pull #337, fixes issue #366 - On install, some experienced: `ValueError: ("Expected ',' or end-of-list in", 'python-http-client ==3.0.*', 'at', '*')` diff --git a/docker/README.md b/docker/README.md index 8d89da654..4ff4e2afa 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,5 +1,6 @@ # Supported tags and respective `Dockerfile` links - - `v5.3.0`, `latest` [(Dockerfile)](https://github.com/sendgrid/sendgrid-python/blob/master/docker/Dockerfile) + - `v5.4.0`, `latest` [(Dockerfile)](https://github.com/sendgrid/sendgrid-python/blob/master/docker/Dockerfile) + - `v5.3.0` - `v5.2.1` - `v5.2.0` - `v5.1.0` diff --git a/sendgrid/version.py b/sendgrid/version.py index 3098f851d..ec1b5ec64 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1,2 +1,2 @@ -version_info = (5, 3, 0) +version_info = (5, 4, 0) __version__ = '.'.join(str(v) for v in version_info) From 8855fb123c9c7aad1f4a640b3eaf3301288ad3ef Mon Sep 17 00:00:00 2001 From: Richard Nias <7244202+richardnias@users.noreply.github.com> Date: Fri, 8 Jun 2018 14:32:04 +0100 Subject: [PATCH 074/462] Fix `Personalization.substitutions` setter Trying to set `substitutions` directly rather than with `add_substitution` was causing an infinite regress --- sendgrid/helpers/mail/personalization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py index e49432cf5..8bb4bed0b 100644 --- a/sendgrid/helpers/mail/personalization.py +++ b/sendgrid/helpers/mail/personalization.py @@ -116,7 +116,7 @@ def substitutions(self): @substitutions.setter def substitutions(self, value): - self.substitutions = value + self._substitutions = value def add_substitution(self, substitution): """Add a new Substitution to this Personalization. From 2125d367e137e5ad4474e7774aebebcc7c2729de Mon Sep 17 00:00:00 2001 From: Richard Nias Date: Fri, 8 Jun 2018 14:45:37 +0100 Subject: [PATCH 075/462] add test_directly_setting_substitutions --- test/test_mail.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_mail.py b/test/test_mail.py index 86f21c069..7721b5205 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -558,3 +558,7 @@ def test_disable_tracking(self): tracking_settings.get(), {'click_tracking': {'enable': False, 'enable_text': False}} ) + + def test_directly_setting_substitutions(self): + personalization = Personalization() + personalization.substitutions = [{'a': 0}] From 0692f31f89fdd853596694abca6aba3ea09eb93b Mon Sep 17 00:00:00 2001 From: Anurag Anand Date: Fri, 15 Jun 2018 15:44:11 +0530 Subject: [PATCH 076/462] Updated def build_personalization Corrected a typo. --- examples/helpers/mail/mail_example.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/helpers/mail/mail_example.py b/examples/helpers/mail/mail_example.py index 93b3fb6a4..b2de7f0a0 100644 --- a/examples/helpers/mail/mail_example.py +++ b/examples/helpers/mail/mail_example.py @@ -30,7 +30,7 @@ def build_personalization(personalization): mock_personalization.add_to(cc_addr) for bcc_addr in personalization['bcc_list']: - mock_personalization.add_bc(bcc_addr) + mock_personalization.add_bcc(bcc_addr) for header in personalization['headers']: mock_personalization.add_header(header) @@ -216,4 +216,4 @@ def send_kitchen_sink(): send_hello_email() # this will only send an email if you set SandBox Mode to False -send_kitchen_sink() \ No newline at end of file +send_kitchen_sink() From 1ac58c6fc491554bc24573393ae6242e579dbabb Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 26 Jun 2018 10:04:26 -0700 Subject: [PATCH 077/462] Version Bump v5.4.1: Bug Fixes --- CHANGELOG.md | 5 +++++ docker/README.md | 3 ++- register.py | 4 ++-- sendgrid/version.py | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ba760400..2c1869666 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Change Log All notable changes to this project will be documented in this file. +## [5.4.1] - 2018-06-26 ## +### Fixed +- [PR #585](https://github.com/sendgrid/sendgrid-python/pull/585): Fix typo in `mail_example.py`. Big thanks to [Anurag Anand](https://github.com/theanuraganand) for the PR! +- [PR #583](https://github.com/sendgrid/sendgrid-python/pull/585): Fix `Personalization.substitutions` setter. Trying to set substitutions directly rather than with add_substitution was causing an infinite regress. Big thanks to [Richard Nias](https://github.com/richardnias) for the PR! + ## [5.4.0] - 2018-06-07 ## ### Added - [PR #384](https://github.com/sendgrid/sendgrid-python/pull/384): Adds how to set up domain whitelabel and how to view email statistics. Big thanks to [Aditya Tandon](https://github.com/adityatandon007) for the PR! diff --git a/docker/README.md b/docker/README.md index 4ff4e2afa..a523dc93d 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,5 +1,6 @@ # Supported tags and respective `Dockerfile` links - - `v5.4.0`, `latest` [(Dockerfile)](https://github.com/sendgrid/sendgrid-python/blob/master/docker/Dockerfile) + - `v5.4.1`, `latest` [(Dockerfile)](https://github.com/sendgrid/sendgrid-python/blob/master/docker/Dockerfile) + - `v5.4.0` - `v5.3.0` - `v5.2.1` - `v5.2.0` diff --git a/register.py b/register.py index b71341a34..0a7ffe8d8 100644 --- a/register.py +++ b/register.py @@ -2,8 +2,8 @@ from io import open output = pypandoc.convert('README.md', 'rst') -with open('README.txt' 'w+') as f: - f.write(str(output.encode('utf-8'))) +with open('README.txt', 'w+') as f: + f.write(output) readme_rst = open('./README.txt', 'r', encoding='utf-8').read() replace = ''' diff --git a/sendgrid/version.py b/sendgrid/version.py index ec1b5ec64..c191754f8 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1,2 +1,2 @@ -version_info = (5, 4, 0) +version_info = (5, 4, 1) __version__ = '.'.join(str(v) for v in version_info) From e07db8d02269883dd42eda7af560ee2128d03425 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 26 Jun 2018 11:36:44 -0700 Subject: [PATCH 078/462] Initial working version of Send a Single Email to a Single Recipient --- live_test.py | 19 ++++++++++ proposals/mail-helper-refactor.md | 3 +- sendgrid/helpers/mail/__init__.py | 5 +++ sendgrid/helpers/mail/from_email.py | 4 ++ sendgrid/helpers/mail/html_content.py | 41 +++++++++++++++++++++ sendgrid/helpers/mail/mail.py | 20 ++++++---- sendgrid/helpers/mail/plain_text_content.py | 41 +++++++++++++++++++++ sendgrid/helpers/mail/subject.py | 38 +++++++++++++++++++ sendgrid/helpers/mail/to_email.py | 4 ++ sendgrid/sendgrid.py | 4 ++ 10 files changed, 170 insertions(+), 9 deletions(-) create mode 100644 live_test.py create mode 100644 sendgrid/helpers/mail/from_email.py create mode 100644 sendgrid/helpers/mail/html_content.py create mode 100644 sendgrid/helpers/mail/plain_text_content.py create mode 100644 sendgrid/helpers/mail/subject.py create mode 100644 sendgrid/helpers/mail/to_email.py diff --git a/live_test.py b/live_test.py new file mode 100644 index 000000000..c432133b6 --- /dev/null +++ b/live_test.py @@ -0,0 +1,19 @@ +# Send a Single Email to a Single Recipient +import os +from sendgrid import SendGridAPIClient # import sendgrid +from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException # from sendgrid.helpers.mail + +msg = 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: + sg_client = SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + response = sg_client.send(request_body=msg) + 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..9162e4a4d 100644 --- a/proposals/mail-helper-refactor.md +++ b/proposals/mail-helper-refactor.md @@ -16,7 +16,8 @@ msg = Mail(from_email=From('from@example.com', 'From Name'), html_content=HtmlContent('and easy to do anywhere, even with Python')) try: - response = sendgrid.send(msg, apikey=os.environ.get('SENDGRID_apikey')) + sg_client = SendGridAPIClient() + response = sg_client.send(msg, apikey=os.environ.get('SENDGRID_apikey')) print(response.status_code) print(response.body) print(response.headers) diff --git a/sendgrid/helpers/mail/__init__.py b/sendgrid/helpers/mail/__init__.py index 1dd769e99..7a26aa411 100644 --- a/sendgrid/helpers/mail/__init__.py +++ b/sendgrid/helpers/mail/__init__.py @@ -9,16 +9,21 @@ from .email import Email from .exceptions import SendGridException, APIKeyIncludedException from .footer_settings import FooterSettings +from .from_email import From from .ganalytics import Ganalytics from .header import Header +from .html_content import HtmlContent from .mail_settings import MailSettings from .mail import Mail from .open_tracking import OpenTracking from .personalization import Personalization +from .plain_text_content import PlainTextContent from .sandbox_mode import SandBoxMode from .section import Section from .spam_check import SpamCheck +from .subject import Subject from .subscription_tracking import SubscriptionTracking from .substitution import Substitution from .tracking_settings import TrackingSettings +from .to_email import To from .validators import ValidateAPIKey diff --git a/sendgrid/helpers/mail/from_email.py b/sendgrid/helpers/mail/from_email.py new file mode 100644 index 000000000..c12eeb4ac --- /dev/null +++ b/sendgrid/helpers/mail/from_email.py @@ -0,0 +1,4 @@ +from .email import Email + +class From(Email): + """A from email address with an optional name.""" \ No newline at end of file diff --git a/sendgrid/helpers/mail/html_content.py b/sendgrid/helpers/mail/html_content.py new file mode 100644 index 000000000..271f7321b --- /dev/null +++ b/sendgrid/helpers/mail/html_content.py @@ -0,0 +1,41 @@ +from .content import Content +from .validators import ValidateAPIKey + +class HtmlContent(Content): + """HTML content to be included in your email. + """ + + def __init__(self, value): + """Create a HtmlContent with the specified MIME type and value. + + :param value: The actual HTML content. + :type value: string, optional + """ + self._type = "text/html" + self._value = value + self._validator = ValidateAPIKey() + + @property + def value(self): + """The actual HTML content. + + :rtype: string + """ + return self._value + + @value.setter + def value(self, value): + self._validator.validate_message_dict(value) + self._value = 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 = {} + content["type"] = "text/html" + content["value"] = self._value + return content diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 6554d4508..1563f5c5c 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -2,7 +2,6 @@ from .personalization import Personalization from .header import Header - class Mail(object): """A request to be sent with the SendGrid v3 Mail Send API (v3/mail/send). @@ -10,9 +9,10 @@ class Mail(object): """ def __init__(self, from_email=None, + to_emails=None, subject=None, - to_email=None, - content=None): + plain_text_content=None, + html_content=None): """Create a Mail object. If any parameters are not supplied, they must be set after initialization. @@ -47,15 +47,18 @@ def __init__(self, self.from_email = from_email if subject: - self.subject = subject + self.subject = subject.get() - if to_email: + if to_emails: personalization = Personalization() - personalization.add_to(to_email) + personalization.add_to(to_emails) self.add_personalization(personalization) - if content: - self.add_content(content) + if plain_text_content: + self.add_content(plain_text_content) + + if html_content: + self.add_content(html_content) def __str__(self): """Get a JSON representation of this Mail request. @@ -395,3 +398,4 @@ def add_custom_arg(self, custom_arg): if self._custom_args is None: self._custom_args = [] self._custom_args.append(custom_arg) + diff --git a/sendgrid/helpers/mail/plain_text_content.py b/sendgrid/helpers/mail/plain_text_content.py new file mode 100644 index 000000000..a3048158b --- /dev/null +++ b/sendgrid/helpers/mail/plain_text_content.py @@ -0,0 +1,41 @@ +from .content import Content +from .validators import ValidateAPIKey + +class PlainTextContent(Content): + """Plain text content to be included in your email. + """ + + def __init__(self, value): + """Create a PlainTextContent with the specified MIME type and value. + + :param value: The actual text content. + :type value: string, optional + """ + self._type = "text/plain" + self._value = value + self._validator = ValidateAPIKey() + + @property + def value(self): + """The actual text content. + + :rtype: string + """ + return self._value + + @value.setter + def value(self, value): + self._validator.validate_message_dict(value) + self._value = 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 = {} + content["type"] = "text/plain" + content["value"] = self._value + return content diff --git a/sendgrid/helpers/mail/subject.py b/sendgrid/helpers/mail/subject.py new file mode 100644 index 000000000..8fc3ad8b0 --- /dev/null +++ b/sendgrid/helpers/mail/subject.py @@ -0,0 +1,38 @@ +class Subject(object): + """A subject for an email message.""" + + def __init__(self, subject): + """Create a Subjuct. + + :param subject: The subject for an email + :type subject: string + """ + self._subject = subject + + @property + def subject(self): + """The subject of an email. + + :rtype: string + """ + return self._subject + + @subject.setter + def subject(self, value): + self._subject = 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/to_email.py b/sendgrid/helpers/mail/to_email.py new file mode 100644 index 000000000..18ef8f725 --- /dev/null +++ b/sendgrid/helpers/mail/to_email.py @@ -0,0 +1,4 @@ +from .email import Email + +class To(Email): + """A to email address with an optional name.""" \ No newline at end of file diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index 0f09bd542..994c11f16 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -103,3 +103,7 @@ def api_key(self): @api_key.setter def api_key(self, value): self.apikey = value + + def send(self, request_body): + response = self.client.mail.send.post(request_body=request_body.get()) + return response From 7ccbf5d4097878c77bf6a9f8731e9bc3c7bd3488 Mon Sep 17 00:00:00 2001 From: Bhargav Chandaka Date: Sun, 8 Jul 2018 18:57:21 -0500 Subject: [PATCH 079/462] Update Readme Added windows environment variable setup --- README.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dbcebafdc..0b6a3b483 100644 --- a/README.md +++ b/README.md @@ -46,19 +46,26 @@ We appreciate your continued support, thank you! - 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), for example: - +### Mac ```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 +### Windows +Temporarily set the environment variable(accesible only during the current cli session): +```bash +set SENDGRID_API_KEY=YOUR_API_KEY +``` +Permanently set the environment variable: +```bash +setx SENDGRID_API_KEY "YOUR_API_KEY" +``` +## Install Package ```bash pip install sendgrid ``` From 2da51c238fd6ecc1caaa343cf061437ff8bb2a2a Mon Sep 17 00:00:00 2001 From: Bhargav Chandaka Date: Sun, 8 Jul 2018 18:59:56 -0500 Subject: [PATCH 080/462] Update README.md Added environmental variable setup in windows --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b6a3b483..f34a46235 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Temporarily set the environment variable(accesible only during the current cli s ```bash set SENDGRID_API_KEY=YOUR_API_KEY ``` -Permanently set the environment variable: +Permanently set the environment variable(accessible in all subsequent cli sessions): ```bash setx SENDGRID_API_KEY "YOUR_API_KEY" ``` From 6fb64da5e08ed8a7e510f918c98e25de87944ebf Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 12 Jul 2018 10:52:54 -0700 Subject: [PATCH 081/462] Code cleanup for first use case --- live_test.py | 25 ++++++++++++--------- proposals/mail-helper-refactor.md | 20 ++++++++--------- sendgrid/helpers/mail/html_content.py | 11 +++++---- sendgrid/helpers/mail/plain_text_content.py | 4 ++-- sendgrid/helpers/mail/subject.py | 2 +- sendgrid/sendgrid.py | 4 ++-- 6 files changed, 35 insertions(+), 31 deletions(-) diff --git a/live_test.py b/live_test.py index c432133b6..97fa15816 100644 --- a/live_test.py +++ b/live_test.py @@ -1,19 +1,24 @@ # Send a Single Email to a Single Recipient import os -from sendgrid import SendGridAPIClient # import sendgrid -from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException # from sendgrid.helpers.mail +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException -msg = 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 = 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: - sg_client = SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - response = sg_client.send(request_body=msg) + sendgrid_client = SendGridAPIClient(apikey=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 + print(e.message) + +# ToDo + +## The Mail constructor should also support passing in tuples and strings +## The send function parameter should be better named, maybe email_message or simply message diff --git a/proposals/mail-helper-refactor.md b/proposals/mail-helper-refactor.md index 9162e4a4d..68c3f403a 100644 --- a/proposals/mail-helper-refactor.md +++ b/proposals/mail-helper-refactor.md @@ -6,22 +6,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: - sg_client = SendGridAPIClient() - response = sg_client.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()) ``` diff --git a/sendgrid/helpers/mail/html_content.py b/sendgrid/helpers/mail/html_content.py index 271f7321b..0c81f9ce6 100644 --- a/sendgrid/helpers/mail/html_content.py +++ b/sendgrid/helpers/mail/html_content.py @@ -2,18 +2,17 @@ from .validators import ValidateAPIKey class HtmlContent(Content): - """HTML content to be included in your email. - """ + """HTML content to be included in your email.""" def __init__(self, value): - """Create a HtmlContent with the specified MIME type and value. + """Create an HtmlContent with the specified MIME type and value. - :param value: The actual HTML content. + :param value: The HTML content. :type value: string, optional """ - self._type = "text/html" - self._value = value self._validator = ValidateAPIKey() + self.type = "text/html" + self.value = value @property def value(self): diff --git a/sendgrid/helpers/mail/plain_text_content.py b/sendgrid/helpers/mail/plain_text_content.py index a3048158b..9c55d12d9 100644 --- a/sendgrid/helpers/mail/plain_text_content.py +++ b/sendgrid/helpers/mail/plain_text_content.py @@ -11,9 +11,9 @@ def __init__(self, value): :param value: The actual text content. :type value: string, optional """ - self._type = "text/plain" - self._value = value self._validator = ValidateAPIKey() + self.type = "text/plain" + self.value = value @property def value(self): diff --git a/sendgrid/helpers/mail/subject.py b/sendgrid/helpers/mail/subject.py index 8fc3ad8b0..2a9841c16 100644 --- a/sendgrid/helpers/mail/subject.py +++ b/sendgrid/helpers/mail/subject.py @@ -7,7 +7,7 @@ def __init__(self, subject): :param subject: The subject for an email :type subject: string """ - self._subject = subject + self.subject = subject @property def subject(self): diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index 994c11f16..c044196a3 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -104,6 +104,6 @@ def api_key(self): def api_key(self, value): self.apikey = value - def send(self, request_body): - response = self.client.mail.send.post(request_body=request_body.get()) + def send(self, message): + response = self.client.mail.send.post(request_body=message.get()) return response From 4ee2dd679a43f925f08981af76714f57a612226d Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 12 Jul 2018 11:41:59 -0700 Subject: [PATCH 082/462] Integrate PR #486 --- sendgrid/helpers/mail/mail.py | 142 ++++++++++++++++++---------------- 1 file changed, 76 insertions(+), 66 deletions(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 1563f5c5c..408f25819 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -74,68 +74,42 @@ def get(self): """ mail = {} - if self.from_email is not None: - mail["from"] = self.from_email.get() - - if self.subject is not None: - mail["subject"] = self.subject - - if self.personalizations: - mail["personalizations"] = [ - personalization.get() - for personalization in self.personalizations - ] + REQUEST_BODY_KEYS = [ + 'attachments', + 'batch_id', + 'categories', + 'custom_args', + 'headers', + 'ip_pool_name', + 'personalizations', + 'sections', + 'send_at', + 'subject', + 'template_id' + ] + + for key in REQUEST_BODY_KEYS: + value = getattr(self, key, None) + if value: + mail[key] = value 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 - - if self.categories: - mail["categories"] = [category.get() for category in - self.categories] - - if self.custom_args: - custom_args = {} - for key in self.custom_args: - custom_args.update(key.get()) - mail["custom_args"] = custom_args + mail["content"] = self.contents - if self.send_at is not None: - mail["send_at"] = self.send_at + if self.from_email: + mail["from"] = self.from_email.get() - if self.batch_id is not None: - mail["batch_id"] = self.batch_id - if self.asm is not None: + if self.asm: mail["asm"] = self.asm.get() - if self.ip_pool_name is not None: - mail["ip_pool_name"] = self.ip_pool_name - - if self.mail_settings is not None: + if self.mail_settings: mail["mail_settings"] = self.mail_settings.get() - if self.tracking_settings is not None: + if self.tracking_settings: mail["tracking_settings"] = self.tracking_settings.get() - if self.reply_to is not None: + if self.reply_to: mail["reply_to"] = self.reply_to.get() return mail @@ -282,9 +256,15 @@ def personalizations(self): message should be handled. A maximum of 1000 personalizations can be included. - :rtype: list + :returns: List of dictionaries. Each dictionary is obtained by + Personalization.get + :rtype: list(dictionaries) """ - return self._personalizations + if self._personalizations is not None: + return [ + personalization.get() + for personalization in self._personalizations + ] def add_personalization(self, personalizations): """Add a new Personalization to this Mail. @@ -296,10 +276,16 @@ def add_personalization(self, personalizations): @property def contents(self): """The Contents of this Mail. Must include at least one MIME type. - + + :returns: List of dictionaries returned by content.get :rtype: list(Content) """ - return self._contents + if self._contents is not None: + return[ + ob.get() + for ob in self._contents + ] + return None def add_content(self, content): """Add a new Content to this Mail. Usually the plaintext or HTML @@ -320,10 +306,16 @@ def add_content(self, content): def attachments(self): """The attachments included with this Mail. - :returns: List of Attachment objects. - :rtype: list(Attachment) + :returns: List of dictionaries. Each dictionary is obtained by + Attachment.get + :rtype: list(dictionaries) """ - return self._attachments + if self._attachments: + return [ + ob.get() + for ob in self._attachments + ] + return None def add_attachment(self, attachment): """Add an Attachment to this Mail. @@ -336,10 +328,16 @@ def add_attachment(self, attachment): def sections(self): """The sections included with this Mail. - :returns: List of Section objects. - :rtype: list(Section) + :returns: List of of dictionaries. Each dictionary is obtained by + Section.get + :rtype: list(dictionaries) """ - return self._sections + if self._sections: + sections = {} + for key in self._sections: + sections.update(key.get()) + return sections + return None def add_section(self, section): """Add a Section to this Mail. @@ -352,10 +350,16 @@ def add_section(self, section): def headers(self): """The Headers included with this Mail. - :returns: List of Header objects. - :rtype: list(Header) + :returns: List of dictionaries. Each dictionary is obtained by + Header.get + :rtype: list(dictionaries) """ - return self._headers + if self._headers: + headers = {} + for key in self._headers: + headers.update(key.get()) + return headers + return None def add_header(self, header): """Add a Header to this Mail. @@ -374,9 +378,15 @@ def add_header(self, header): def categories(self): """The Categories applied to this Mail. Must not exceed 10 items - :rtype: list(Category) + :returns: List of dictionaries. Each dictionary is obtained by + Category.get + :rtype: list(dictionaries) """ - return self._categories + if self._categories: + return [ + category.get + for category in self._categories + ] def add_category(self, category): """Add a Category to this Mail. Must be less than 255 characters. From 3b705ed408c0c28073d53d9638a8b3010cb2dfe0 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 12 Jul 2018 11:49:18 -0700 Subject: [PATCH 083/462] Integrate PR #458 --- sendgrid/helpers/mail/mail.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 408f25819..d70a0eca8 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -333,10 +333,7 @@ def sections(self): :rtype: list(dictionaries) """ if self._sections: - sections = {} - for key in self._sections: - sections.update(key.get()) - return sections + return self.update_objects(self._sections) return None def add_section(self, section): @@ -355,10 +352,7 @@ def headers(self): :rtype: list(dictionaries) """ if self._headers: - headers = {} - for key in self._headers: - headers.update(key.get()) - return headers + return self.update_objects(self._headers) return None def add_header(self, header): @@ -402,10 +396,20 @@ def custom_args(self): Must not exceed 10,000 characters. :rtype: list(CustomArg) """ - return self._custom_args + if self._custom_args: + return self.update_objects(self._custom_args) + return None def add_custom_arg(self, custom_arg): if self._custom_args is None: self._custom_args = [] self._custom_args.append(custom_arg) + + def update_objects(self, property): + objects = {} + for key in property: + objects.update(key.get()) + return objects + + From 7ed4338d67507a183b0897532aa3e22b58644374 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 12 Jul 2018 11:56:59 -0700 Subject: [PATCH 084/462] Integrate PR #493 --- sendgrid/helpers/mail/personalization.py | 53 +++++++++--------------- 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py index 8bb4bed0b..49a779cf9 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 = [] @@ -166,36 +166,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']: + 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 From 6d5e9908cd074dbcb9dd6ac24df8de25245e318d Mon Sep 17 00:00:00 2001 From: Swapnil Agarwal Date: Fri, 27 Oct 2017 00:42:03 +0530 Subject: [PATCH 085/462] Add a tutorial to deploy a simple 'Hello Email' app on Heroku See #356 --- use_cases/README.md | 1 + use_cases/flask_heroku.md | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 use_cases/flask_heroku.md diff --git a/use_cases/README.md b/use_cases/README.md index 188464d09..4e0fc24d6 100644 --- a/use_cases/README.md +++ b/use_cases/README.md @@ -8,6 +8,7 @@ This directory provides examples for specific use cases of this library. Please * [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 Deploy a simple Flask app, to send Email with SendGrid, on Heroku](flask_heroku.md) * [How to Setup a Domain Whitelabel](domain_whitelabel.md) * [How to View Email Statistics](email_stats.md) diff --git a/use_cases/flask_heroku.md b/use_cases/flask_heroku.md new file mode 100644 index 000000000..ef0fa599a --- /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! From 6c232045fc7cee179aeae16d75bdaaaad6cd09f4 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 25 Jul 2018 10:05:47 -0700 Subject: [PATCH 086/462] Add PR #496 --- sendgrid/helpers/mail/mail.py | 445 +++++++--------------------------- 1 file changed, 86 insertions(+), 359 deletions(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index d70a0eca8..8813337e9 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -3,413 +3,140 @@ from .header import Header 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, - to_emails=None, - subject=None, - plain_text_content=None, - html_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 - """ - self._from_email = None - self._subject = None - self._template_id = None - self._send_at = None - self._batch_id = None - self._asm = 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 - - if subject: - self.subject = subject.get() - - if to_emails: + """Creates the response body for v3/mail/send""" + def __init__( + self, from_email=None, subject=None, to_email=None, content=None): + self._personalizations = None + self._contents = None + self._attachments = None + self._sections = None + self._headers = None + self._categories = None + self._custom_args = None + self.from_email = None + self.subject = None + self.template_id = None + self.send_at = None + self.batch_id = None + self.asm = None + self.ip_pool_name = None + self.mail_settings = None + self.tracking_settings = None + self.reply_to = None + + # Minimum required to send a single email + if to_email: personalization = Personalization() - personalization.add_to(to_emails) + personalization.add_to(to_email) self.add_personalization(personalization) - - if plain_text_content: - self.add_content(plain_text_content) - - if html_content: - self.add_content(html_content) + if subject: + self.subject = subject + if from_email: + self.from_email = from_email + if content: + self.add_content(content) def __str__(self): - """Get a JSON representation of this Mail request. - - :rtype: string - """ return str(self.get()) def get(self): - """Get a response body for this Mail. - - :rtype: dict """ - mail = {} - - REQUEST_BODY_KEYS = [ - 'attachments', - 'batch_id', - 'categories', - 'custom_args', - 'headers', - 'ip_pool_name', - 'personalizations', - 'sections', - 'send_at', - 'subject', - 'template_id' - ] - - for key in REQUEST_BODY_KEYS: - value = getattr(self, key, None) - if value: - mail[key] = value - - if self.contents: - mail["content"] = self.contents - - if self.from_email: - mail["from"] = self.from_email.get() - - - if self.asm: - mail["asm"] = self.asm.get() - - if self.mail_settings: - mail["mail_settings"] = self.mail_settings.get() - - if self.tracking_settings: - mail["tracking_settings"] = self.tracking_settings.get() - - if self.reply_to: - mail["reply_to"] = self.reply_to.get() - - return mail - - @property - def from_email(self): - """The email from which this Mail will be sent. - - :rtype: string - """ - return self._from_email - - @from_email.setter - def from_email(self, value): - self._from_email = value - - @property - def subject(self): - """The global, or "message level", subject of this Mail. - - This may be overridden by personalizations[x].subject. - :rtype: string - """ - return self._subject - - @subject.setter - def subject(self, value): - self._subject = value - - @property - def template_id(self): - """The id of a template that you would like to use. - - If you use a template that contains a subject and content (either text - or html), you do not need to specify those at the personalizations nor - message level. - - :rtype: int - """ - - return self._template_id - - @template_id.setter - def template_id(self, value): - self._template_id = value - - @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. - - :rtype: int - """ - return self._send_at - - @send_at.setter - def send_at(self, value): - self._send_at = value - - @property - def batch_id(self): - """An ID for this batch of emails. - - 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 - - :rtype: int - """ - return self._batch_id - - @batch_id.setter - def batch_id(self, value): - self._batch_id = value - - @property - def asm(self): - """The ASM for this Mail. - - :rtype: ASM - """ - return self._asm - - @asm.setter - def asm(self, value): - self._asm = value - - @property - def mail_settings(self): - """The MailSettings for this Mail. - - :rtype: MailSettings - """ - return self._mail_settings - - @mail_settings.setter - def mail_settings(self, value): - self._mail_settings = value - - @property - def tracking_settings(self): - """The TrackingSettings for this Mail. - - :rtype: TrackingSettings - """ - return self._tracking_settings - - @tracking_settings.setter - def tracking_settings(self, value): - self._tracking_settings = value - - @property - def ip_pool_name(self): - """The IP Pool that you would like to send this Mail email from. - - :rtype: string - """ - return self._ip_pool_name - - @ip_pool_name.setter - def ip_pool_name(self, value): - self._ip_pool_name = value - - @property - def reply_to(self): - """The email address to use in the Reply-To header. - - :rtype: Email - """ - return self._reply_to - - @reply_to.setter - def reply_to(self, value): - self._reply_to = value + :return: request body dict + """ + mail = { + 'from': self._get_or_none(self.from_email), + 'subject': 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.template_id, + 'sections': self._flatten_dictionaries(self.sections), + 'headers': self._flatten_dictionaries(self.headers), + 'categories': [c.get() for c in self.categories or []], + 'custom_args': self._flatten_dictionaries(self.custom_args), + 'send_at': self.send_at, + 'batch_id': self.batch_id, + 'asm': self._get_or_none(self.asm), + 'ip_pool_name': 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 dict((key, value) for key, value in mail.items() + if value is not None and value != [] and value != {}) + + def _get_or_none(self, from_obj): + return from_obj.get() if from_obj is not None else None + + def _flatten_dictionaries(self, dicts): + list_of_dicts = [d.get() for d in dicts or []] + return dict((k, v) for d in list_of_dicts for k, v in d.items()) @property def personalizations(self): - """The Personalizations applied to this Mail. - - Each object within personalizations can be thought of as an envelope - - it defines who should receive an individual message and how that - message should be handled. A maximum of 1000 personalizations can be - included. - - :returns: List of dictionaries. Each dictionary is obtained by - Personalization.get - :rtype: list(dictionaries) - """ - if self._personalizations is not None: - return [ - personalization.get() - for personalization in self._personalizations - ] + return self._personalizations def add_personalization(self, personalizations): - """Add a new Personalization to this Mail. - - :type personalizations: Personalization - """ - self._personalizations.append(personalizations) + self._personalizations = self._ensure_append( + personalizations, self._personalizations) @property def contents(self): - """The Contents of this Mail. Must include at least one MIME type. - - :returns: List of dictionaries returned by content.get - :rtype: list(Content) - """ - if self._contents is not None: - return[ - ob.get() - for ob in self._contents - ] - return None + return self._contents def add_content(self, content): - """Add a new Content to this Mail. Usually the plaintext or HTML - message contents. - - :type content: Content - """ - if self._contents is None: - self._contents = [] - # Text content should be before HTML content if content._type == "text/plain": - self._contents.insert(0, content) + self._contents = self._ensure_insert(content, self._contents) else: - self._contents.append(content) + self._contents = self._ensure_append(content, self._contents) @property def attachments(self): - """The attachments included with this Mail. - - :returns: List of dictionaries. Each dictionary is obtained by - Attachment.get - :rtype: list(dictionaries) - """ - if self._attachments: - return [ - ob.get() - for ob in self._attachments - ] - return None + return self._attachments def add_attachment(self, attachment): - """Add an Attachment to this Mail. - - :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. - - :returns: List of of dictionaries. Each dictionary is obtained by - Section.get - :rtype: list(dictionaries) - """ - if self._sections: - return self.update_objects(self._sections) - return None + return self._sections def add_section(self, section): - """Add a Section to this Mail. - - :type section: Section - """ - self._sections.append(section) + self._sections = self._ensure_append(section, self._sections) @property def headers(self): - """The Headers included with this Mail. - - :returns: List of dictionaries. Each dictionary is obtained by - Header.get - :rtype: list(dictionaries) - """ - if self._headers: - return self.update_objects(self._headers) - return None + return self._headers def add_header(self, header): - """Add a Header to this Mail. - - The header provided can be a Header or a dictionary with a single - key-value pair. - :type header: object - """ if isinstance(header, dict): (k, v) = list(header.items())[0] - self._headers.append(Header(k, v)) + self._headers = self._ensure_append(Header(k, v), self._headers) else: - self._headers.append(header) + self._headers = self._ensure_append(header, self._headers) @property def categories(self): - """The Categories applied to this Mail. Must not exceed 10 items - - :returns: List of dictionaries. Each dictionary is obtained by - Category.get - :rtype: list(dictionaries) - """ - if self._categories: - return [ - category.get - for category in self._categories - ] + return self._categories def add_category(self, category): - """Add a Category to this Mail. Must be less than 255 characters. - - :type category: string - """ - self._categories.append(category) + self._categories = self._ensure_append(category, self._categories) @property def custom_args(self): - """The CustomArgs attached to this Mail. - - Must not exceed 10,000 characters. - :rtype: list(CustomArg) - """ - if self._custom_args: - return self.update_objects(self._custom_args) - return None + return self._custom_args def add_custom_arg(self, custom_arg): - if self._custom_args is None: - self._custom_args = [] - self._custom_args.append(custom_arg) - - def update_objects(self, property): - objects = {} - for key in property: - objects.update(key.get()) - return objects + self._custom_args = self._ensure_append(custom_arg, self._custom_args) - + def _ensure_append(self, new_items, append_to): + append_to = append_to or [] + append_to.append(new_items) + return append_to + def _ensure_insert(self, new_items, insert_to): + insert_to = insert_to or [] + insert_to.insert(0, new_items) + return insert_to \ No newline at end of file From 2ee538f00c535b5ce0c04cb6c68cc472b507e3b7 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 25 Jul 2018 11:11:38 -0700 Subject: [PATCH 087/462] Formatting --- sendgrid/helpers/mail/mail.py | 154 +++++++++++++++++----------------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 8813337e9..b0ac616f7 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -6,80 +6,76 @@ class Mail(object): """Creates the response body for v3/mail/send""" def __init__( self, from_email=None, subject=None, to_email=None, content=None): - self._personalizations = None - self._contents = None self._attachments = None - self._sections = None - self._headers = None self._categories = None + self._contents = None self._custom_args = None - self.from_email = None - self.subject = None - self.template_id = None - self.send_at = None - self.batch_id = None + self._headers = None + self._personalizations = None + 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.send_at = None + self.subject = None + self.template_id = None + self.tracking_settings = None # Minimum required to send a single email + if from_email: + self.from_email = from_email + if subject: + self.subject = subject if to_email: personalization = Personalization() personalization.add_to(to_email) self.add_personalization(personalization) - if subject: - self.subject = subject - if from_email: - self.from_email = from_email if content: self.add_content(content) def __str__(self): return str(self.get()) - def get(self): - """ - :return: request body dict - """ - mail = { - 'from': self._get_or_none(self.from_email), - 'subject': 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.template_id, - 'sections': self._flatten_dictionaries(self.sections), - 'headers': self._flatten_dictionaries(self.headers), - 'categories': [c.get() for c in self.categories or []], - 'custom_args': self._flatten_dictionaries(self.custom_args), - 'send_at': self.send_at, - 'batch_id': self.batch_id, - 'asm': self._get_or_none(self.asm), - 'ip_pool_name': 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), - } + def _ensure_append(self, new_items, append_to): + append_to = append_to or [] + append_to.append(new_items) + return append_to - return dict((key, value) for key, value in mail.items() - if value is not None and value != [] and value != {}) + def _ensure_insert(self, new_items, insert_to): + insert_to = insert_to or [] + insert_to.insert(0, new_items) + return insert_to + + def _flatten_dicts(self, dicts): + list_of_dicts = [d.get() for d in dicts or []] + return dict((k, v) for d in list_of_dicts for k, v in d.items()) def _get_or_none(self, from_obj): return from_obj.get() if from_obj is not None else None - def _flatten_dictionaries(self, dicts): - list_of_dicts = [d.get() for d in dicts or []] - return dict((k, v) for d in list_of_dicts for k, v in d.items()) + @property + def attachments(self): + return self._attachments + + def add_attachment(self, attachment): + self._attachments = self._ensure_append(attachment, self._attachments) @property - def personalizations(self): - return self._personalizations + def categories(self): + return self._categories - def add_personalization(self, personalizations): - self._personalizations = self._ensure_append( - personalizations, self._personalizations) + def add_category(self, category): + self._categories = self._ensure_append(category, self._categories) + + @property + def custom_args(self): + return self._custom_args + + def add_custom_arg(self, custom_arg): + self._custom_args = self._ensure_append(custom_arg, self._custom_args) @property def contents(self): @@ -92,20 +88,6 @@ def add_content(self, content): else: self._contents = self._ensure_append(content, self._contents) - @property - def attachments(self): - return self._attachments - - def add_attachment(self, attachment): - self._attachments = self._ensure_append(attachment, self._attachments) - - @property - def sections(self): - return self._sections - - def add_section(self, section): - self._sections = self._ensure_append(section, self._sections) - @property def headers(self): return self._headers @@ -118,25 +100,43 @@ def add_header(self, header): self._headers = self._ensure_append(header, self._headers) @property - def categories(self): - return self._categories + def personalizations(self): + return self._personalizations - def add_category(self, category): - self._categories = self._ensure_append(category, self._categories) + def add_personalization(self, personalizations): + self._personalizations = self._ensure_append( + personalizations, self._personalizations) @property - def custom_args(self): - return self._custom_args + def sections(self): + return self._sections - def add_custom_arg(self, custom_arg): - self._custom_args = self._ensure_append(custom_arg, self._custom_args) + def add_section(self, section): + self._sections = self._ensure_append(section, self._sections) - def _ensure_append(self, new_items, append_to): - append_to = append_to or [] - append_to.append(new_items) - return append_to + def get(self): + """ + :return: request body dict + """ + mail = { + 'from': self._get_or_none(self.from_email), + 'subject': 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.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.send_at, + 'batch_id': self.batch_id, + 'asm': self._get_or_none(self.asm), + 'ip_pool_name': 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), + } - def _ensure_insert(self, new_items, insert_to): - insert_to = insert_to or [] - insert_to.insert(0, new_items) - return insert_to \ No newline at end of file + return dict((key, value) for key, value in mail.items() + if value is not None and value != [] and value != {}) From 56a0ad09e7343e0c95321931bea0745927ad61ed Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 25 Jul 2018 11:16:47 -0700 Subject: [PATCH 088/462] Don't forget to thank previous PRs --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c1869666..52f1479e4 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! From b00219775f23d7d934e0bc6787482aa7d7e74679 Mon Sep 17 00:00:00 2001 From: Slam <3lnc.slam@gmail.com> Date: Wed, 1 Aug 2018 20:16:35 +0300 Subject: [PATCH 089/462] Adds support for dynamic template data in personalizations --- examples/helpers/{mail => }/mail_example.py | 26 +++++++++++++++++---- sendgrid/helpers/mail/personalization.py | 9 +++++++ test/test_mail.py | 14 +++++++++-- 3 files changed, 43 insertions(+), 6 deletions(-) rename examples/helpers/{mail => }/mail_example.py (91%) diff --git a/examples/helpers/mail/mail_example.py b/examples/helpers/mail_example.py similarity index 91% rename from examples/helpers/mail/mail_example.py rename to examples/helpers/mail_example.py index b2de7f0a0..e227b22cc 100644 --- a/examples/helpers/mail/mail_example.py +++ b/examples/helpers/mail_example.py @@ -1,8 +1,6 @@ -import json -import os -import urllib2 +from sendgrid import SendGridAPIClient from sendgrid.helpers.mail import * -from sendgrid import * + # NOTE: you will need move this file to the root # directory of this project to execute properly. @@ -217,3 +215,23 @@ def send_kitchen_sink(): # this will only send an email if you set SandBox Mode to False send_kitchen_sink() + + +def dynamic_template_usage(): + """ + Sample usage of dynamic (handlebars) transactional templates. + To make this work, you should have dynamic template created within your + SendGrid account. For this particular example, template may be like:: + +

Hello, {{name}}! Your current balance is {{balance}}

+ + """ + mail = Mail(from_email='templates@sendgrid.com') + mail.template_id = 'd-your-dynamic-template-uid' + p = Personalization() + p.add_to(Email('user@example.com')) + p.dynamic_template_data = {'name': 'Bob', 'balance': 42} + mail.add_personalization(p) + + sg = SendGridAPIClient(apikey='SG.your-api-key') + sg.client.mail.send.post(request_body=mail.get()) diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py index 8bb4bed0b..8032af958 100644 --- a/sendgrid/helpers/mail/personalization.py +++ b/sendgrid/helpers/mail/personalization.py @@ -1,6 +1,10 @@ class Personalization(object): """A Personalization defines who should receive an individual message and how that message should be handled. + + :var dynamic_template_data: data for dynamic transactional template. + Should be JSON-serializeable structure. No pre-processing sill be done + prior to sending this via http client. """ def __init__(self): @@ -13,6 +17,7 @@ def __init__(self): self._substitutions = [] self._custom_args = [] self._send_at = None + self.dynamic_template_data = None @property def tos(self): @@ -198,4 +203,8 @@ def get(self): if self.send_at is not None: personalization["send_at"] = self.send_at + + if self.dynamic_template_data is not None: + personalization['dynamic_template_data'] = self.dynamic_template_data + return personalization diff --git a/test/test_mail.py b/test/test_mail.py index 7721b5205..fb46d34c2 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -80,7 +80,6 @@ def test_sendgridAPIKey(self): else: self.fail("Should have failed as SendGrid API key included") - def test_helloEmail(self): self.max_diff = None @@ -130,7 +129,7 @@ def test_helloEmailAdditionalContent(self): 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")) @@ -562,3 +561,14 @@ def test_disable_tracking(self): def test_directly_setting_substitutions(self): personalization = Personalization() personalization.substitutions = [{'a': 0}] + + def test_dynamic_template_data(self): + p = Personalization() + p.add_to(Email('test@sendgrid.com')) + p.dynamic_template_data = {'customer': {'name': 'Bob', 'returning': True}, 'total': 42} + + expected = { + 'to': [{'email': 'test@sendgrid.com'}], + 'dynamic_template_data': {'customer': {'name': 'Bob', 'returning': True}, 'total': 42} + } + self.assertDictEqual(p.get(), expected) From 210a4fa63a10ad88a2b09ae716344155a180d73a Mon Sep 17 00:00:00 2001 From: Slam <3lnc.slam@gmail.com> Date: Wed, 1 Aug 2018 20:30:30 +0300 Subject: [PATCH 090/462] Example for .md usecase --- use_cases/transational_templates.md | 32 ++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/use_cases/transational_templates.md b/use_cases/transational_templates.md index d3e3a005d..1e89e08a9 100644 --- a/use_cases/transational_templates.md +++ b/use_cases/transational_templates.md @@ -66,6 +66,36 @@ print(response.body) print(response.headers) ``` +### With dynamic templates + +Sendgrid dynamic templates let you leverage power of [handlebars](https://handlebarsjs.com/) +syntax to easily manage complex dynamic content in transactional emails. + +To check this example snippet, create transactional email with code like +```html +

Hello, {{name}}! Your current balance is {{balance}}

+``` + +Than send email based on it, providing context for substitutions: +```python +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Email, Personalization + + +sg = SendGridAPIClient(apikey='SG.your-api-key') + +mail = Mail(from_email='templates@example.com') +mail.template_id = 'd-your-dynamic-template-uid' +p = Personalization() +p.add_to(Email('user@example.com')) +p.dynamic_template_data = {'name': 'Bob', 'balance': 42} +mail.add_personalization(p) + +sg.client.mail.send.post(request_body=mail.get()) +``` + +Read more about dynamic templates in [docs](https://sendgrid.com/docs/User_Guide/Transactional_Templates/how_to_send_an_email_with_transactional_templates.html) + ## Without Mail Helper Class ```python @@ -113,4 +143,4 @@ except urllib.HTTPError as e: print(response.status_code) print(response.body) print(response.headers) -``` \ No newline at end of file +``` From 8ff82a83abe14c2d5910b56fad8ccc3cbf53bc74 Mon Sep 17 00:00:00 2001 From: Slam <3lnc.slam@gmail.com> Date: Wed, 1 Aug 2018 20:35:15 +0300 Subject: [PATCH 091/462] Linking SG dashboard --- use_cases/transational_templates.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/use_cases/transational_templates.md b/use_cases/transational_templates.md index 1e89e08a9..d88ffae76 100644 --- a/use_cases/transational_templates.md +++ b/use_cases/transational_templates.md @@ -71,7 +71,8 @@ print(response.headers) Sendgrid dynamic templates let you leverage power of [handlebars](https://handlebarsjs.com/) syntax to easily manage complex dynamic content in transactional emails. -To check this example snippet, create transactional email with code like +To check this example snippet, create +[transactional email template](https://sendgrid.com/dynamic_templates) with code like ```html

Hello, {{name}}! Your current balance is {{balance}}

``` From 65f4857a800c379620f216426f0417c9e78d440c Mon Sep 17 00:00:00 2001 From: lifez Date: Fri, 3 Aug 2018 17:56:12 +0700 Subject: [PATCH 092/462] Change type of category in Mail.add_category from string to Category --- sendgrid/helpers/mail/mail.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 6554d4508..22a3fad66 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -8,10 +8,10 @@ class Mail(object): Use get() to get the request body. """ - def __init__(self, - from_email=None, - subject=None, - to_email=None, + def __init__(self, + from_email=None, + subject=None, + to_email=None, content=None): """Create a Mail object. @@ -73,7 +73,7 @@ def get(self): if self.from_email is not None: mail["from"] = self.from_email.get() - + if self.subject is not None: mail["subject"] = self.subject @@ -306,7 +306,7 @@ def add_content(self, content): """ if self._contents is None: self._contents = [] - + # Text content should be before HTML content if content._type == "text/plain": self._contents.insert(0, content) @@ -376,9 +376,9 @@ def categories(self): return self._categories def add_category(self, category): - """Add a Category to this Mail. Must be less than 255 characters. + """Add a Category to this Mail. - :type category: string + :type category: Category """ self._categories.append(category) From 0ca2578c19b5626d1a60964e1d347b173f9ee65e Mon Sep 17 00:00:00 2001 From: lifez Date: Fri, 3 Aug 2018 18:18:34 +0700 Subject: [PATCH 093/462] Install pip for python3.6 --- docker/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index e891e497c..cd36ea199 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -25,14 +25,15 @@ 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 From 382b5e87d53865ffce702e9bbf4d3a05a3d3d2c6 Mon Sep 17 00:00:00 2001 From: Ryan Jarvis Date: Fri, 3 Aug 2018 16:30:39 -0700 Subject: [PATCH 094/462] Update TROUBLESHOOTING.md --- TROUBLESHOOTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 29c0c8e89..331aee8e7 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -50,7 +50,7 @@ import urllib try: response = sg.client.mail.send.post(request_body=mail.get()) except urllib.error.HTTPError as e: - print e.read() + print(e.read()) ``` From f2c48e33f12bedf5f688f1a1617ba1575ad9e812 Mon Sep 17 00:00:00 2001 From: Anshul Singhal Date: Mon, 6 Aug 2018 13:05:57 -0700 Subject: [PATCH 095/462] Updated README for more info --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dbcebafdc..d8823e2f0 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ We appreciate your continued support, thank you! * [License](#license) + # Installation ## Prerequisites @@ -47,7 +48,7 @@ We appreciate your continued support, thank you! ## Setup Environment Variables -Update the development environment with your [SENDGRID_API_KEY](https://app.sendgrid.com/settings/api_keys), for example: +Update the development environment with your [SENDGRID_API_KEY](https://app.sendgrid.com/settings/api_keys) (more info [here](https://sendgrid.com/docs/User_Guide/Settings/api_keys.html)), for example: ```bash echo "export SENDGRID_API_KEY='YOUR_API_KEY'" > sendgrid.env @@ -67,6 +68,7 @@ pip install sendgrid - [Python-HTTP-Client](https://github.com/sendgrid/python-http-client) + # Quick Start From 6adc94886d389c00dafb005cb56ec292b1bd6af1 Mon Sep 17 00:00:00 2001 From: af4ro <1997ansul@gmail.com> Date: Mon, 6 Aug 2018 16:23:41 -0700 Subject: [PATCH 096/462] fixed assertion error --- test/test_project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_project.py b/test/test_project.py index e5f05de36..a9edc8195 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -9,7 +9,7 @@ class ProjectTests(unittest.TestCase): # ./Docker or docker/Docker def test_docker_dir(self): - self.assertTrue(os.path.isdir("./docker/Dockerfile")) + self.assertTrue(os.path.isfile("./docker/Dockerfile")) # ./docker-compose.yml or ./docker/docker-compose.yml def test_docker_compose(self): From 946a683fc04b2fe5a4415bb32fbb6e071bfabc9d Mon Sep 17 00:00:00 2001 From: Anshul Singhal Date: Tue, 7 Aug 2018 11:27:00 -0700 Subject: [PATCH 097/462] readme tag update --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dbcebafdc..fddcc69d2 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![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:** From e1cc25685ffc8ea0d5c7ebc22c083f62cf0169ab Mon Sep 17 00:00:00 2001 From: af4ro <1997anshul@gmail.com> Date: Thu, 9 Aug 2018 09:39:27 -0700 Subject: [PATCH 098/462] minor formatting --- examples/helpers/mail_example.py | 8 ++++++-- test/test_mail.py | 16 ++++++++++++++-- use_cases/transational_templates.md | 3 ++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/examples/helpers/mail_example.py b/examples/helpers/mail_example.py index e227b22cc..0a5b868bc 100644 --- a/examples/helpers/mail_example.py +++ b/examples/helpers/mail_example.py @@ -226,11 +226,15 @@ def dynamic_template_usage():

Hello, {{name}}! Your current balance is {{balance}}

""" - mail = Mail(from_email='templates@sendgrid.com') + mail = Mail() + mail.from_email = 'templates@sendgrid.com' mail.template_id = 'd-your-dynamic-template-uid' p = Personalization() p.add_to(Email('user@example.com')) - p.dynamic_template_data = {'name': 'Bob', 'balance': 42} + p.dynamic_template_data = { + 'name': 'Bob', + 'balance': 42 + } mail.add_personalization(p) sg = SendGridAPIClient(apikey='SG.your-api-key') diff --git a/test/test_mail.py b/test/test_mail.py index fb46d34c2..08d0feb8e 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -565,10 +565,22 @@ def test_directly_setting_substitutions(self): def test_dynamic_template_data(self): p = Personalization() p.add_to(Email('test@sendgrid.com')) - p.dynamic_template_data = {'customer': {'name': 'Bob', 'returning': True}, 'total': 42} + p.dynamic_template_data = { + 'customer': { + 'name': 'Bob', + 'returning': True + }, + 'total': 42 + } expected = { 'to': [{'email': 'test@sendgrid.com'}], - 'dynamic_template_data': {'customer': {'name': 'Bob', 'returning': True}, 'total': 42} + 'dynamic_template_data': { + 'customer': { + 'name': 'Bob', + 'returning': True + }, + 'total': 42 + } } self.assertDictEqual(p.get(), expected) diff --git a/use_cases/transational_templates.md b/use_cases/transational_templates.md index d88ffae76..2d74f92a5 100644 --- a/use_cases/transational_templates.md +++ b/use_cases/transational_templates.md @@ -85,7 +85,8 @@ from sendgrid.helpers.mail import Email, Personalization sg = SendGridAPIClient(apikey='SG.your-api-key') -mail = Mail(from_email='templates@example.com') +mail = Mail() +mail.from_email='templates@example.com' mail.template_id = 'd-your-dynamic-template-uid' p = Personalization() p.add_to(Email('user@example.com')) From 54aa6ec05ced0c9c5577abef118f5a2ecb1ed711 Mon Sep 17 00:00:00 2001 From: Anshul Singhal <1997anshul@gmail.com> Date: Fri, 10 Aug 2018 16:03:54 -0700 Subject: [PATCH 099/462] Minor readability update --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b38f1c1c4..882e52164 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -191,8 +191,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 ``` From 0c2163c3f545e7e7a91d05b6b2fcb037768d83af Mon Sep 17 00:00:00 2001 From: Navin Pai Date: Tue, 14 Aug 2018 03:00:04 +0530 Subject: [PATCH 100/462] Add slack API integration to use_cases --- use_cases/README.md | 1 + use_cases/slack_event_api_integration.md | 46 ++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 use_cases/slack_event_api_integration.md diff --git a/use_cases/README.md b/use_cases/README.md index 188464d09..ea8156533 100644 --- a/use_cases/README.md +++ b/use_cases/README.md @@ -15,6 +15,7 @@ This directory provides examples for specific use cases of this library. Please * [Asynchronous Mail Send](asynchronous_mail_send.md) * [Attachment](attachment.md) * [Transactional Templates](transational_templates.md) +* [Integrate with Slack Events API](slack_event_api_integration.md) ### Library Features * [Error Handling](error_handling.md) \ 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..d0e0f6140 --- /dev/null +++ b/use_cases/slack_event_api_integration.md @@ -0,0 +1,46 @@ +# 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 +import sendgrid +from sendgrid.helpers.mail import * + +sg = sendgrid.SendGridAPIClient(apikey=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 = Email("slack_integration@example.com") + to_email = Email("test@example.com") + subject = "Psst... Someone needs help!" + content = Content("text/plain", message) + mail = Mail(from_email, subject, to_email, content) + response = sg.client.mail.send.post(request_body=mail.get()) + return response.status_code + +# Start the slack event listener server on port 3000 +slack_events_adapter.start(port=3000) +``` \ No newline at end of file From 5364bf9872b76bc496759b5d8f740e22059e7c43 Mon Sep 17 00:00:00 2001 From: James Purpura Date: Mon, 13 Aug 2018 15:01:44 -0700 Subject: [PATCH 101/462] update TROUBLESHOOTING.md editted "our use cases" link --- TROUBLESHOOTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 29c0c8e89..a2332ae1b 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -111,4 +111,4 @@ print mail.get() # 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#use-cases) for examples of error handling. From 07ed3228313eb4379877867fa81ce3fac18c612b Mon Sep 17 00:00:00 2001 From: Agnes Jang Date: Tue, 14 Aug 2018 13:45:04 -0700 Subject: [PATCH 102/462] Added comment for attachments to be base64 encoded --- examples/helpers/mail/mail_example.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/helpers/mail/mail_example.py b/examples/helpers/mail/mail_example.py index b2de7f0a0..3a4a350a8 100644 --- a/examples/helpers/mail/mail_example.py +++ b/examples/helpers/mail/mail_example.py @@ -80,7 +80,8 @@ def get_mock_personalization_dict(): def build_attachment1(): - """Build attachment mock.""" + """Build attachment mock. Make sure your content is base64 encoded before passing into attachment.content. + Another example: https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/attachment.md""" attachment = Attachment() attachment.content = ("TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNl" "Y3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12") From ff555caf7fdafbcf1dec584951bc3e2efcabc23f Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 16 Aug 2018 09:08:26 -0700 Subject: [PATCH 103/462] WIP --- sendgrid/helpers/mail/attachment.py | 37 ++++++++++++++- sendgrid/helpers/mail/bcc_settings.py | 10 +++- .../helpers/mail/bypass_list_management.py | 5 +- sendgrid/helpers/mail/category.py | 5 +- sendgrid/helpers/mail/click_tracking.py | 10 +++- sendgrid/helpers/mail/custom_arg.py | 18 ++++++-- sendgrid/helpers/mail/email.py | 10 +++- sendgrid/helpers/mail/exceptions.py | 46 ++++++++++++++++--- sendgrid/helpers/mail/footer_settings.py | 15 ++++-- sendgrid/helpers/mail/ganalytics.py | 2 +- sendgrid/helpers/mail/html_content.py | 8 ++-- sendgrid/helpers/mail/mail_settings.py | 37 ++++++++++++++- 12 files changed, 174 insertions(+), 29 deletions(-) diff --git a/sendgrid/helpers/mail/attachment.py b/sendgrid/helpers/mail/attachment.py index 09215f97f..045db6572 100644 --- a/sendgrid/helpers/mail/attachment.py +++ b/sendgrid/helpers/mail/attachment.py @@ -1,14 +1,47 @@ class Attachment(object): """An attachment to be included with an email.""" - def __init__(self): - """Create an empty Attachment.""" + def __init__(self, content=None, type_=None, filename=None, disposition=None, content_id=None): + """Create an Attachment + + :param content: The Base64 encoded content of the attachment + :type content: string, optional + :param filename: The filename of the attachment + :type filename: 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: 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: string, optional + """ self._content = None self._type = None self._filename = None self._disposition = None self._content_id = None + if content is not None: + self.content = content + + if type_ is not None: + self.type = type_ + + if filename is not None: + self.filename = filename + + if disposition is not None: + self.disposition = disposition + + if content_id is not None: + self.content_id = content_id + @property def content(self): """The Base64 encoded content of the attachment. diff --git a/sendgrid/helpers/mail/bcc_settings.py b/sendgrid/helpers/mail/bcc_settings.py index 391792be0..c2456e989 100644 --- a/sendgrid/helpers/mail/bcc_settings.py +++ b/sendgrid/helpers/mail/bcc_settings.py @@ -13,8 +13,14 @@ def __init__(self, enable=None, email=None): :param email: Who should be BCCed. :type email: Email, 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): diff --git a/sendgrid/helpers/mail/bypass_list_management.py b/sendgrid/helpers/mail/bypass_list_management.py index bedc00c3d..308ca9cfb 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): diff --git a/sendgrid/helpers/mail/category.py b/sendgrid/helpers/mail/category.py index f17da4ecf..a3e885538 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): diff --git a/sendgrid/helpers/mail/click_tracking.py b/sendgrid/helpers/mail/click_tracking.py index 90f0b2928..54b98c414 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): diff --git a/sendgrid/helpers/mail/custom_arg.py b/sendgrid/helpers/mail/custom_arg.py index f4d92601e..aae6cdd68 100644 --- a/sendgrid/helpers/mail/custom_arg.py +++ b/sendgrid/helpers/mail/custom_arg.py @@ -8,9 +8,21 @@ class CustomArg(object): """ def __init__(self, key=None, value=None): - """Create a CustomArg with the given key and value.""" - self.key = key - self.value = value + """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 + """ + 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): diff --git a/sendgrid/helpers/mail/email.py b/sendgrid/helpers/mail/email.py index 0cc633986..3486af97b 100644 --- a/sendgrid/helpers/mail/email.py +++ b/sendgrid/helpers/mail/email.py @@ -17,13 +17,19 @@ def __init__(self, email=None, name=None): :param name: Name for this sender or recipient. :type name: string """ + self._name = None + self._email = None + if email and not name: # allows passing emails as "dude Fella " 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 @property def name(self): diff --git a/sendgrid/helpers/mail/exceptions.py b/sendgrid/helpers/mail/exceptions.py index ab4dd9c0c..9cfc63397 100644 --- a/sendgrid/helpers/mail/exceptions.py +++ b/sendgrid/helpers/mail/exceptions.py @@ -8,15 +8,47 @@ class SendGridException(Exception): 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 - """ + """Exception raised for when SendGrid API Key included in message text""" 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): + self._expression = value + + @property + def message(self): + """Explanation of the error + + :rtype: string + """ + return self._message + + @message.setter + def message(self, value): + self._message = value \ No newline at end of file diff --git a/sendgrid/helpers/mail/footer_settings.py b/sendgrid/helpers/mail/footer_settings.py index b50786309..980da575e 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): diff --git a/sendgrid/helpers/mail/ganalytics.py b/sendgrid/helpers/mail/ganalytics.py index b955b7241..61b881dd0 100644 --- a/sendgrid/helpers/mail/ganalytics.py +++ b/sendgrid/helpers/mail/ganalytics.py @@ -41,7 +41,7 @@ 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 + :type field: string :param value: value to be set, ignored if None :type value: Any """ diff --git a/sendgrid/helpers/mail/html_content.py b/sendgrid/helpers/mail/html_content.py index 0c81f9ce6..f51473e53 100644 --- a/sendgrid/helpers/mail/html_content.py +++ b/sendgrid/helpers/mail/html_content.py @@ -4,15 +4,17 @@ class HtmlContent(Content): """HTML content to be included in your email.""" - def __init__(self, value): + def __init__(self, value = None): """Create an HtmlContent with the specified MIME type and value. :param value: The HTML content. :type value: string, optional """ + self._value = None self._validator = ValidateAPIKey() - self.type = "text/html" - self.value = value + + if value is not None: + self.value = value @property def value(self): diff --git a/sendgrid/helpers/mail/mail_settings.py b/sendgrid/helpers/mail/mail_settings.py index 14d1e1a92..665ee81c0 100644 --- a/sendgrid/helpers/mail/mail_settings.py +++ b/sendgrid/helpers/mail/mail_settings.py @@ -1,14 +1,47 @@ 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. From 3ec8e6535baf9a75814aae4cc7f7c539c1af577a Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 16 Aug 2018 09:22:35 -0700 Subject: [PATCH 104/462] Add missing dependencies --- docker-test/entrypoint.sh | 2 +- requirements.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-test/entrypoint.sh b/docker-test/entrypoint.sh index f64d0cccb..abb55b9f2 100644 --- a/docker-test/entrypoint.sh +++ b/docker-test/entrypoint.sh @@ -13,5 +13,5 @@ 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/requirements.txt b/requirements.txt index 34d770b5b..ceae6cf62 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ Flask==0.10.1 PyYAML==3.11 python-http-client==2.2.1 six==1.10.0 +pytest=3.7.1 \ No newline at end of file From 793aad7c3e520e18b391de4a1333d9ec463abd2f Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 16 Aug 2018 12:03:10 -0700 Subject: [PATCH 105/462] Version Bump v5.5.0: Pre-Dynamic Template Roll Up Release --- CHANGELOG.md | 16 +++++++++++++++- docker/README.md | 3 ++- sendgrid/version.py | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c1869666..69c0a5211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,24 @@ # Change Log All notable changes to this project will be documented in this file. +## [5.5.1] - 2018-08-16 ## +### Added +- [PR #588](https://github.com/sendgrid/sendgrid-python/pull/588): Updates the Readme to include environment variable setup in windows. Big thanks to [Bhargav Chandaka](https://github.com/bchandaka) for the PR! +- [PR #599](https://github.com/sendgrid/sendgrid-python/pull/599): Updates the Readme to include additional API Key instruction. Big thanks to [Anshul Singhal](https://github.com/af4ro) for the PR! +- [PR #600](https://github.com/sendgrid/sendgrid-python/pull/600): Add CodeTriage Badge. Big thanks to [Anshul Singhal](https://github.com/af4ro) for the PR! +- [PR #601](https://github.com/sendgrid/sendgrid-python/pull/601): Readability improvements to CONTRIBUTING.md. Big thanks to [Anshul Singhal](https://github.com/af4ro) for the PR! +- [PR #604](https://github.com/sendgrid/sendgrid-python/pull/604): Readability improvements to CONTRIBUTING.md. Big thanks to [agnesjang98](https://github.com/agnesjang98) for the PR! + +### Fixed +- [PR #595](https://github.com/sendgrid/sendgrid-python/pull/595): Change type of category in Mail.add_category from string to Category. Big thanks to [Phawin Khongkhasawan](https://github.com/lifez) for the PR! +- [PR #596](https://github.com/sendgrid/sendgrid-python/pull/596): Fix Docker build. Big thanks to [Phawin Khongkhasawan](https://github.com/lifez) for the PR! +- [PR #598](https://github.com/sendgrid/sendgrid-python/pull/598): Fix python3 print example in TROUBLESHOOTING.md. Big thanks to [Ryan Jarvis](https://github.com/Cabalist) for the PR! +- [PR #603](https://github.com/sendgrid/sendgrid-python/pull/603): Update TROUBLESHOOTING.md to link to correct use cases page. Big thanks to [James Purpura](https://github.com/jpurpura) for the PR! + ## [5.4.1] - 2018-06-26 ## ### Fixed - [PR #585](https://github.com/sendgrid/sendgrid-python/pull/585): Fix typo in `mail_example.py`. Big thanks to [Anurag Anand](https://github.com/theanuraganand) for the PR! -- [PR #583](https://github.com/sendgrid/sendgrid-python/pull/585): Fix `Personalization.substitutions` setter. Trying to set substitutions directly rather than with add_substitution was causing an infinite regress. Big thanks to [Richard Nias](https://github.com/richardnias) for the PR! +- [PR #583](https://github.com/sendgrid/sendgrid-python/pull/583): Fix `Personalization.substitutions` setter. Trying to set substitutions directly rather than with add_substitution was causing an infinite regress. Big thanks to [Richard Nias](https://github.com/richardnias) for the PR! ## [5.4.0] - 2018-06-07 ## ### Added diff --git a/docker/README.md b/docker/README.md index a523dc93d..41c235fcc 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,5 +1,6 @@ # Supported tags and respective `Dockerfile` links - - `v5.4.1`, `latest` [(Dockerfile)](https://github.com/sendgrid/sendgrid-python/blob/master/docker/Dockerfile) + - `v5.5.0`, `latest` [(Dockerfile)](https://github.com/sendgrid/sendgrid-python/blob/master/docker/Dockerfile) + - `v5.4.1` - `v5.4.0` - `v5.3.0` - `v5.2.1` diff --git a/sendgrid/version.py b/sendgrid/version.py index c191754f8..30415fcbd 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1,2 +1,2 @@ -version_info = (5, 4, 1) +version_info = (5, 5, 0) __version__ = '.'.join(str(v) for v in version_info) From 28bd2a5d91c0130ffd832bec4cc95356e0b37ce7 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Mon, 20 Aug 2018 17:05:50 -0700 Subject: [PATCH 106/462] Merge #593 --- docker-test/entrypoint.sh | 2 +- examples/helpers/mail_example.py | 14 ++- sendgrid/helpers/mail/mail.py | 3 + sendgrid/helpers/mail/personalization.py | 14 ++- use_cases/transational_templates.md | 103 +++++++++++++++-------- 5 files changed, 95 insertions(+), 41 deletions(-) diff --git a/docker-test/entrypoint.sh b/docker-test/entrypoint.sh index abb55b9f2..6749e8cf3 100644 --- a/docker-test/entrypoint.sh +++ b/docker-test/entrypoint.sh @@ -13,5 +13,5 @@ fi cd sendgrid-python python3.6 setup.py install -pip install pyyaml six werkzeug flask python-http-client +pip install pyyaml six werkzeug flask python-http-client pytest exec $SHELL diff --git a/examples/helpers/mail_example.py b/examples/helpers/mail_example.py index 65176a6ca..c1cd166ad 100644 --- a/examples/helpers/mail_example.py +++ b/examples/helpers/mail_example.py @@ -218,7 +218,10 @@ def send_kitchen_sink(): send_kitchen_sink() -def dynamic_template_usage(): +def transactional_template_usage(): + # Assumes you set your environment variable: + # https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key + """ Sample usage of dynamic (handlebars) transactional templates. To make this work, you should have dynamic template created within your @@ -228,7 +231,7 @@ def dynamic_template_usage(): """ mail = Mail() - mail.from_email = 'templates@sendgrid.com' + mail.from_email = Email('templates@sendgrid.com') mail.template_id = 'd-your-dynamic-template-uid' p = Personalization() p.add_to(Email('user@example.com')) @@ -238,5 +241,8 @@ def dynamic_template_usage(): } mail.add_personalization(p) - sg = SendGridAPIClient(apikey='SG.your-api-key') - sg.client.mail.send.post(request_body=mail.get()) + sg = SendGridAPIClient() + response = sg.client.mail.send.post(request_body=mail.get()) + print(response.status_code) + print(response.headers) + print(response.body) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 22a3fad66..fd606fd18 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -1,6 +1,7 @@ """v3/mail/send response body builder""" from .personalization import Personalization from .header import Header +from .email import Email class Mail(object): @@ -147,6 +148,8 @@ def from_email(self): @from_email.setter def from_email(self, value): + if isinstance(value, str): + value = Email(value) self._from_email = value @property diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py index 8032af958..c42694fe9 100644 --- a/sendgrid/helpers/mail/personalization.py +++ b/sendgrid/helpers/mail/personalization.py @@ -17,7 +17,7 @@ def __init__(self): self._substitutions = [] self._custom_args = [] self._send_at = None - self.dynamic_template_data = None + self._dynamic_template_data = None @property def tos(self): @@ -163,6 +163,18 @@ def send_at(self): def send_at(self, value): self._send_at = value + @property + def dynamic_template_data(self): + """Data for dynamic transactional template. + + :rtype: JSON-serializeable structure + """ + return self._dynamic_template_data + + @dynamic_template_data.setter + def dynamic_template_data(self, json): + self._dynamic_template_data = json + def get(self): """ Get a JSON-ready representation of this Personalization. diff --git a/use_cases/transational_templates.md b/use_cases/transational_templates.md index 2d74f92a5..d3d587bc0 100644 --- a/use_cases/transational_templates.md +++ b/use_cases/transational_templates.md @@ -1,6 +1,71 @@ -# Transactional Templates +### Transactional Templates -For this example, we assume you have created a [transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). Following is the template content we used for testing. +Sendgrid transactional templates let you leverage power of [handlebars](https://handlebarsjs.com/) +syntax to easily manage complex dynamic content in transactional emails. + +For this example, we assume you have created a [transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/create_and_edit_transactional_templates.html). Following is the template content we used for testing. + +This example also assumes you [set your environment variable](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key) with your SendGrid API Key. + +Template ID (replace with your own): + +```text +d-13b8f94fbcae4ec6b75270d6cb59f932 +``` + +Email Subject: + +```text +{{ subject }} +``` + +Template Body: + +```html + + + + + +Hello {{ name }}, +

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

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

+ + +``` + +```python +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail, Email, Personalization + + +sg = SendGridAPIClient() +mail = Mail() +mail.from_email = Email('templates@example.com') +mail.template_id = 'd-your-dynamic-template-uid' +p = Personalization() +p.add_to(Email('user@example.com')) +p.dynamic_template_data = { + 'subject': 'Dynamic Templates in Python', + 'name': 'Example User', + 'city': 'Denver' +} +mail.add_personalization(p) + +response = sg.client.mail.send.post(request_body=mail.get()) +print(response.status_code) +print(response.headers) +print(response.body) +``` + +Read more about dynamic templates [here](https://sendgrid.com/docs/User_Guide/Transactional_Templates/how_to_send_an_email_with_transactional_templates.html). + +# Legacy Templates + +For this example, we assume you have created a [Legacy Template](https://sendgrid.com/templates). Following is the template content we used for testing. Template ID (replace with your own): @@ -66,38 +131,6 @@ print(response.body) print(response.headers) ``` -### With dynamic templates - -Sendgrid dynamic templates let you leverage power of [handlebars](https://handlebarsjs.com/) -syntax to easily manage complex dynamic content in transactional emails. - -To check this example snippet, create -[transactional email template](https://sendgrid.com/dynamic_templates) with code like -```html -

Hello, {{name}}! Your current balance is {{balance}}

-``` - -Than send email based on it, providing context for substitutions: -```python -from sendgrid import SendGridAPIClient -from sendgrid.helpers.mail import Email, Personalization - - -sg = SendGridAPIClient(apikey='SG.your-api-key') - -mail = Mail() -mail.from_email='templates@example.com' -mail.template_id = 'd-your-dynamic-template-uid' -p = Personalization() -p.add_to(Email('user@example.com')) -p.dynamic_template_data = {'name': 'Bob', 'balance': 42} -mail.add_personalization(p) - -sg.client.mail.send.post(request_body=mail.get()) -``` - -Read more about dynamic templates in [docs](https://sendgrid.com/docs/User_Guide/Transactional_Templates/how_to_send_an_email_with_transactional_templates.html) - ## Without Mail Helper Class ```python @@ -145,4 +178,4 @@ except urllib.HTTPError as e: print(response.status_code) print(response.body) print(response.headers) -``` +``` \ No newline at end of file From 7438ae17406f6f25cab58f9e807576c94a785d3d Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Mon, 20 Aug 2018 17:12:59 -0700 Subject: [PATCH 107/462] Version Bump v5.6.0: Dynamic Template support --- CHANGELOG.md | 6 +++++- docker/README.md | 3 ++- sendgrid/version.py | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69c0a5211..cde406f18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,11 @@ # Change Log All notable changes to this project will be documented in this file. -## [5.5.1] - 2018-08-16 ## +## [5.6.0] - 2018-08-20 ## +### Added +- [PR #593](https://github.com/sendgrid/sendgrid-python/pull/593): Adds support for dynamic template data. Big thanks to [Wojciech Bartosiak](https://github.com/wojtek-fliposports) for the PR! Also, big thanks to []() for [PR #597](https://github.com/sendgrid/sendgrid-python/pull/597)! + +## [5.5.0] - 2018-08-16 ## ### Added - [PR #588](https://github.com/sendgrid/sendgrid-python/pull/588): Updates the Readme to include environment variable setup in windows. Big thanks to [Bhargav Chandaka](https://github.com/bchandaka) for the PR! - [PR #599](https://github.com/sendgrid/sendgrid-python/pull/599): Updates the Readme to include additional API Key instruction. Big thanks to [Anshul Singhal](https://github.com/af4ro) for the PR! diff --git a/docker/README.md b/docker/README.md index 41c235fcc..b439fe892 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,5 +1,6 @@ # Supported tags and respective `Dockerfile` links - - `v5.5.0`, `latest` [(Dockerfile)](https://github.com/sendgrid/sendgrid-python/blob/master/docker/Dockerfile) + - `v5.6.0`, `latest` [(Dockerfile)](https://github.com/sendgrid/sendgrid-python/blob/master/docker/Dockerfile) + - `v5.5.0` - `v5.4.1` - `v5.4.0` - `v5.3.0` diff --git a/sendgrid/version.py b/sendgrid/version.py index 30415fcbd..fffe2c469 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1,2 +1,2 @@ -version_info = (5, 5, 0) +version_info = (5, 6, 0) __version__ = '.'.join(str(v) for v in version_info) From fe61c16513c968b2c33ef034f9cbe4759d6d4614 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Mon, 20 Aug 2018 17:15:55 -0700 Subject: [PATCH 108/462] Fix CHANGELOG attribution --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cde406f18..dd2334ba4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to this project will be documented in this file. ## [5.6.0] - 2018-08-20 ## ### Added -- [PR #593](https://github.com/sendgrid/sendgrid-python/pull/593): Adds support for dynamic template data. Big thanks to [Wojciech Bartosiak](https://github.com/wojtek-fliposports) for the PR! Also, big thanks to []() for [PR #597](https://github.com/sendgrid/sendgrid-python/pull/597)! +- [PR #593](https://github.com/sendgrid/sendgrid-python/pull/593): Adds support for dynamic template data. Big thanks to [Slam](https://github.com/3lnc) for the PR! Also, big thanks to [Wojciech Bartosiak](https://github.com/wojtek-fliposports) for [PR #597](https://github.com/sendgrid/sendgrid-python/pull/597)! ## [5.5.0] - 2018-08-16 ## ### Added From 84f7cf258f77f179dce0ef3bee8dc3b90f4af2f3 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 22 Aug 2018 13:08:57 -0700 Subject: [PATCH 109/462] Typo in comments --- sendgrid/helpers/mail/personalization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py index c42694fe9..1ecbb8678 100644 --- a/sendgrid/helpers/mail/personalization.py +++ b/sendgrid/helpers/mail/personalization.py @@ -3,7 +3,7 @@ class Personalization(object): how that message should be handled. :var dynamic_template_data: data for dynamic transactional template. - Should be JSON-serializeable structure. No pre-processing sill be done + Should be JSON-serializeable structure. No pre-processing will be done prior to sending this via http client. """ From 27b302f05d187d1926cfb08b6b365d544eaa8feb Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Thu, 13 Sep 2018 11:49:12 -0400 Subject: [PATCH 110/462] Fix format of dependency `pytest` --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ceae6cf62..9756ebf8c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ Flask==0.10.1 PyYAML==3.11 python-http-client==2.2.1 six==1.10.0 -pytest=3.7.1 \ No newline at end of file +pytest==3.7.1 \ No newline at end of file From aba7f0449ca22d21be0ae32957e061c6ca64f6f7 Mon Sep 17 00:00:00 2001 From: themousepotato Date: Fri, 28 Sep 2018 09:41:51 +0530 Subject: [PATCH 111/462] fixes #610 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b2b6a1dbd..20bdfbc9b 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ print(response.body) print(response.headers) ``` -The `Mail` constructor creates a [personalization object](https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/personalizations.html) for you. [Here](https://github.com/sendgrid/sendgrid-python/blob/master/examples/helpers/mail/mail_example.py#L16) is an example of how to add it. +The `Mail` constructor creates a [personalization object](https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/personalizations.html) for you. [Here](https://github.com/sendgrid/sendgrid-python/blob/master/examples/helpers/mail_example.py#L16) is an example of how to add it. ### Without Mail Helper Class From 29fa10d3aaf8363ae51d3be49be0e77199fbe169 Mon Sep 17 00:00:00 2001 From: Bharat Raghunathan Date: Mon, 1 Oct 2018 09:41:45 +0530 Subject: [PATCH 112/462] Update README.md by including email Make it clear that the link is a redirect to an email and not to any members page or developer team page --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b2b6a1dbd..295582eb5 100644 --- a/README.md +++ b/README.md @@ -224,7 +224,8 @@ Please see our [troubleshooting guide](https://github.com/sendgrid/sendgrid-pyth # About -sendgrid-python is guided and supported by the SendGrid [Developer Experience Team](mailto:dx@sendgrid.com). +sendgrid-python is guided and supported by the SendGrid Developer Experience Team. +Email the Developer Experience Team [here](mailto:dx@sendgrid.com) 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. From a8907d8a68bc05455b874d58c69393d29c08fb31 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 1 Oct 2018 11:14:04 +0300 Subject: [PATCH 113/462] Fix typos --- .github/PULL_REQUEST_TEMPLATE | 2 +- CONTRIBUTING.md | 4 ++-- README.md | 6 +++--- sendgrid/__init__.py | 2 +- sendgrid/helpers/endpoints/ip/unassigned.py | 2 +- sendgrid/sendgrid.py | 2 +- use_cases/aws.md | 10 +++++----- use_cases/slack_event_api_integration.md | 4 ++-- use_cases/transational_templates.md | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE index 7ad590b42..b3b7a4446 100644 --- a/.github/PULL_REQUEST_TEMPLATE +++ b/.github/PULL_REQUEST_TEMPLATE @@ -21,4 +21,4 @@ Closes #2 - - -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 [SendGrid](mailto:dx@sendgrid.com), or file a GitHub Issue in this repository. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 882e52164..a875bf9c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,7 +46,7 @@ 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. +1. Check the GitHub issues tab if the identified issue has already been reported, if so, please add a +1 to the existing post. 2. Update to the latest version of this code and check if issue has already been fixed 3. Copy and fill in the Bug Report Template we have provided below @@ -242,4 +242,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/README.md b/README.md index b2b6a1dbd..a4aa8c343 100644 --- a/README.md +++ b/README.md @@ -57,10 +57,10 @@ echo "export SENDGRID_API_KEY='YOUR_API_KEY'" > sendgrid.env echo "sendgrid.env" >> .gitignore source ./sendgrid.env ``` -Sendgrid also supports local environment file `.env`. Copy or rename `.env_sample` into `.env` and update [SENDGRID_API_KEY](https://app.sendgrid.com/settings/api_keys) with your key. +SendGrid also supports local 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. -### Windows -Temporarily set the environment variable(accesible only during the current cli session): +### Windows +Temporarily set the environment variable(accessible only during the current cli session): ```bash set SENDGRID_API_KEY=YOUR_API_KEY ``` diff --git a/sendgrid/__init__.py b/sendgrid/__init__.py index 2bbd38b59..1b5100c01 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 diff --git a/sendgrid/helpers/endpoints/ip/unassigned.py b/sendgrid/helpers/endpoints/ip/unassigned.py index 075f19857..ff5edbd73 100644 --- a/sendgrid/helpers/endpoints/ip/unassigned.py +++ b/sendgrid/helpers/endpoints/ip/unassigned.py @@ -21,7 +21,7 @@ def unassigned(data, as_json=False): and the usernames assigned to an IP unassigned returns a listing of the IP addresses that are allocated - but have 0 usera assigned + but have 0 users assigned data (response.body from sg.client.ips.get()) diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index 0f09bd542..cc3450091 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.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 diff --git a/use_cases/aws.md b/use_cases/aws.md index 2ff04bd1f..d07d5769c 100644 --- a/use_cases/aws.md +++ b/use_cases/aws.md @@ -13,7 +13,7 @@ Python 2.6, 2.7, 3.4, or 3.5 are supported by the sendgrid Python library, howev 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 @@ -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 @@ -139,7 +139,7 @@ def handler(event, context): 'headers': {'Content-Type': 'application/json'}} ``` -Note that for the most part, we've simply copied the intial code from the API verification with SendGrid. Some slight modifications were needed to allow it to run as a lambda function, and for the output to be passed cleanly from the API endpoint. +Note that for the most part, we've simply copied the initial code from the API verification with SendGrid. Some slight modifications were needed to allow it to run as a lambda function, and for the output to be passed cleanly from the API endpoint. Change the `test@example.com` emails appropriately so that you may receive the test email. diff --git a/use_cases/slack_event_api_integration.md b/use_cases/slack_event_api_integration.md index d0e0f6140..ecce40695 100644 --- a/use_cases/slack_event_api_integration.md +++ b/use_cases/slack_event_api_integration.md @@ -1,12 +1,12 @@ # 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. +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. +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. diff --git a/use_cases/transational_templates.md b/use_cases/transational_templates.md index d3d587bc0..491d528bd 100644 --- a/use_cases/transational_templates.md +++ b/use_cases/transational_templates.md @@ -1,6 +1,6 @@ ### Transactional Templates -Sendgrid transactional templates let you leverage power of [handlebars](https://handlebarsjs.com/) +SendGrid transactional templates let you leverage power of [handlebars](https://handlebarsjs.com/) syntax to easily manage complex dynamic content in transactional emails. For this example, we assume you have created a [transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/create_and_edit_transactional_templates.html). Following is the template content we used for testing. From 79ec255aa1f9ff1f173f1b17e02cbf783369e948 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 1 Oct 2018 11:32:56 +0300 Subject: [PATCH 114/462] Drop support for EOL Python --- .travis.yml | 4 +--- CONTRIBUTING.md | 11 ++--------- README.md | 2 +- docker/Dockerfile | 2 +- setup.py | 11 +++-------- test/test_app.py | 6 +----- test/test_config.py | 6 ++---- test/test_email.py | 7 +------ test/test_mail.py | 6 +----- test/test_parse.py | 7 ++----- test/test_project.py | 5 +---- test/test_send.py | 7 ++----- test/test_sendgrid.py | 8 +------- tox.ini | 11 +---------- use_cases/aws.md | 2 +- 15 files changed, 21 insertions(+), 74 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5ad33bccf..91c45daab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: python sudo: false cache: pip python: -- '2.6' - '2.7' - '3.4' - '3.5' @@ -11,7 +10,6 @@ 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 @@ -30,7 +28,7 @@ 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 $? diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b38f1c1c4..c4aa2c4a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,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: ##### @@ -122,12 +122,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 +143,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 +152,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 ``` diff --git a/README.md b/README.md index dbcebafdc..ba6468f23 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ We appreciate your continued support, thank you! ## Prerequisites -- Python version 2.6, 2.7, 3.4, 3.5 or 3.6 +- Python version 2.7 and 3.4+ - The SendGrid service, starting at the [free level](https://sendgrid.com/free?source=sendgrid-python) ## Setup Environment Variables diff --git a/docker/Dockerfile b/docker/Dockerfile index e891e497c..798b494e0 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,5 @@ FROM ubuntu:xenial -ENV PYTHON_VERSIONS='python2.6 python2.7 python3.4 python3.5 python3.6' \ +ENV PYTHON_VERSIONS='python2.7 python3.4 python3.5 python3.6' \ OAI_SPEC_URL="https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/oai_stoplight.json" # install testing versions of python, including old versions, from deadsnakes diff --git a/setup.py b/setup.py index 014691b61..3d93f4636 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -import sys import os from io import open from setuptools import setup, find_packages @@ -14,10 +13,6 @@ def getRequires(): deps = ['python_http_client>=3.0'] - if sys.version_info < (2, 7): - deps.append('unittest2') - elif (3, 0) <= sys.version_info < (3, 2): - deps.append('unittest2py3k') return deps setup( @@ -32,11 +27,11 @@ def getRequires(): description='SendGrid library for Python', long_description=long_description, 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', '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' diff --git a/test/test_app.py b/test/test_app.py index 1a8e4a698..d628f3241 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): diff --git a/test/test_config.py b/test/test_config.py index 301bacc92..6f7ffb81b 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): diff --git a/test/test_email.py b/test/test_email.py index 902c59d4e..3ea38f588 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): diff --git a/test/test_mail.py b/test/test_mail.py index 7721b5205..26fe77aa0 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import json +import unittest from sendgrid.helpers.mail import ( ASM, @@ -29,11 +30,6 @@ ValidateAPIKey ) -try: - import unittest2 as unittest -except ImportError: - import unittest - class UnitTests(unittest.TestCase): 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..89728182c 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -1,9 +1,6 @@ import os +import unittest -try: - import unittest2 as unittest -except ImportError: - import unittest class ProjectTests(unittest.TestCase): diff --git a/test/test_send.py b/test/test_send.py index 16d496b85..360ea7a82 100644 --- a/test/test_send.py +++ b/test/test_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 diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index c545cbb2d..42db06227 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -1,15 +1,9 @@ import sendgrid from sendgrid.helpers.mail import * from sendgrid.version import __version__ -try: - import unittest2 as unittest -except ImportError: - import unittest import os -import subprocess -import sys -import time import datetime +import unittest host = "http://localhost:4010" diff --git a/tox.ini b/tox.ini index 9336a97b8..2f35f4872 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 [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} diff --git a/use_cases/aws.md b/use_cases/aws.md index 2ff04bd1f..9c30fd7ed 100644 --- a/use_cases/aws.md +++ b/use_cases/aws.md @@ -9,7 +9,7 @@ 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/). From 7da6749432f69d0d37cb49c87acc29b5678c1a38 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 3 Oct 2018 09:00:43 +0300 Subject: [PATCH 115/462] Upgrade Python syntax with pyupgrade --- sendgrid/helpers/mail/mail.py | 10 +++++----- sendgrid/sendgrid.py | 4 ++-- test/test_email.py | 2 +- test/test_sendgrid.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index b0ac616f7..974d005c1 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -26,9 +26,9 @@ def __init__( # Minimum required to send a single email if from_email: - self.from_email = from_email + self.from_email = from_email if subject: - self.subject = subject + self.subject = subject if to_email: personalization = Personalization() personalization.add_to(to_email) @@ -51,7 +51,7 @@ def _ensure_insert(self, new_items, insert_to): def _flatten_dicts(self, dicts): list_of_dicts = [d.get() for d in dicts or []] - return dict((k, v) for d in list_of_dicts for k, v in d.items()) + return {k: v for d in list_of_dicts for k, v in d.items()} def _get_or_none(self, from_obj): return from_obj.get() if from_obj is not None else None @@ -138,5 +138,5 @@ def get(self): 'reply_to': self._get_or_none(self.reply_to), } - return dict((key, value) for key, value in mail.items() - if value is not None and value != [] and value != {}) + return {key: value for key, value in mail.items() + if value is not None and value != [] and value != {}} diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index c044196a3..5be0a651f 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -69,7 +69,7 @@ def __init__( self.apikey = apikey or 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.useragent = 'sendgrid/{};python'.format(__version__) self.version = __version__ self.client = python_http_client.Client(host=self.host, @@ -79,7 +79,7 @@ def __init__( @property def _default_headers(self): headers = { - "Authorization": 'Bearer {0}'.format(self.apikey), + "Authorization": 'Bearer {}'.format(self.apikey), "User-agent": self.useragent, "Accept": 'application/json' } diff --git a/test/test_email.py b/test/test_email.py index 3ea38f588..8213a20c9 100644 --- a/test/test_email.py +++ b/test/test_email.py @@ -35,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") diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index 42db06227..603c500d2 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -13,7 +13,7 @@ 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) @@ -95,7 +95,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/', __version__, ';python') self.assertEqual(self.sg.useragent, useragent) def test_host(self): From 76df67eb43034b0bf3672293798a13bcef27c847 Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 3 Oct 2018 09:07:39 +0300 Subject: [PATCH 116/462] Upgrade unit tests to use more useful asserts --- test/test_config.py | 2 +- test/test_mail.py | 4 ++-- test/test_sendgrid.py | 19 ++++++++++++++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/test/test_config.py b/test/test_config.py index 6f7ffb81b..715eb685d 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -37,7 +37,7 @@ 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__ diff --git a/test/test_mail.py b/test/test_mail.py index 26fe77aa0..079795839 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -109,7 +109,7 @@ def test_helloEmail(self): '"subject": "Hello World from the SendGrid Python Library"}' ) - self.assertTrue(isinstance(str(mail), str)) + self.assertIsInstance(str(mail), str) def test_helloEmailAdditionalContent(self): """Tests bug found in Issue-451 with Content ordering causing a crash""" @@ -142,7 +142,7 @@ def test_helloEmailAdditionalContent(self): '"subject": "Hello World from the SendGrid Python Library"}' ) - self.assertTrue(isinstance(str(mail), str)) + self.assertIsInstance(str(mail), str) def test_kitchenSink(self): self.max_diff = None diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index 603c500d2..cb8c7c245 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -19,7 +19,7 @@ def setUpClass(cls): 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: @@ -138,8 +138,21 @@ def test_hello_world(self): 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'}) + self.assertEqual( + 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} From 3b01e12e364d7559b9c6309cb4a0ce4e6f31dbff Mon Sep 17 00:00:00 2001 From: hugovk Date: Wed, 3 Oct 2018 09:21:29 +0300 Subject: [PATCH 117/462] Fix flake8 and code inspections --- sendgrid/helpers/inbound/send.py | 4 +++- sendgrid/helpers/mail/content.py | 1 + sendgrid/helpers/mail/exceptions.py | 6 +++--- sendgrid/helpers/mail/from_email.py | 3 ++- sendgrid/helpers/mail/html_content.py | 1 + sendgrid/helpers/mail/mail.py | 1 + sendgrid/helpers/mail/plain_text_content.py | 1 + sendgrid/helpers/mail/to_email.py | 3 ++- sendgrid/helpers/mail/validators.py | 10 ++++------ setup.py | 1 + test/test_app.py | 2 +- test/test_mail.py | 9 ++++----- test/test_project.py | 1 + test/test_sendgrid.py | 3 +-- 14 files changed, 26 insertions(+), 20 deletions(-) diff --git a/sendgrid/helpers/inbound/send.py b/sendgrid/helpers/inbound/send.py index 6de575aab..e3526eb7c 100644 --- a/sendgrid/helpers/inbound/send.py +++ b/sendgrid/helpers/inbound/send.py @@ -37,6 +37,7 @@ def url(self): """URL to send to.""" return self._url + def main(): config = Config() parser = argparse.ArgumentParser(description='Test data and optional host.') @@ -54,5 +55,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/content.py b/sendgrid/helpers/mail/content.py index cff8ac498..da4ed8027 100644 --- a/sendgrid/helpers/mail/content.py +++ b/sendgrid/helpers/mail/content.py @@ -1,5 +1,6 @@ from .validators import ValidateAPIKey + class Content(object): """Content to be included in your email. diff --git a/sendgrid/helpers/mail/exceptions.py b/sendgrid/helpers/mail/exceptions.py index ab4dd9c0c..1b5da92fc 100644 --- a/sendgrid/helpers/mail/exceptions.py +++ b/sendgrid/helpers/mail/exceptions.py @@ -2,6 +2,7 @@ # Various types of extensible SendGrid related exceptions ################################################################ + class SendGridException(Exception): """Wrapper/default SendGrid-related exception""" pass @@ -14,9 +15,8 @@ class APIKeyIncludedException(SendGridException): message -- explanation of the error """ - def __init__(self, - expression="Email body", + def __init__(self, + expression="Email body", message="SendGrid API Key detected"): self.expression = expression self.message = message - diff --git a/sendgrid/helpers/mail/from_email.py b/sendgrid/helpers/mail/from_email.py index c12eeb4ac..0f6f231ce 100644 --- a/sendgrid/helpers/mail/from_email.py +++ b/sendgrid/helpers/mail/from_email.py @@ -1,4 +1,5 @@ from .email import Email + class From(Email): - """A from email address with an optional name.""" \ No newline at end of file + """A from email address with an optional name.""" diff --git a/sendgrid/helpers/mail/html_content.py b/sendgrid/helpers/mail/html_content.py index 0c81f9ce6..71c018b4a 100644 --- a/sendgrid/helpers/mail/html_content.py +++ b/sendgrid/helpers/mail/html_content.py @@ -1,6 +1,7 @@ from .content import Content from .validators import ValidateAPIKey + class HtmlContent(Content): """HTML content to be included in your email.""" diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 974d005c1..f374e6ad1 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -2,6 +2,7 @@ from .personalization import Personalization from .header import Header + class Mail(object): """Creates the response body for v3/mail/send""" def __init__( diff --git a/sendgrid/helpers/mail/plain_text_content.py b/sendgrid/helpers/mail/plain_text_content.py index 9c55d12d9..1a3c00191 100644 --- a/sendgrid/helpers/mail/plain_text_content.py +++ b/sendgrid/helpers/mail/plain_text_content.py @@ -1,6 +1,7 @@ from .content import Content from .validators import ValidateAPIKey + class PlainTextContent(Content): """Plain text content to be included in your email. """ diff --git a/sendgrid/helpers/mail/to_email.py b/sendgrid/helpers/mail/to_email.py index 18ef8f725..e4f294f03 100644 --- a/sendgrid/helpers/mail/to_email.py +++ b/sendgrid/helpers/mail/to_email.py @@ -1,4 +1,5 @@ from .email import Email + class To(Email): - """A to email address with an optional name.""" \ No newline at end of file + """A to email address with an optional name.""" diff --git a/sendgrid/helpers/mail/validators.py b/sendgrid/helpers/mail/validators.py index b4a69f697..816ec71a4 100644 --- a/sendgrid/helpers/mail/validators.py +++ b/sendgrid/helpers/mail/validators.py @@ -3,6 +3,7 @@ # Various types of Validators ################################################################ + class ValidateAPIKey(object): """Validates content to ensure SendGrid API key is not present""" @@ -27,9 +28,8 @@ def __init__(self, regex_strings=None, use_default=True): default_regex_string = '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, + """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 @@ -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,7 +54,6 @@ 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 @@ -68,4 +67,3 @@ def validate_message_text(self, message_string): for regex in self.regexes: if regex.match(message_string) is not None: raise APIKeyIncludedException() - diff --git a/setup.py b/setup.py index 3d93f4636..5ccaa10f5 100644 --- a/setup.py +++ b/setup.py @@ -15,6 +15,7 @@ def getRequires(): deps = ['python_http_client>=3.0'] return deps + setup( name='sendgrid', version=str(__version__), diff --git a/test/test_app.py b/test/test_app.py index d628f3241..56027d570 100644 --- a/test/test_app.py +++ b/test/test_app.py @@ -19,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_mail.py b/test/test_mail.py index 079795839..98af29a09 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -48,7 +48,7 @@ def test_sendgridAPIKey(self): personalization.add_to(Email("test@example.com")) mail.add_personalization(personalization) - #Try to include SendGrid API key + # Try to include SendGrid API key try: mail.add_content(Content("text/plain", "some SG.2123b1B.1212lBaC here")) mail.add_content( @@ -68,15 +68,14 @@ def test_sendgridAPIKey(self): '"subject": "Hello World from the SendGrid Python Library"}' ) - #Exception should be thrown + # Exception should be thrown except Exception as e: pass - #Exception not thrown + # Exception not thrown else: self.fail("Should have failed as SendGrid API key included") - def test_helloEmail(self): self.max_diff = None @@ -126,7 +125,7 @@ def test_helloEmailAdditionalContent(self): 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")) diff --git a/test/test_project.py b/test/test_project.py index 89728182c..a0b87908a 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -72,5 +72,6 @@ def test_usage(self): 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 cb8c7c245..214eb2de9 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -128,7 +128,7 @@ 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): @@ -153,7 +153,6 @@ def test_hello_world(self): }, ) - def test_access_settings_activity_get(self): params = {'limit': 1} headers = {'X-Mock': 200} From 1a1d3a50e326f66222ac6e193cfa84b02e7fd365 Mon Sep 17 00:00:00 2001 From: Bharat123Rox Date: Wed, 3 Oct 2018 13:06:39 +0530 Subject: [PATCH 118/462] Update README.md for Email and fix alphabetical order --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f019c1ce3..c08e417fc 100644 --- a/README.md +++ b/README.md @@ -212,9 +212,9 @@ 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) +- [Sign the CLA to Create a Pull Request](https://cla.sendgrid.com/sendgrid/sendgrid-python) # Troubleshooting @@ -224,7 +224,9 @@ Please see our [troubleshooting guide](https://github.com/sendgrid/sendgrid-pyth # About -sendgrid-python is guided and supported by the SendGrid [Developer Experience Team](mailto:dx@sendgrid.com). +sendgrid-python is guided and supported by the SendGrid Developer Experience Team. + +Email the team [here](mailto:dx@sendgrid.com) 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. From 164c08adb9fd710da65013f961589f059d390f0b Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Thu, 13 Sep 2018 12:04:28 -0400 Subject: [PATCH 119/462] Quote names containing comma or semicolon --- sendgrid/helpers/mail/email.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sendgrid/helpers/mail/email.py b/sendgrid/helpers/mail/email.py index 0cc633986..ea4f240e0 100644 --- a/sendgrid/helpers/mail/email.py +++ b/sendgrid/helpers/mail/email.py @@ -3,6 +3,20 @@ 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.""" @@ -35,6 +49,12 @@ def name(self): @name.setter def name(self, value): + if not (value is None or isinstance(value, str)): + raise TypeError('name must be of type string.') + + if value is not None and (',' in value or ';' in value): + value = html_entity_decode(value) + value = '"' + value + '"' self._name = value @property From d53d19369dc2d50f5e803cfae33e1a879d9c6510 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Mon, 1 Oct 2018 09:04:43 -0400 Subject: [PATCH 120/462] add explanatory comment --- sendgrid/helpers/mail/email.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sendgrid/helpers/mail/email.py b/sendgrid/helpers/mail/email.py index ea4f240e0..432c9809e 100644 --- a/sendgrid/helpers/mail/email.py +++ b/sendgrid/helpers/mail/email.py @@ -52,6 +52,8 @@ def name(self, value): 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 + '"' From 0042b7bb3da9919487026f2c13b54cd88a05ae00 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Mon, 1 Oct 2018 09:29:47 -0400 Subject: [PATCH 121/462] Add test for comma in name --- test/test_email.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/test_email.py b/test/test_email.py index 902c59d4e..44be5c256 100644 --- a/test/test_email.py +++ b/test/test_email.py @@ -58,3 +58,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) From 32183b70b548cb8d6cfb27ba7ca1eb40d66c7dd9 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Mon, 1 Oct 2018 09:36:04 -0400 Subject: [PATCH 122/462] Add quotes around expected value in new test --- test/test_email.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_email.py b/test/test_email.py index 44be5c256..0604ef250 100644 --- a/test/test_email.py +++ b/test/test_email.py @@ -64,4 +64,4 @@ def test_add_name_with_comma(self): name = "Name, Some" email.name = name - self.assertEqual(email.name, name) + self.assertEqual(email.name, '"' + name + '"') From 80788ede975a874673ad412b1113f83c771ab027 Mon Sep 17 00:00:00 2001 From: Jeremy Yang Date: Wed, 3 Oct 2018 07:37:24 -0700 Subject: [PATCH 123/462] Update README Remove link to a position not found and replaced with general link to the careers page. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f019c1ce3..c70ef1f26 100644 --- a/README.md +++ b/README.md @@ -192,7 +192,7 @@ Please see [our helper](https://github.com/sendgrid/sendgrid-python/tree/master/ # 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) +Join an experienced and passionate team that focuses on making an impact. [Opportunities abound](https://sendgrid.com/careers) to grow the product - and grow your career! Please see our announcement regarding [breaking changes](https://github.com/sendgrid/sendgrid-python/issues/217). Your support is appreciated! From e4f1097e2a1d7da9d7353b86eaad065cd4d6efa8 Mon Sep 17 00:00:00 2001 From: Rahul Arulkumaran Date: Thu, 4 Oct 2018 10:14:33 +0530 Subject: [PATCH 124/462] Update requirements.txt --- requirements.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 34d770b5b..a6646a692 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==3.13 +python-http-client==3.1.0 +six==1.11.0 +pytest==3.8.2 From ce152d814e942b7876cdbece3fac091f3bb944dc Mon Sep 17 00:00:00 2001 From: Matthew Egan Date: Fri, 5 Oct 2018 11:04:50 +1000 Subject: [PATCH 125/462] Fix broken link for mail example --- sendgrid/helpers/mail/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sendgrid/helpers/mail/README.md b/sendgrid/helpers/mail/README.md index 09e3e2035..1d39fa1a6 100644 --- a/sendgrid/helpers/mail/README.md +++ b/sendgrid/helpers/mail/README.md @@ -11,5 +11,5 @@ python mail_settings.py ## 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 +- See the [examples](https://github.com/sendgrid/sendgrid-python/blob/master/examples/mail/mail.py) for complete working examples. +- [Documentation](https://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/overview.html) From bf610ae83ab694dd51dbf3a24146f0606d02b012 Mon Sep 17 00:00:00 2001 From: Matthew Egan Date: Fri, 5 Oct 2018 11:18:08 +1000 Subject: [PATCH 126/462] Fix link to actual helper examples --- sendgrid/helpers/mail/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/helpers/mail/README.md b/sendgrid/helpers/mail/README.md index 1d39fa1a6..c065f5b98 100644 --- a/sendgrid/helpers/mail/README.md +++ b/sendgrid/helpers/mail/README.md @@ -11,5 +11,5 @@ python mail_settings.py ## Usage -- See the [examples](https://github.com/sendgrid/sendgrid-python/blob/master/examples/mail/mail.py) for complete working examples. +- See the [examples](https://github.com/sendgrid/sendgrid-python/blob/master/examples/helpers/mail_example.py) for complete working examples. - [Documentation](https://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/overview.html) From 91a234c7f34b6856fd62335d5ea3d753ca3819e6 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 5 Oct 2018 11:35:14 +0300 Subject: [PATCH 127/462] Add support for Python 3.7 --- .travis.yml | 6 ++++++ setup.py | 4 ++-- tox.ini | 9 +++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 91c45daab..1a8231c0e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,12 @@ python: - '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 diff --git a/setup.py b/setup.py index 5ccaa10f5..2720ec461 100644 --- a/setup.py +++ b/setup.py @@ -30,11 +30,11 @@ def getRequires(): install_requires=getRequires(), python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', '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/tox.ini b/tox.ini index 2f35f4872..926a3c9dd 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py27, py34, py35, py36 +envlist = py27, py34, py35, py36, py37 [testenv] commands = coverage erase @@ -33,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 From f7732dddb17447f573007171c856c7ee18c041f3 Mon Sep 17 00:00:00 2001 From: vinuthegr8 Date: Fri, 5 Oct 2018 18:34:32 +0530 Subject: [PATCH 128/462] Fixed syntax errors in Kitchen sink Python example code --- proposals/mail-helper-refactor.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/mail-helper-refactor.md b/proposals/mail-helper-refactor.md index 5a0127fd6..4f21eb5f7 100644 --- a/proposals/mail-helper-refactor.md +++ b/proposals/mail-helper-refactor.md @@ -191,7 +191,7 @@ 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) @@ -230,13 +230,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: From a25435e4fbf961e22bb16bf8cdbc38478b6e6d3e Mon Sep 17 00:00:00 2001 From: vinuthegr8 Date: Fri, 5 Oct 2018 18:54:49 +0530 Subject: [PATCH 129/462] Fixed ModuleNotFoundError --- sendgrid/__init__.py | 6 +++--- sendgrid/sendgrid.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sendgrid/__init__.py b/sendgrid/__init__.py index 1b5100c01..4d8324235 100644 --- a/sendgrid/__init__.py +++ b/sendgrid/__init__.py @@ -15,7 +15,7 @@ Modules to help with common tasks. """ -from .version import __version__ # noqa +from version import __version__ # noqa # v3 API -from .sendgrid import SendGridAPIClient # noqa -from .helpers.mail import Email # noqa +from sendgrid import SendGridAPIClient # noqa +from helpers.mail import Email # noqa diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index cc3450091..85478b6fd 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -18,7 +18,7 @@ import python_http_client -from .version import __version__ +from version import __version__ class SendGridAPIClient(object): From 5fc6178a70b9f8928577ea02a919d48667a01634 Mon Sep 17 00:00:00 2001 From: vinuthegr8 Date: Fri, 5 Oct 2018 19:37:25 +0530 Subject: [PATCH 130/462] Reverted changes to files in /sendgrid --- sendgrid/__init__.py | 6 +++--- sendgrid/sendgrid.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sendgrid/__init__.py b/sendgrid/__init__.py index 4d8324235..1b5100c01 100644 --- a/sendgrid/__init__.py +++ b/sendgrid/__init__.py @@ -15,7 +15,7 @@ Modules to help with common tasks. """ -from version import __version__ # noqa +from .version import __version__ # noqa # v3 API -from sendgrid import SendGridAPIClient # noqa -from helpers.mail import Email # noqa +from .sendgrid import SendGridAPIClient # noqa +from .helpers.mail import Email # noqa diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index 85478b6fd..cc3450091 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -18,7 +18,7 @@ import python_http_client -from version import __version__ +from .version import __version__ class SendGridAPIClient(object): From 88ddc58b5c036b1cbd9befa5c8d2856c1512a1a0 Mon Sep 17 00:00:00 2001 From: Xeon Zolt Date: Fri, 5 Oct 2018 23:04:02 +0530 Subject: [PATCH 131/462] fixed changes suggested by grammerly --- CHANGELOG.md | 10 +- CODE_OF_CONDUCT.md | 32 +- CONTRIBUTING.md | 14 +- TROUBLESHOOTING.md | 4 +- USAGE.md | 4623 ++++++++++++++++++++++++++- docker-test/README.md | 2 +- docker/USAGE.md | 2 +- proposals/mail-helper-refactor.md | 2 +- sendgrid/helpers/inbound/README.md | 2 +- use_cases/asynchronous_mail_send.md | 2 +- use_cases/aws.md | 24 +- use_cases/django.md | 8 +- use_cases/flask_heroku.md | 2 +- 13 files changed, 4674 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd2334ba4..78b7fa26f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,7 +48,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! @@ -58,13 +58,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! +Updated date-range in the LICENSE file. Big thanks to [Dhruv Srivastava](https://github.com/dhruvhacks) for the PR! - [PR #482](https://github.com/sendgrid/sendgrid-python/pull/482): Typos. Big thanks to [Karan Samani](https://github.com/Kimi450) for the PR! - [PR #504](https://github.com/sendgrid/sendgrid-python/pull/504): Fix .codeclimate.yml. Big thanks to [Matt Bernier](https://github.com/mbernier) for the PR! -- [PR #505](https://github.com/sendgrid/sendgrid-python/pull/505): Remove unnecessary github PR templates. Big thanks to [Alex](https://github.com/pushkyn) for the PR! +- [PR #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! @@ -72,7 +72,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..670a82f62 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,41 +1,41 @@ # SendGrid Community Code of Conduct - + The SendGrid open source community is made up of members from around the globe with a diverse set of skills, personalities, and experiences. It is through these differences that our community experiences successes and continued growth. When you're working with members of the community, we encourage you to follow these guidelines, which help steer our interactions and strive to maintain a positive, successful and growing community. - + ### Be Open Members of the community are open to collaboration, whether it's on pull requests, code reviews, approvals, issues or otherwise. We're receptive to constructive comments and criticism, as the experiences and skill sets of all members contribute to the whole of our efforts. We're accepting of all who wish to take part in our activities, fostering an environment where anyone can participate, and everyone can make a difference. - + ### Be Considerate Members of the community are considerate of their peers, which include other contributors and users of SendGrid. We're thoughtful when addressing the efforts of others, keeping in mind that often the labor was completed with the intent of the good of the community. We're attentive in our communications, whether in person or online, and we're tactful when approaching differing views. - + ### Be Respectful - Members of the community are respectful. We're respectful of others, their positions, their skills, their commitments and their efforts. We're respectful of the volunteer efforts that permeate the SendGrid community. We're respectful of the processes outlined in the community, and we work within them. When we disagree, we are courteous in raising our issues. Overall, we're good to each other. We contribute to this community not because we have to, but because we want to. If we remember that, these guidelines will come naturally. - + Members of the community are respectful. We're respectful of others, their positions, their skills, their commitments ,and their efforts. We're respectful of the volunteer efforts that permeate the SendGrid community. We're respectful of the processes outlined in the community, and we work within them. When we disagree, we are courteous in raising our issues. Overall, we're good with each other. We contribute to this community not because we have to, but because we want to. If we remember that, these guidelines will come naturally. + ## Additional Guidance - + ### Disclose Potential Conflicts of Interest Community discussions often involve interested parties. We expect participants to be aware when they are conflicted due to employment or other projects they are involved in and disclose those interests to other project members. When in doubt, over-disclose. Perceived conflicts of interest are important to address so that the community’s decisions are credible even when unpopular, difficult or favorable to the interests of one group over another. - + ### Interpretation This Code is not exhaustive or complete. It is not a rulebook; it serves to distill our common understanding of a collaborative, shared environment and goals. We expect it to be followed in spirit as much as in the letter. When in doubt, try to abide by [SendGrid’s cultural values](https://sendgrid.com/blog/employee-engagement-the-4h-way) defined by our “4H’s”: Happy, Hungry, Humble and Honest. - + ### Enforcement Most members of the SendGrid community always comply with this Code, not because of the existence of this Code, but because they have long experience participating in open source communities where the conduct described above is normal and expected. However, failure to observe this Code may be grounds for suspension, reporting the user for abuse or changing permissions for outside contributors. - + ## If you have concerns about someone’s conduct **Initiate Direct Contact** - It is always appropriate to email a community member (if contact information is available), mention that you think their behavior was out of line, and (if necessary) point them to this Code. - + **Discuss Publicly** - Discussing publicly is always acceptable. Note, though, that approaching the person directly may be better, as it tends to make them less defensive, and it respects the time of other community members, so you probably want to try direct contact first. - + **Contact the Moderators** - You can reach the SendGrid moderators by emailing dx@sendgrid.com. - + ## Submission to SendGrid Repositories Finally, just a reminder, changes to the SendGrid repositories will only be accepted upon completion of the [SendGrid Contributor Agreement](https://cla.sendgrid.com). - + ## Attribution - + SendGrid thanks the following, on which it draws for content and inspiration: - + [Python Community Code of Conduct](https://www.python.org/psf/codeofconduct/) [Open Source Initiative General Code of Conduct](https://opensource.org/codeofconduct) [Apache Code of Conduct](https://www.apache.org/foundation/policies/conduct.html) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a875bf9c1..90f8a1f42 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,14 +12,14 @@ Hello! Thank you for choosing to help contribute to one of the SendGrid open sou - [Code Reviews](#code-reviews) -We use [Milestones](https://github.com/sendgrid/sendgrid-python/milestones) to help define current roadmaps, please feel free to grab an issue from the current milestone. Please indicate that you have begun work on it to avoid collisions. Once a PR is made, community reviews, comments, suggestions and additional PRs are welcomed and encouraged. +We use [Milestones](https://github.com/sendgrid/sendgrid-python/milestones) to help define current roadmaps, please feel free to grab an issue from the current milestone. Please indicate that you have begun work on it to avoid collisions. Once a PR is made, community reviews, comments, suggestions, and additional PRs are welcomed and encouraged. ## CLAs and CCLAs Before you get started, SendGrid requires that a SendGrid Contributor License Agreement (CLA) be filled out by every contributor to a SendGrid open source project. -Our goal with the CLA is to clarify the rights of our contributors and reduce other risks arising from inappropriate contributions. The CLA also clarifies the rights SendGrid holds in each contribution and helps to avoid misunderstandings over what rights each contributor is required to grant to SendGrid when making a contribution. In this way the CLA encourages broad participation by our open source community and helps us build strong open source projects, free from any individual contributor withholding or revoking rights to any contribution. +Our goal with the CLA is to clarify the rights of our contributors and reduce other risks arising from inappropriate contributions. The CLA also clarifies the rights SendGrid holds in each contribution and helps to avoid misunderstandings over what rights each contributor is required to grant to SendGrid when making a contribution. In this way, the CLA encourages broad participation by our open source community and helps us build strong open source projects, free from any individual contributor withholding or revoking rights to any contribution. SendGrid does not merge a pull request made against a SendGrid open source project until that pull request is associated with a signed CLA. Copies of the CLA are available [here](https://gist.github.com/SendGridDX/98b42c0a5d500058357b80278fde3be8#file-sendgrid_cla). @@ -47,7 +47,7 @@ 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 +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 @@ -107,7 +107,7 @@ Working examples that demonstrate usage. **/tests** -Currently we have unit and profiling tests. +Currently, we have unit and profiling tests. **/sendgrid** @@ -206,14 +206,14 @@ Please run your code through: git pull upstream ``` -3. Create a new topic branch (off the main project development branch) to +3. Create a new topic branch (of the main project development branch) to contain your feature, change, or fix: ```bash git checkout -b ``` -4. Commit your changes in logical chunks. Please adhere to these [git commit +4. Commit your changes in logical chunks. Please adhere to these [git commits message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) or your code is unlikely to be merged into the main project. Use Git's [interactive rebase](https://help.github.com/articles/interactive-rebase) @@ -242,4 +242,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/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 1298ab7d1..a33919a26 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -27,7 +27,7 @@ becomes `apikey='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 @@ -95,7 +95,7 @@ If you are using a [requirements file](https://pip.readthedocs.io/en/1.1/require ## Versioning Convention -We follow the MAJOR.MINOR.PATCH versioning scheme as described by [SemVer.org](http://semver.org). Therefore, we recommend that you always pin (or vendor) the particular version you are working with to your code and never auto-update to the latest version. Especially when there is a MAJOR point release, since that is guaranteed to be a breaking change. Changes are documented in the [CHANGELOG](https://github.com/sendgrid/sendgrid-python/blob/master/CHANGELOG.md) and [releases](https://github.com/sendgrid/sendgrid-python/releases) section. +We follow the MAJOR.MINOR.PATCH versioning scheme as described by [SemVer.org](http://semver.org). Therefore, we recommend that you always pin (or vendor) the particular version you are working with your code and never auto-update to the latest version. Especially when there is a MAJOR point release since that is guaranteed to be a breaking change. Changes are documented in the [CHANGELOG](https://github.com/sendgrid/sendgrid-python/blob/master/CHANGELOG.md) and [releases](https://github.com/sendgrid/sendgrid-python/releases) section. ## Viewing the Request Body diff --git a/USAGE.md b/USAGE.md index 09b78609a..ea2beae39 100644 --- a/USAGE.md +++ b/USAGE.md @@ -70,7 +70,4628 @@ IP Access Management allows you to control which IP addresses can be used to acc For more information, please see our [User Guide](http://sendgrid.com/docs/User_Guide/Settings/ip_access_management.html). -### POST /access_settings/whitelist +This documentation is based on our OAI specification. + +INITIALIZATION + + +import sendgrid +import os + + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +Table of Contents + +ACCESS SETTINGS + +ALERTS + +API KEYS + +ASM + +BROWSERS + +CAMPAIGNS + +CATEGORIES + +CLIENTS + +CONTACTDB + +DEVICES + +GEO + +IPS + +MAIL + +MAIL SETTINGS + +MAILBOX PROVIDERS + +PARTNER SETTINGS + +SCOPES + +SENDERS + +STATS + +SUBUSERS + +SUPPRESSION + +TEMPLATES + +TRACKING SETTINGS + +USER + +WHITELABEL + + + +ACCESS SETTINGS + +Retrieve all recent access attempts + +This endpoint allows you to retrieve a list of all of the IP addresses that recently attempted to access your account either through the User Interface or the API. + +IP Access Management allows you to control which IP addresses can be used to access your account, either through the User Interface or the API. There is no limit to the number of IP addresses that you can add to your whitelist. It is possible to remove your own IP address from the whitelist, thus preventing yourself from accessing your account. + +For more information, please see our User Guide. + +GET /access_settings/activity + + +params = {'limit': 1} +response = sg.client.access_settings.activity.get(query_params=params) +print response.status_code +print response.body +print response.headers +Add one or more IPs to the whitelist + +This endpoint allows you to add one or more IP addresses to your IP whitelist. + +When adding an IP to your whitelist, include the IP address in an array. You can whitelist one IP at a time, or you can whitelist multiple IPs at once. + +IP Access Management allows you to control which IP addresses can be used to access your account, either through the User Interface or the API. There is no limit to the number of IP addresses that you can add to your whitelist. It is possible to remove your own IP address from the whitelist, thus preventing yourself from accessing your account. + +For more information, please see our User Guide. + +POST /access_settings/whitelist + + +data = { + "ips": [ +​ { +​ "ip": "192.168.1.1" +​ }, +​ { +​ "ip": "192.*.*.*" +​ }, +​ { +​ "ip": "192.168.1.3/32" +​ } + ] +} +response = sg.client.access_settings.whitelist.post(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve a list of currently whitelisted IPs + +This endpoint allows you to retrieve a list of IP addresses that are currently whitelisted. + +IP Access Management allows you to control which IP addresses can be used to access your account, either through the User Interface or the API. There is no limit to the number of IP addresses that you can add to your whitelist. It is possible to remove your own IP address from the whitelist, thus preventing yourself from accessing your account. + +For more information, please see our User Guide. + +GET /access_settings/whitelist + + +response = sg.client.access_settings.whitelist.get() +print response.status_code +print response.body +print response.headers +Remove one or more IPs from the whitelist + +This endpoint allows you to remove one or more IPs from your IP whitelist. + +You can remove one IP at a time, or you can remove multiple IP addresses. + +IP Access Management allows you to control which IP addresses can be used to access your account, either through the User Interface or the API. There is no limit to the number of IP addresses that you can add to your whitelist. It is possible to remove your own IP address from the whitelist, thus preventing yourself from accessing your account. + +For more information, please see our User Guide. + +DELETE /access_settings/whitelist + + +data = { + "ids": [ +​ 1, +​ 2, +​ 3 + ] +} +response = sg.client.access_settings.whitelist.delete(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve a specific whitelisted IP + +This endpoint allows you to retrieve a specific IP address that has been whitelisted. + +You must include the ID for the specific IP address you want to retrieve in your call. + +IP Access Management allows you to control which IP addresses can be used to access your account, either through the User Interface or the API. There is no limit to the number of IP addresses that you can add to your whitelist. It is possible to remove your own IP address from the whitelist, thus preventing yourself from accessing your account. + +For more information, please see our User Guide. + +GET /access_settings/whitelist/{rule_id} + + +rule_id = "test_url_param" +response = sg.client.access_settings.whitelist._(rule_id).get() +print response.status_code +print response.body +print response.headers +Remove a specific IP from the whitelist + +This endpoint allows you to remove a specific IP address from your IP whitelist. + +When removing a specific IP address from your whitelist, you must include the ID in your call. + +IP Access Management allows you to control which IP addresses can be used to access your account, either through the User Interface or the API. There is no limit to the number of IP addresses that you can add to your whitelist. It is possible to remove your own IP address from the whitelist, thus preventing yourself from accessing your account. + +For more information, please see our User Guide. + +DELETE /access_settings/whitelist/{rule_id} + + +rule_id = "test_url_param" +response = sg.client.access_settings.whitelist._(rule_id).delete() +print response.status_code +print response.body +print response.headers + + +ALERTS + +Create a new Alert + +This endpoint allows you to create a new alert. + +Alerts allow you to specify an email address to receive notifications regarding your email usage or statistics. + +Usage alerts allow you to set the threshold at which an alert will be sent. + +Stats notifications allow you to set how frequently you would like to receive email statistics reports. For example, "daily", "weekly", or "monthly". + +For more information about alerts, please see our User Guide. + +POST /alerts + + +data = { + "email_to": "example@example.com", + "frequency": "daily", + "type": "stats_notification" +} +response = sg.client.alerts.post(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve all alerts + +GET /alerts + + +response = sg.client.alerts.get() +print response.status_code +print response.body +print response.headers +Update an alert + +This endpoint allows you to update an alert. + +Alerts allow you to specify an email address to receive notifications regarding your email usage or statistics. + +Usage alerts allow you to set the threshold at which an alert will be sent. + +Stats notifications allow you to set how frequently you would like to receive email statistics reports. For example, "daily", "weekly", or "monthly". + +For more information about alerts, please see our User Guide. + +PATCH /alerts/{alert_id} + + +data = { + "email_to": "example@example.com" +} +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 +Retrieve a specific alert + +This endpoint allows you to retrieve a specific alert. + +Alerts allow you to specify an email address to receive notifications regarding your email usage or statistics. + +Usage alerts allow you to set the threshold at which an alert will be sent. + +Stats notifications allow you to set how frequently you would like to receive email statistics reports. For example, "daily", "weekly", or "monthly". + +For more information about alerts, please see our User Guide. + +GET /alerts/{alert_id} + + +alert_id = "test_url_param" +response = sg.client.alerts._(alert_id).get() +print response.status_code +print response.body +print response.headers +Delete an alert + +This endpoint allows you to delete an alert. + +Alerts allow you to specify an email address to receive notifications regarding your email usage or statistics. + +Usage alerts allow you to set the threshold at which an alert will be sent. + +Stats notifications allow you to set how frequently you would like to receive email statistics reports. For example, "daily", "weekly", or "monthly". + +For more information about alerts, please see our User Guide. + +DELETE /alerts/{alert_id} + + +alert_id = "test_url_param" +response = sg.client.alerts._(alert_id).delete() +print response.status_code +print response.body +print response.headers + + +API KEYS + +Create API keys + +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. + +There is a limit of 100 API Keys on your account. + +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. + +See the API Key Permissions List for a list of all available scopes. + +POST /api_keys + + +data = { + "name": "My API Key", + "sample": "data", + "scopes": [ +​ "mail.send", +​ "alerts.create", +​ "alerts.read" + ] +} +response = sg.client.api_keys.post(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve all API Keys belonging to the authenticated user + +This endpoint allows you to retrieve all API Keys that belong to the authenticated user. + +The API Keys feature allows customers to generate an API Key credential which can be used for authentication with the SendGrid v3 Web API or the Mail API Endpoint. + +GET /api_keys + + +params = {'limit': 1} +response = sg.client.api_keys.get(query_params=params) +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. + +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. + +PUT /api_keys/{api_key_id} + + +data = { + "name": "A New Hope", + "scopes": [ +​ "user.profile.read", +​ "user.profile.update" + ] +} +api_key_id = "test_url_param" +response = sg.client.api_keys._(api_key_id).put(request_body=data) +print response.status_code +print response.body +print response.headers +Update API keys + +This endpoint allows you to update the name of an existing API Key. + +A JSON request body with a "name" property is required. + +The API Keys feature allows customers to be able to generate an API Key credential which can be used for authentication with the SendGrid v3 Web API or the Mail API Endpoint. + +URI Parameters + +URI PARAMETER TYPE REQUIRED? DESCRIPTION +api_key_id string required The ID of the API Key you are updating. +PATCH /api_keys/{api_key_id} + + +data = { + "name": "A New Hope" +} +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 +Retrieve an existing API Key + +This endpoint allows you to retrieve a single API key. + +If the API Key ID does not exist an HTTP 404 will be returned. + +GET /api_keys/{api_key_id} + + +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 +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. + +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. + +URI Parameters + +URI PARAMETER TYPE REQUIRED? DESCRIPTION +api_key_id string required The ID of the API Key you are deleting. +DELETE /api_keys/{api_key_id} + + +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 + + +ASM + +Create a new suppression group + +This endpoint allows you to create a new suppression group. + +Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. + +The name and description of the unsubscribe group will be visible by recipients when they are managing their subscriptions. + +Each user can create up to 25 different suppression groups. + +POST /asm/groups + + +data = { + "description": "Suggestions for products our users might like.", + "is_default": True, + "name": "Product Suggestions" +} +response = sg.client.asm.groups.post(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve information about multiple suppression groups + +This endpoint allows you to retrieve information about multiple suppression groups. + +This endpoint will return information for each group ID that you include in your request. To add a group ID to your request, simply append &id= followed by the group ID. + +Suppressions are a list of email addresses that will not receive content sent under a given group. + +Suppression groups, or unsubscribe groups, allow you to label a category of content that you regularly send. This gives your recipients the ability to opt out of a specific set of your email. For example, you might define a group for your transactional email, and one for your marketing email so that your users can continue receiving your transactional email without having to receive your marketing content. + +GET /asm/groups + + +params = {'id': 1} +response = sg.client.asm.groups.get(query_params=params) +print response.status_code +print response.body +print response.headers +Update a suppression group. + +This endpoint allows you to update or change a suppression group. + +Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. + +The name and description of the unsubscribe group will be visible by recipients when they are managing their subscriptions. + +Each user can create up to 25 different suppression groups. + +PATCH /asm/groups/{group_id} + + +data = { + "description": "Suggestions for items our users might like.", + "id": 103, + "name": "Item Suggestions" +} +group_id = "test_url_param" +response = sg.client.asm.groups._(group_id).patch(request_body=data) +print response.status_code +print response.body +print response.headers +Get information on a single suppression group. + +This endpoint allows you to retrieve a single suppression group. + +Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. + +The name and description of the unsubscribe group will be visible by recipients when they are managing their subscriptions. + +Each user can create up to 25 different suppression groups. + +GET /asm/groups/{group_id} + + +group_id = "test_url_param" +response = sg.client.asm.groups._(group_id).get() +print response.status_code +print response.body +print response.headers +Delete a suppression group. + +This endpoint allows you to delete a suppression group. + +You can only delete groups that have not been attached to sent mail in the last 60 days. If a recipient uses the "one-click unsubscribe" option on an email associated with a deleted group, that recipient will be added to the global suppression list. + +Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. + +The name and description of the unsubscribe group will be visible by recipients when they are managing their subscriptions. + +Each user can create up to 25 different suppression groups. + +DELETE /asm/groups/{group_id} + + +group_id = "test_url_param" +response = sg.client.asm.groups._(group_id).delete() +print response.status_code +print response.body +print response.headers +Add suppressions to a suppression group + +This endpoint allows you to add email addresses to an unsubscribe group. + +If you attempt to add suppressions to a group that has been deleted or does not exist, the suppressions will be added to the global suppressions list. + +Suppressions are recipient email addresses that are added to unsubscribe groups. Once a recipient's address is on the suppressions list for an unsubscribe group, they will not receive any emails that are tagged with that unsubscribe group. + +POST /asm/groups/{group_id}/suppressions + + +data = { + "recipient_emails": [ +​ "test1@example.com", +​ "test2@example.com" + ] +} +group_id = "test_url_param" +response = sg.client.asm.groups._(group_id).suppressions.post(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve all suppressions for a suppression group + +This endpoint allows you to retrieve all suppressed email addresses belonging to the given group. + +Suppressions are recipient email addresses that are added to unsubscribe groups. Once a recipient's address is on the suppressions list for an unsubscribe group, they will not receive any emails that are tagged with that unsubscribe group. + +GET /asm/groups/{group_id}/suppressions + + +group_id = "test_url_param" +response = sg.client.asm.groups._(group_id).suppressions.get() +print response.status_code +print response.body +print response.headers +Search for suppressions within a group + +This endpoint allows you to search a suppression group for multiple suppressions. + +When given a list of email addresses and a group ID, this endpoint will return only the email addresses that have been unsubscribed from the given group. + +Suppressions are a list of email addresses that will not receive content sent under a given group. + +POST /asm/groups/{group_id}/suppressions/search + + +data = { + "recipient_emails": [ +​ "exists1@example.com", +​ "exists2@example.com", +​ "doesnotexists@example.com" + ] +} +group_id = "test_url_param" +response = sg.client.asm.groups._(group_id).suppressions.search.post(request_body=data) +print response.status_code +print response.body +print response.headers +Delete a suppression from a suppression group + +This endpoint allows you to remove a suppressed email address from the given suppression group. + +Suppressions are recipient email addresses that are added to unsubscribe groups. Once a recipient's address is on the suppressions list for an unsubscribe group, they will not receive any emails that are tagged with that unsubscribe group. + +DELETE /asm/groups/{group_id}/suppressions/{email} + + +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 +Retrieve all suppressions + +This endpoint allows you to retrieve a list of all suppressions. + +Suppressions are a list of email addresses that will not receive content sent under a given group. + +GET /asm/suppressions + + +response = sg.client.asm.suppressions.get() +print response.status_code +print response.body +print response.headers +Add recipient addresses to the global suppression group. + +This endpoint allows you to add one or more email addresses to the global suppressions group. + +A global suppression (or global unsubscribe) is an email address of a recipient who does not want to receive any of your messages. A globally suppressed recipient will be removed from any email you send. For more information, please see our User Guide. + +POST /asm/suppressions/global + + +data = { + "recipient_emails": [ +​ "test1@example.com", +​ "test2@example.com" + ] +} +response = sg.client.asm.suppressions._("global").post(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve a Global Suppression + +This endpoint allows you to retrieve a global suppression. You can also use this endpoint to confirm if an email address is already globally suppressed. + +If the email address you include in the URL path parameter {email} is already globally suppressed, the response will include that email address. If the address you enter for {email} is not globally suppressed, an empty JSON object {} will be returned. + +A global suppression (or global unsubscribe) is an email address of a recipient who does not want to receive any of your messages. A globally suppressed recipient will be removed from any email you send. For more information, please see our User Guide. + +GET /asm/suppressions/global/{email} + + +email = "test_url_param" +response = sg.client.asm.suppressions._("global")._(email).get() +print response.status_code +print response.body +print response.headers +Delete a Global Suppression + +This endpoint allows you to remove an email address from the global suppressions group. + +A global suppression (or global unsubscribe) is an email address of a recipient who does not want to receive any of your messages. A globally suppressed recipient will be removed from any email you send. For more information, please see our User Guide. + +DELETE /asm/suppressions/global/{email} + + +email = "test_url_param" +response = sg.client.asm.suppressions._("global")._(email).delete() +print response.status_code +print response.body +print response.headers +Retrieve all suppression groups for an email address + +This endpoint returns the list of all groups that the given email address has been unsubscribed from. + +Suppressions are a list of email addresses that will not receive content sent under a given group. + +GET /asm/suppressions/{email} + + +email = "test_url_param" +response = sg.client.asm.suppressions._(email).get() +print response.status_code +print response.body +print response.headers + + +BROWSERS + +Retrieve email statistics by browser. + +This endpoint allows you to retrieve your email statistics segmented by browser type. + +We only store up to 7 days of email activity in our database. By default, 500 items will be returned per request via the Advanced Stats API endpoints. + +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. + +GET /browsers/stats + + +params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'browsers': 'test_string', 'limit': 'test_string', 'offset': 'test_string', 'start_date': '2016-01-01'} +response = sg.client.browsers.stats.get(query_params=params) +print response.status_code +print response.body +print response.headers + + +CAMPAIGNS + +Create a Campaign + +This endpoint allows you to create a campaign. + +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. + +For more information: + +User Guide > Marketing Campaigns + +POST /campaigns + + +data = { + "categories": [ +​ "spring line" + ], + "custom_unsubscribe_url": "", + "html_content": "

Check out our spring line!

", + "ip_pool": "marketing", + "list_ids": [ +​ 110, +​ 124 + ], + "plain_content": "Check out our spring line!", + "segment_ids": [ +​ 110 + ], + "sender_id": 124451, + "subject": "New Products for Spring!", + "suppression_group_id": 42, + "title": "March Newsletter" +} +response = sg.client.campaigns.post(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve all Campaigns + +This endpoint allows you to retrieve a list of all of your campaigns. + +Returns campaigns in reverse order they were created (newest first). + +Returns an empty array if no campaigns exist. + +For more information: + +User Guide > Marketing Campaigns + +GET /campaigns + + +params = {'limit': 10, 'offset': 0} +response = sg.client.campaigns.get(query_params=params) +print response.status_code +print response.body +print response.headers +Update a Campaign + +Update a campaign. This is especially useful if you only set up the campaign using POST /campaigns, but didn't set many of the parameters. + +For more information: + +User Guide > Marketing Campaigns + +PATCH /campaigns/{campaign_id} + + +data = { + "categories": [ +​ "summer line" + ], + "html_content": "

Check out our summer line!

", + "plain_content": "Check out our summer line!", + "subject": "New Products for Summer!", + "title": "May Newsletter" +} +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 +Retrieve a single campaign + +This endpoint allows you to retrieve a specific campaign. + +Our Marketing Campaigns API lets you create, manage, send, and schedule campaigns. + +For more information: + +User Guide > Marketing Campaigns + +GET /campaigns/{campaign_id} + + +campaign_id = "test_url_param" +response = sg.client.campaigns._(campaign_id).get() +print response.status_code +print response.body +print response.headers +Delete a Campaign + +This endpoint allows you to delete a specific campaign. + +Our Marketing Campaigns API lets you create, manage, send, and schedule campaigns. + +For more information: + +User Guide > Marketing Campaigns + +DELETE /campaigns/{campaign_id} + + +campaign_id = "test_url_param" +response = sg.client.campaigns._(campaign_id).delete() +print response.status_code +print response.body +print response.headers +Update a Scheduled Campaign + +This endpoint allows to you change the scheduled time and date for a campaign to be sent. + +For more information: + +User Guide > Marketing Campaigns + +PATCH /campaigns/{campaign_id}/schedules + + +data = { + "send_at": 1489451436 +} +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 +Schedule a Campaign + +This endpoint allows you to schedule a specific date and time for your campaign to be sent. + +For more information: + +User Guide > Marketing Campaigns + +POST /campaigns/{campaign_id}/schedules + + +data = { + "send_at": 1489771528 +} +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 +View Scheduled Time of a Campaign + +This endpoint allows you to retrieve the date and time that the given campaign has been scheduled to be sent. + +For more information: + +User Guide > Marketing Campaigns + +GET /campaigns/{campaign_id}/schedules + + +campaign_id = "test_url_param" +response = sg.client.campaigns._(campaign_id).schedules.get() +print response.status_code +print response.body +print response.headers +Unschedule a Scheduled Campaign + +This endpoint allows you to unschedule a campaign that has already been scheduled to be sent. + +A successful unschedule will return a 204. +If the specified campaign is in the process of being sent, the only option is to cancel (a different method). + +For more information: + +User Guide > Marketing Campaigns + +DELETE /campaigns/{campaign_id}/schedules + + +campaign_id = "test_url_param" +response = sg.client.campaigns._(campaign_id).schedules.delete() +print response.status_code +print response.body +print response.headers +Send a Campaign + +This endpoint allows you to immediately send a campaign at the time you make the API call. + +Normally a POST would have a request body, but since this endpoint is telling us to send a resource that is already created, a request body is not needed. + +For more information: + +User Guide > Marketing Campaigns + +POST /campaigns/{campaign_id}/schedules/now + + +campaign_id = "test_url_param" +response = sg.client.campaigns._(campaign_id).schedules.now.post() +print response.status_code +print response.body +print response.headers +Send a Test Campaign + +This endpoint allows you to send a test campaign. + +To send to multiple addresses, use an array for the JSON "to" value ["one@address","two@address"] + +For more information: + +User Guide > Marketing Campaigns + +POST /campaigns/{campaign_id}/schedules/test + + +data = { + "to": "your.email@example.com" +} +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 + + +CATEGORIES + +Retrieve all categories + +This endpoint allows you to retrieve a list of all of your categories. + +Categories can help organize your email analytics by enabling you to tag emails by type or broad topic. You can define your own custom categories. For more information, please see our User Guide. + +GET /categories + + +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 +Retrieve Email Statistics for Categories + +This endpoint allows you to retrieve all of your email statistics for each of your categories. + +If you do not define any query parameters, this endpoint will return a sum for each category in groups of 10. + +Categories allow you to group your emails together according to broad topics that you define. For more information, please see our User Guide. + +GET /categories/stats + + +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 +Retrieve sums of email stats for each category [Needs: Stats object defined, has category ID?] + +This endpoint allows you to retrieve the total sum of each email statistic for every category over the given date range. + +If you do not define any query parameters, this endpoint will return a sum for each category in groups of 10. + +Categories allow you to group your emails together according to broad topics that you define. For more information, please see our User Guide. + +GET /categories/stats/sums + + +params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} +response = sg.client.categories.stats.sums.get(query_params=params) +print response.status_code +print response.body +print response.headers + + +CLIENTS + +Retrieve email statistics by client type. + +This endpoint allows you to retrieve your email statistics segmented by client type. + +We only store up to 7 days of email activity in our database. By default, 500 items will be returned per request via the Advanced Stats API endpoints. + +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. + +GET /clients/stats + + +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 +Retrieve stats by a specific client type. + +This endpoint allows you to retrieve your email statistics segmented by a specific client type. + +We only store up to 7 days of email activity in our database. By default, 500 items will be returned per request via the Advanced Stats API endpoints. + +Available Client Types + +phone + +tablet + +webmail + +desktop + +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. + +GET /clients/{client_type}/stats + + +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 + + +CONTACTDB + +Create a Custom Field + +This endpoint allows you to create a custom field. + +The contactdb is a database of your contacts for SendGrid Marketing Campaigns. + +POST /contactdb/custom_fields + + +data = { + "name": "pet", + "type": "text" +} +response = sg.client.contactdb.custom_fields.post(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve all custom fields + +This endpoint allows you to retrieve all custom fields. + +The contactdb is a database of your contacts for SendGrid Marketing Campaigns. + +GET /contactdb/custom_fields + + +response = sg.client.contactdb.custom_fields.get() +print response.status_code +print response.body +print response.headers +Retrieve a Custom Field + +This endpoint allows you to retrieve a custom field by ID. + +The contactdb is a database of your contacts for SendGrid Marketing Campaigns. + +GET /contactdb/custom_fields/{custom_field_id} + + +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 +Delete a Custom Field + +This endpoint allows you to delete a custom field by ID. + +The contactdb is a database of your contacts for SendGrid Marketing Campaigns. + +DELETE /contactdb/custom_fields/{custom_field_id} + + +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 +Create a List + +This endpoint allows you to create a list for your recipients. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +POST /contactdb/lists + + +data = { + "name": "your list name" +} +response = sg.client.contactdb.lists.post(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve all lists + +This endpoint allows you to retrieve all of your recipient lists. If you don't have any lists, an empty array will be returned. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +GET /contactdb/lists + + +response = sg.client.contactdb.lists.get() +print response.status_code +print response.body +print response.headers +Delete Multiple lists + +This endpoint allows you to delete multiple recipient lists. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +DELETE /contactdb/lists + + +data = [ + 1, + 2, + 3, + 4 +] +response = sg.client.contactdb.lists.delete(request_body=data) +print response.status_code +print response.body +print response.headers +Update a List + +This endpoint allows you to update the name of one of your recipient lists. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +PATCH /contactdb/lists/{list_id} + + +data = { + "name": "newlistname" +} +params = {'list_id': 1} +list_id = "test_url_param" +response = sg.client.contactdb.lists._(list_id).patch(request_body=data, query_params=params) +print response.status_code +print response.body +print response.headers +Retrieve a single list + +This endpoint allows you to retrieve a single recipient list. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +GET /contactdb/lists/{list_id} + + +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 +Delete a List + +This endpoint allows you to delete a specific recipient list with the given ID. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +DELETE /contactdb/lists/{list_id} + + +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 +Add Multiple Recipients to a List + +This endpoint allows you to add multiple recipients to a list. + +Adds existing recipients to a list, passing in the recipient IDs to add. Recipient IDs should be passed exactly as they are returned from recipient endpoints. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +POST /contactdb/lists/{list_id}/recipients + + +data = [ + "recipient_id1", + "recipient_id2" +] +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 +Retrieve all recipients on a List + +This endpoint allows you to retrieve all recipients on the list with the given ID. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +GET /contactdb/lists/{list_id}/recipients + + +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 +Add a Single Recipient to a List + +This endpoint allows you to add a single recipient to a list. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +POST /contactdb/lists/{list_id}/recipients/{recipient_id} + + +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 +Delete a Single Recipient from a Single List + +This endpoint allows you to delete a single recipient from a list. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +DELETE /contactdb/lists/{list_id}/recipients/{recipient_id} + + +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 +Update Recipient + +This endpoint allows you to update one or more recipients. + +The body of an API call to this endpoint must include an array of one or more recipient objects. + +It is of note that you can add custom field data as parameters on recipient objects. We have provided an example using some of the default custom fields SendGrid provides. + +The contactdb is a database of your contacts for SendGrid Marketing Campaigns. + +PATCH /contactdb/recipients + + +data = [ + { +​ "email": "jones@example.com", +​ "first_name": "Guy", +​ "last_name": "Jones" + } +] +response = sg.client.contactdb.recipients.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Add recipients + +This endpoint allows you to add a Marketing Campaigns recipient. + +It is of note that you can add custom field data as a parameter on this endpoint. We have provided an example using some of the default custom fields SendGrid provides. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +POST /contactdb/recipients + + +data = [ + { +​ "age": 25, +​ "email": "example@example.com", +​ "first_name": "", +​ "last_name": "User" + }, + { +​ "age": 25, +​ "email": "example2@example.com", +​ "first_name": "Example", +​ "last_name": "User" + } +] +response = sg.client.contactdb.recipients.post(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve recipients + +This endpoint allows you to retrieve all of your Marketing Campaigns recipients. + +Batch deletion of a page makes it possible to receive an empty page of recipients before reaching the end of +the list of recipients. To avoid this issue; iterate over pages until a 404 is retrieved. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +GET /contactdb/recipients + + +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 +Delete Recipient + +This endpoint allows you to deletes one or more recipients. + +The body of an API call to this endpoint must include an array of recipient IDs of the recipients you want to delete. + +The contactdb is a database of your contacts for SendGrid Marketing Campaigns. + +DELETE /contactdb/recipients + + +data = [ + "recipient_id1", + "recipient_id2" +] +response = sg.client.contactdb.recipients.delete(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve the count of billable recipients + +This endpoint allows you to retrieve the number of Marketing Campaigns recipients that you will be billed for. + +You are billed for marketing campaigns based on the highest number of recipients you have had in your account at one time. This endpoint will allow you to know the current billable count value. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +GET /contactdb/recipients/billable_count + + +response = sg.client.contactdb.recipients.billable_count.get() +print response.status_code +print response.body +print response.headers +Retrieve a Count of Recipients + +This endpoint allows you to retrieve the total number of Marketing Campaigns recipients. + +The contactdb is a database of your contacts for SendGrid Marketing Campaigns. + +GET /contactdb/recipients/count + + +response = sg.client.contactdb.recipients.count.get() +print response.status_code +print response.body +print response.headers +Retrieve recipients matching search criteria + +This endpoint allows you to perform a search on all of your Marketing Campaigns recipients. + +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) + +If field_name is a 'reserved' date field, such as created_at or updated_at, the system will internally convert +your epoch time to a date range encompassing the entire day. For example, an epoch time of 1422835600 converts to +Mon, 02 Feb 2015 00:06:40 GMT, but internally the system will search from Mon, 02 Feb 2015 00:00:00 GMT through +Mon, 02 Feb 2015 23:59:59 GMT. + +The contactdb is a database of your contacts for SendGrid Marketing Campaigns. + +GET /contactdb/recipients/search + + +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 +Retrieve a single recipient + +This endpoint allows you to retrieve a single recipient by ID from your contact database. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +GET /contactdb/recipients/{recipient_id} + + +recipient_id = "test_url_param" +response = sg.client.contactdb.recipients._(recipient_id).get() +print response.status_code +print response.body +print response.headers +Delete a Recipient + +This endpoint allows you to delete a single recipient with the given ID from your contact database. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +DELETE /contactdb/recipients/{recipient_id} + + +recipient_id = "test_url_param" +response = sg.client.contactdb.recipients._(recipient_id).delete() +print response.status_code +print response.body +print response.headers +Retrieve the lists that a recipient is on + +This endpoint allows you to retrieve the lists that a given recipient belongs to. + +Each recipient can be on many lists. This endpoint gives you all of the lists that any one recipient has been added to. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +GET /contactdb/recipients/{recipient_id}/lists + + +recipient_id = "test_url_param" +response = sg.client.contactdb.recipients._(recipient_id).lists.get() +print response.status_code +print response.body +print response.headers +Retrieve reserved fields + +This endpoint allows you to list all fields that are reserved and can't be used for custom field names. + +The contactdb is a database of your contacts for SendGrid Marketing Campaigns. + +GET /contactdb/reserved_fields + + +response = sg.client.contactdb.reserved_fields.get() +print response.status_code +print response.body +print response.headers +Create a Segment + +This endpoint allows you to create a segment. + +All recipients in your contactdb will be added or removed automatically depending on whether they match the criteria for this segment. + +List Id: + +Send this to segment from an existing list + +Don't send this in order to segment from your entire contactdb. + +Valid operators for create and update depend on the type of the field you are segmenting: + +Dates: "eq", "ne", "lt" (before), "gt" (after) + +Text: "contains", "eq" (is - matches the full field), "ne" (is not - matches any field where the entire field is not the condition value) + +Numbers: "eq", "lt", "gt" + +Email Clicks and Opens: "eq" (opened), "ne" (not opened) + +Segment conditions using "eq" or "ne" for email clicks and opens should provide a "field" of either clicks.campaign_identifier or opens.campaign_identifier. The condition value should be a string containing the id of a completed campaign. + +Segments may contain multiple conditions, joined by an "and" or "or" in the "and_or" field. The first condition in the conditions list must have an empty "and_or", and subsequent conditions must all specify an "and_or". + +The Contacts API helps you manage your Marketing Campaigns recipients. + +For more information about segments in Marketing Campaigns, please see our User Guide. + +POST /contactdb/segments + + +data = { + "conditions": [ +​ { +​ "and_or": "", +​ "field": "last_name", +​ "operator": "eq", +​ "value": "Miller" +​ }, +​ { +​ "and_or": "and", +​ "field": "last_clicked", +​ "operator": "gt", +​ "value": "01/02/2015" +​ }, +​ { +​ "and_or": "or", +​ "field": "clicks.campaign_identifier", +​ "operator": "eq", +​ "value": "513" +​ } + ], + "list_id": 4, + "name": "Last Name Miller" +} +response = sg.client.contactdb.segments.post(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve all segments + +This endpoint allows you to retrieve all of your segments. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +For more information about segments in Marketing Campaigns, please see our User Guide. + +GET /contactdb/segments + + +response = sg.client.contactdb.segments.get() +print response.status_code +print response.body +print response.headers +Update a segment + +This endpoint allows you to update a segment. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +For more information about segments in Marketing Campaigns, please see our User Guide. + +PATCH /contactdb/segments/{segment_id} + + +data = { + "conditions": [ +​ { +​ "and_or": "", +​ "field": "last_name", +​ "operator": "eq", +​ "value": "Miller" +​ } + ], + "list_id": 5, + "name": "The Millers" +} +params = {'segment_id': 'test_string'} +segment_id = "test_url_param" +response = sg.client.contactdb.segments._(segment_id).patch(request_body=data, query_params=params) +print response.status_code +print response.body +print response.headers +Retrieve a segment + +This endpoint allows you to retrieve a single segment with the given ID. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +For more information about segments in Marketing Campaigns, please see our User Guide. + +GET /contactdb/segments/{segment_id} + + +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 +Delete a segment + +This endpoint allows you to delete a segment from your recipients database. + +You also have the option to delete all the contacts from your Marketing Campaigns recipient database who were in this segment. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +For more information about segments in Marketing Campaigns, please see our User Guide. + +DELETE /contactdb/segments/{segment_id} + + +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 +Retrieve recipients on a segment + +This endpoint allows you to retrieve all of the recipients in a segment with the given ID. + +The Contacts API helps you manage your Marketing Campaigns recipients. + +For more information about segments in Marketing Campaigns, please see our User Guide. + +GET /contactdb/segments/{segment_id}/recipients + + +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 + + +DEVICES + +Retrieve email statistics by device type. + +This endpoint allows you to retrieve your email statistics segmented by the device type. + +We only store up to 7 days of email activity in our database. By default, 500 items will be returned per request via the Advanced Stats API endpoints. + +Available Device Types + +DEVICE DESCRIPTION EXAMPLE +Desktop Email software on 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. | +| 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. + +GET /devices/stats + + +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 + + +GEO + +Retrieve email statistics by country and state/province. + +This endpoint allows you to retrieve your email statistics segmented by country and state/province. + +We only store up to 7 days of email activity in our database. By default, 500 items will be returned per request via the Advanced Stats API endpoints. + +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. + +GET /geo/stats + + +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 + + +IPS + +Retrieve all IP addresses + +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. + +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. + +GET /ips + + +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 +Retrieve all assigned IPs + +This endpoint allows you to retrieve only assigned IP addresses. + +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. + +GET /ips/assigned + + +response = sg.client.ips.assigned.get() +print response.status_code +print response.body +print response.headers +Create an IP pool. + +This endpoint allows you to create an IP pool. + +Each user can create up to 10 different IP pools. + +IP Pools allow you to group your dedicated SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. + +IP pools can only be used with whitelabeled IP addresses. + +If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. + +POST /ips/pools + + +data = { + "name": "marketing" +} +response = sg.client.ips.pools.post(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve all IP pools. + +This endpoint allows you to retrieve all of your IP pools. + +IP Pools allow you to group your dedicated SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. + +IP pools can only be used with whitelabeled IP addresses. + +If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. + +GET /ips/pools + + +response = sg.client.ips.pools.get() +print response.status_code +print response.body +print response.headers +Update an IP pools name. + +This endpoint allows you to update the name of an IP pool. + +IP Pools allow you to group your dedicated SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. + +IP pools can only be used with whitelabeled IP addresses. + +If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. + +PUT /ips/pools/{pool_name} + + +data = { + "name": "new_pool_name" +} +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 +Retrieve all IPs in a specified pool. + +This endpoint allows you to list all of the IP addresses that are in a specific IP pool. + +IP Pools allow you to group your dedicated SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. + +IP pools can only be used with whitelabeled IP addresses. + +If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. + +GET /ips/pools/{pool_name} + + +pool_name = "test_url_param" +response = sg.client.ips.pools._(pool_name).get() +print response.status_code +print response.body +print response.headers +Delete an IP pool. + +This endpoint allows you to delete an IP pool. + +IP Pools allow you to group your dedicated SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. + +IP pools can only be used with whitelabeled IP addresses. + +If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. + +DELETE /ips/pools/{pool_name} + + +pool_name = "test_url_param" +response = sg.client.ips.pools._(pool_name).delete() +print response.status_code +print response.body +print response.headers +Add an IP address to a pool + +This endpoint allows you to add an IP address to an IP pool. + +You can add the same IP address to multiple pools. It may take up to 60 seconds for your IP address to be added to a pool after your request is made. + +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. + +POST /ips/pools/{pool_name}/ips + + +data = { + "ip": "0.0.0.0" +} +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 +Remove an IP address from a pool. + +This endpoint allows you to remove an IP address from an IP pool. + +The same IP address can be added to multiple IP pools. + +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. + +DELETE /ips/pools/{pool_name}/ips/{ip} + + +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 +Add an IP to warmup + +This endpoint allows you to enter an IP address into warmup mode. + +SendGrid can automatically warm up dedicated IP addresses by limiting the amount of mail that can be sent through them per hour, with the limit determined by how long the IP address has been in warmup. See the warmup schedule for more details on how SendGrid limits your email traffic for IPs in warmup. + +For more general information about warming up IPs, please see our Classroom. + +POST /ips/warmup + + +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 +Retrieve all IPs currently in warmup + +This endpoint allows you to retrieve all of your IP addresses that are currently warming up. + +SendGrid can automatically warm up dedicated IP addresses by limiting the amount of mail that can be sent through them per hour, with the limit determined by how long the IP address has been in warmup. See the warmup schedule for more details on how SendGrid limits your email traffic for IPs in warmup. + +For more general information about warming up IPs, please see our Classroom. + +GET /ips/warmup + + +response = sg.client.ips.warmup.get() +print response.status_code +print response.body +print response.headers +Retrieve warmup status for a specific IP address + +This endpoint allows you to retrieve the warmup status for a specific IP address. + +SendGrid can automatically warm up dedicated IP addresses by limiting the amount of mail that can be sent through them per hour, with the limit determined by how long the IP address has been in warmup. See the warmup schedule for more details on how SendGrid limits your email traffic for IPs in warmup. + +For more general information about warming up IPs, please see our Classroom. + +GET /ips/warmup/{ip_address} + + +ip_address = "test_url_param" +response = sg.client.ips.warmup._(ip_address).get() +print response.status_code +print response.body +print response.headers +Remove an IP from warmup + +This endpoint allows you to remove an IP address from warmup mode. + +SendGrid can automatically warm up dedicated IP addresses by limiting the amount of mail that can be sent through them per hour, with the limit determined by how long the IP address has been in warmup. See the warmup schedule for more details on how SendGrid limits your email traffic for IPs in warmup. + +For more general information about warming up IPs, please see our Classroom. + +DELETE /ips/warmup/{ip_address} + + +ip_address = "test_url_param" +response = sg.client.ips.warmup._(ip_address).delete() +print response.status_code +print response.body +print response.headers +Retrieve all IP pools an IP address belongs to + +This endpoint allows you to see which IP pools a particular IP address has been added to. + +The same IP address can be added to multiple IP pools. + +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. + +GET /ips/{ip_address} + + +ip_address = "test_url_param" +response = sg.client.ips._(ip_address).get() +print response.status_code +print response.body +print response.headers + + +MAIL + +Create a batch ID + +This endpoint allows you to generate a new batch ID. This batch ID can be associated with scheduled sends via the mail/send endpoint. + +If you set the SMTPAPI header batch_id, it allows you to then associate multiple scheduled mail/send requests together with the same ID. Then at anytime up to 10 minutes before the schedule date, you can cancel all of the mail/send requests that have this batch ID by calling the Cancel Scheduled Send endpoint. + +More Information: + +Scheduling Parameters > Batch ID + +POST /mail/batch + + +response = sg.client.mail.batch.post() +print response.status_code +print response.body +print response.headers +Validate batch ID + +This endpoint allows you to validate a batch ID. + +If you set the SMTPAPI header batch_id, it allows you to then associate multiple scheduled mail/send requests together with the same ID. Then at anytime up to 10 minutes before the schedule date, you can cancel all of the mail/send requests that have this batch ID by calling the Cancel Scheduled Send endpoint. + +More Information: + +Scheduling Parameters > Batch ID + +GET /mail/batch/{batch_id} + + +batch_id = "test_url_param" +response = sg.client.mail.batch._(batch_id).get() +print response.status_code +print response.body +print response.headers +v3 Mail Send + +This endpoint allows you to send email over SendGrids v3 Web API, the most recent version of our API. If you are looking for documentation about the v2 Mail Send endpoint, please see our v2 API Reference. + +Top level parameters are referred to as "global". + +Individual fields within the personalizations array will override any other global, or message level, parameters that are defined outside of personalizations. + +For an overview of the v3 Mail Send endpoint, please visit our v3 API Reference + +For more detailed information about how to use the v3 Mail Send endpoint, please visit our Classroom. + +POST /mail/send + +This endpoint has a helper, check it out here. + + +data = { + "asm": { +​ "group_id": 1, +​ "groups_to_display": [ +​ 1, +​ 2, +​ 3 +​ ] + }, + "attachments": [ +​ { +​ "content": "[BASE64 encoded content block here]", +​ "content_id": "ii_139db99fdb5c3704", +​ "disposition": "inline", +​ "filename": "file1.jpg", +​ "name": "file1", +​ "type": "jpg" +​ } + ], + "batch_id": "[YOUR BATCH ID GOES HERE]", + "categories": [ +​ "category1", +​ "category2" + ], + "content": [ +​ { +​ "type": "text/html", +​ "value": "

Hello, world!

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

Thanks
The SendGrid Team

", +​ "text": "Thanks,/n The SendGrid Team" +​ }, +​ "sandbox_mode": { +​ "enable": False +​ }, +​ "spam_check": { +​ "enable": True, +​ "post_to_url": "http://example.com/compliance", +​ "threshold": 3 +​ } + }, + "personalizations": [ +​ { +​ "bcc": [ +​ { +​ "email": "sam.doe@example.com", +​ "name": "Sam Doe" +​ } +​ ], +​ "cc": [ +​ { +​ "email": "jane.doe@example.com", +​ "name": "Jane Doe" +​ } +​ ], +​ "custom_args": { +​ "New Argument 1": "New Value 1", +​ "activationAttempt": "1", +​ "customerAccountNumber": "[CUSTOMER ACCOUNT NUMBER GOES HERE]" +​ }, +​ "headers": { +​ "X-Accept-Language": "en", +​ "X-Mailer": "MyApp" +​ }, +​ "send_at": 1409348513, +​ "subject": "Hello, World!", +​ "substitutions": { +​ "id": "substitutions", +​ "type": "object" +​ }, +​ "to": [ +​ { +​ "email": "john.doe@example.com", +​ "name": "John Doe" +​ } +​ ] +​ } + ], + "reply_to": { +​ "email": "sam.smith@example.com", +​ "name": "Sam Smith" + }, + "sections": { +​ "section": { +​ ":sectionName1": "section 1 text", +​ ":sectionName2": "section 2 text" +​ } + }, + "send_at": 1409348513, + "subject": "Hello, World!", + "template_id": "[YOUR TEMPLATE ID GOES HERE]", + "tracking_settings": { +​ "click_tracking": { +​ "enable": True, +​ "enable_text": True +​ }, +​ "ganalytics": { +​ "enable": True, +​ "utm_campaign": "[NAME OF YOUR REFERRER SOURCE]", +​ "utm_content": "[USE THIS SPACE TO DIFFERENTIATE YOUR EMAIL FROM ADS]", +​ "utm_medium": "[NAME OF YOUR MARKETING MEDIUM e.g. email]", +​ "utm_name": "[NAME OF YOUR CAMPAIGN]", +​ "utm_term": "[IDENTIFY PAID KEYWORDS HERE]" +​ }, +​ "open_tracking": { +​ "enable": True, +​ "substitution_tag": "%opentrack" +​ }, +​ "subscription_tracking": { +​ "enable": True, +​ "html": "If you would like to unsubscribe and stop receiving these emails <% clickhere %>.", +​ "substitution_tag": "<%click here%>", +​ "text": "If you would like to unsubscribe and stop receiving these emails <% click here %>." +​ } + } +} +response = sg.client.mail.send.post(request_body=data) +print response.status_code +print response.body +print response.headers + + +MAIL SETTINGS + +Retrieve all mail settings + +This endpoint allows you to retrieve a list of all mail settings. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +GET /mail_settings + + +params = {'limit': 1, 'offset': 1} +response = sg.client.mail_settings.get(query_params=params) +print response.status_code +print response.body +print response.headers +Update address whitelist mail settings + +This endpoint allows you to update your current email address whitelist settings. + +The address whitelist setting whitelists a specified email address or domain for which mail should never be suppressed. For example, you own the domain example.com, and one or more of your recipients use email@example.com addresses, by placing example.com in the address whitelist setting, all bounces, blocks, and unsubscribes logged for that domain will be ignored and sent as if under normal sending conditions. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +PATCH /mail_settings/address_whitelist + + +data = { + "enabled": True, + "list": [ +​ "email1@example.com", +​ "example.com" + ] +} +response = sg.client.mail_settings.address_whitelist.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve address whitelist mail settings + +This endpoint allows you to retrieve your current email address whitelist settings. + +The address whitelist setting whitelists a specified email address or domain for which mail should never be suppressed. For example, you own the domain example.com, and one or more of your recipients use email@example.com addresses, by placing example.com in the address whitelist setting, all bounces, blocks, and unsubscribes logged for that domain will be ignored and sent as if under normal sending conditions. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +GET /mail_settings/address_whitelist + + +response = sg.client.mail_settings.address_whitelist.get() +print response.status_code +print response.body +print response.headers +Update BCC mail settings + +This endpoint allows you to update your current BCC mail settings. + +When the BCC mail setting is enabled, SendGrid will automatically send a blind carbon copy (BCC) to an address for every email sent without adding that address to the header. Please note that only one email address may be entered in this field, if you wish to distribute BCCs to multiple addresses you will need to create a distribution group or use forwarding rules. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +PATCH /mail_settings/bcc + + +data = { + "email": "email@example.com", + "enabled": False +} +response = sg.client.mail_settings.bcc.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve all BCC mail settings + +This endpoint allows you to retrieve your current BCC mail settings. + +When the BCC mail setting is enabled, SendGrid will automatically send a blind carbon copy (BCC) to an address for every email sent without adding that address to the header. Please note that only one email address may be entered in this field, if you wish to distribute BCCs to multiple addresses you will need to create a distribution group or use forwarding rules. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +GET /mail_settings/bcc + + +response = sg.client.mail_settings.bcc.get() +print response.status_code +print response.body +print response.headers +Update bounce purge mail settings + +This endpoint allows you to update your current bounce purge settings. + +This setting allows you to set a schedule for SendGrid to automatically delete contacts from your soft and hard bounce suppression lists. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +PATCH /mail_settings/bounce_purge + + +data = { + "enabled": True, + "hard_bounces": 5, + "soft_bounces": 5 +} +response = sg.client.mail_settings.bounce_purge.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve bounce purge mail settings + +This endpoint allows you to retrieve your current bounce purge settings. + +This setting allows you to set a schedule for SendGrid to automatically delete contacts from your soft and hard bounce suppression lists. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +GET /mail_settings/bounce_purge + + +response = sg.client.mail_settings.bounce_purge.get() +print response.status_code +print response.body +print response.headers +Update footer mail settings + +This endpoint allows you to update your current Footer mail settings. + +The footer setting will insert a custom footer at the bottom of the text and HTML bodies. Use the embedded HTML editor and plain text entry fields to create the content of the footers to be inserted into your emails. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +PATCH /mail_settings/footer + + +data = { + "enabled": True, + "html_content": "...", + "plain_content": "..." +} +response = sg.client.mail_settings.footer.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve footer mail settings + +This endpoint allows you to retrieve your current Footer mail settings. + +The footer setting will insert a custom footer at the bottom of the text and HTML bodies. Use the embedded HTML editor and plain text entry fields to create the content of the footers to be inserted into your emails. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +GET /mail_settings/footer + + +response = sg.client.mail_settings.footer.get() +print response.status_code +print response.body +print response.headers +Update forward bounce mail settings + +This endpoint allows you to update your current bounce forwarding mail settings. + +Activating this setting allows you to specify an email address to which bounce reports are forwarded. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +PATCH /mail_settings/forward_bounce + + +data = { + "email": "example@example.com", + "enabled": True +} +response = sg.client.mail_settings.forward_bounce.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve forward bounce mail settings + +This endpoint allows you to retrieve your current bounce forwarding mail settings. + +Activating this setting allows you to specify an email address to which bounce reports are forwarded. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +GET /mail_settings/forward_bounce + + +response = sg.client.mail_settings.forward_bounce.get() +print response.status_code +print response.body +print response.headers +Update forward spam mail settings + +This endpoint allows you to update your current Forward Spam mail settings. + +Enabling the forward spam setting allows you to specify an email address to which spam reports will be forwarded. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +PATCH /mail_settings/forward_spam + + +data = { + "email": "", + "enabled": False +} +response = sg.client.mail_settings.forward_spam.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve forward spam mail settings + +This endpoint allows you to retrieve your current Forward Spam mail settings. + +Enabling the forward spam setting allows you to specify an email address to which spam reports will be forwarded. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +GET /mail_settings/forward_spam + + +response = sg.client.mail_settings.forward_spam.get() +print response.status_code +print response.body +print response.headers +Update plain content mail settings + +This endpoint allows you to update your current Plain Content mail settings. + +The plain content setting will automatically convert any plain text emails that you send to HTML before sending. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +PATCH /mail_settings/plain_content + + +data = { + "enabled": False +} +response = sg.client.mail_settings.plain_content.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve plain content mail settings + +This endpoint allows you to retrieve your current Plain Content mail settings. + +The plain content setting will automatically convert any plain text emails that you send to HTML before sending. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +GET /mail_settings/plain_content + + +response = sg.client.mail_settings.plain_content.get() +print response.status_code +print response.body +print response.headers +Update spam check mail settings + +This endpoint allows you to update your current spam checker mail settings. + +The spam checker filter notifies you when emails are detected that exceed a predefined spam threshold. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +PATCH /mail_settings/spam_check + + +data = { + "enabled": True, + "max_score": 5, + "url": "url" +} +response = sg.client.mail_settings.spam_check.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve spam check mail settings + +This endpoint allows you to retrieve your current Spam Checker mail settings. + +The spam checker filter notifies you when emails are detected that exceed a predefined spam threshold. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +GET /mail_settings/spam_check + + +response = sg.client.mail_settings.spam_check.get() +print response.status_code +print response.body +print response.headers +Update template mail settings + +This endpoint allows you to update your current legacy email template settings. + +This setting refers to our original email templates. We currently support more fully featured transactional templates. + +The legacy email template setting wraps an HTML template around your email content. This can be useful for sending out marketing email and/or other HTML formatted messages. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +PATCH /mail_settings/template + + +data = { + "enabled": True, + "html_content": "<% body %>" +} +response = sg.client.mail_settings.template.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve legacy template mail settings + +This endpoint allows you to retrieve your current legacy email template settings. + +This setting refers to our original email templates. We currently support more fully featured transactional templates. + +The legacy email template setting wraps an HTML template around your email content. This can be useful for sending out marketing email and/or other HTML formatted messages. + +Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. + +GET /mail_settings/template + + +response = sg.client.mail_settings.template.get() +print response.status_code +print response.body +print response.headers + + +MAILBOX PROVIDERS + +Retrieve email statistics by mailbox provider. + +This endpoint allows you to retrieve your email statistics segmented by recipient mailbox provider. + +We only store up to 7 days of email activity in our database. By default, 500 items will be returned per request via the Advanced Stats API endpoints. + +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. + +GET /mailbox_providers/stats + + +params = {'end_date': '2016-04-01', 'mailbox_providers': 'test_string', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01'} +response = sg.client.mailbox_providers.stats.get(query_params=params) +print response.status_code +print response.body +print response.headers + + +PARTNER SETTINGS + +Returns a list of all partner settings. + +This endpoint allows you to retrieve a list of all partner settings that you can enable. + +Our partner settings allow you to integrate your SendGrid account with our partners to increase your SendGrid experience and functionality. For more information about our partners, and how you can begin integrating with them, please visit our User Guide. + +GET /partner_settings + + +params = {'limit': 1, 'offset': 1} +response = sg.client.partner_settings.get(query_params=params) +print response.status_code +print response.body +print response.headers +Updates New Relic partner settings. + +This endpoint allows you to update or change your New Relic partner settings. + +Our partner settings allow you to integrate your SendGrid account with our partners to increase your SendGrid experience and functionality. For more information about our partners, and how you can begin integrating with them, please visit our User Guide. + +By integrating with New Relic, you can send your SendGrid email statistics to your New Relic Dashboard. If you enable this setting, your stats will be sent to New Relic every 5 minutes. You will need your New Relic License Key to enable this setting. For more information, please see our Classroom. + +PATCH /partner_settings/new_relic + + +data = { + "enable_subuser_statistics": True, + "enabled": True, + "license_key": "" +} +response = sg.client.partner_settings.new_relic.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Returns all New Relic partner settings. + +This endpoint allows you to retrieve your current New Relic partner settings. + +Our partner settings allow you to integrate your SendGrid account with our partners to increase your SendGrid experience and functionality. For more information about our partners, and how you can begin integrating with them, please visit our User Guide. + +By integrating with New Relic, you can send your SendGrid email statistics to your New Relic Dashboard. If you enable this setting, your stats will be sent to New Relic every 5 minutes. You will need your New Relic License Key to enable this setting. For more information, please see our Classroom. + +GET /partner_settings/new_relic + + +response = sg.client.partner_settings.new_relic.get() +print response.status_code +print response.body +print response.headers + + +SCOPES + +Retrieve a list of scopes for which this user has access. + +This endpoint returns a list of all scopes that this user has access to. + +API Keys can be used to authenticate the use of SendGrids v3 Web API, or the Mail API Endpoint. API Keys may be assigned certain permissions, or scopes, that limit which API endpoints they are able to access. For a more detailed explanation of how you can use API Key permissions, please visit our User Guide or Classroom. + +GET /scopes + + +response = sg.client.scopes.get() +print response.status_code +print response.body +print response.headers + + +SENDERS + +Create a Sender Identity + +This endpoint allows you to create a new sender identity. + +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. + +POST /senders + + +data = { + "address": "123 Elm St.", + "address_2": "Apt. 456", + "city": "Denver", + "country": "United States", + "from": { +​ "email": "from@example.com", +​ "name": "Example INC" + }, + "nickname": "My Sender ID", + "reply_to": { +​ "email": "replyto@example.com", +​ "name": "Example INC" + }, + "state": "Colorado", + "zip": "80202" +} +response = sg.client.senders.post(request_body=data) +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. + +GET /senders + + +response = sg.client.senders.get() +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. + +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. + +PATCH /senders/{sender_id} + + +data = { + "address": "123 Elm St.", + "address_2": "Apt. 456", + "city": "Denver", + "country": "United States", + "from": { +​ "email": "from@example.com", +​ "name": "Example INC" + }, + "nickname": "My Sender ID", + "reply_to": { +​ "email": "replyto@example.com", +​ "name": "Example INC" + }, + "state": "Colorado", + "zip": "80202" +} +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 +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. + +GET /senders/{sender_id} + + +sender_id = "test_url_param" +response = sg.client.senders._(sender_id).get() +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. + +DELETE /senders/{sender_id} + + +sender_id = "test_url_param" +response = sg.client.senders._(sender_id).delete() +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. + +POST /senders/{sender_id}/resend_verification + + +sender_id = "test_url_param" +response = sg.client.senders._(sender_id).resend_verification.post() +print response.status_code +print response.body +print response.headers + + +STATS + +Retrieve global email statistics + +This endpoint allows you to retrieve all of your global email statistics between a given date range. + +Parent accounts will see aggregated stats for their account and all subuser accounts. Subuser accounts will only see their own stats. + +GET /stats + + +params = {'aggregated_by': 'day', 'limit': 1, 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} +response = sg.client.stats.get(query_params=params) +print response.status_code +print response.body +print response.headers + + +SUBUSERS + +Create Subuser + +This endpoint allows you to retrieve a list of all of your subusers. You can choose to retrieve specific subusers as well as limit the results that come back from the API. + +For more information about Subusers: + +User Guide > Subusers + +Classroom > How do I add more subusers to my account? + +POST /subusers + + +data = { + "email": "John@example.com", + "ips": [ +​ "1.1.1.1", +​ "2.2.2.2" + ], + "password": "johns_password", + "username": "John@example.com" +} +response = sg.client.subusers.post(request_body=data) +print response.status_code +print response.body +print response.headers +List all Subusers + +This endpoint allows you to retrieve a list of all of your subusers. You can choose to retrieve specific subusers as well as limit the results that come back from the API. + +For more information about Subusers: + +User Guide > Subusers + +Classroom > How do I add more subusers to my account? + +GET /subusers + + +params = {'username': 'test_string', 'limit': 1, 'offset': 1} +response = sg.client.subusers.get(query_params=params) +print response.status_code +print response.body +print response.headers +Retrieve Subuser Reputations + +Subuser sender reputations give a good idea how well a sender is doing with regards to how recipients and recipient servers react to the mail that is being received. When a bounce, spam report, or other negative action happens on a sent email, it will effect your sender rating. + +This endpoint allows you to request the reputations for your subusers. + +GET /subusers/reputations + + +params = {'usernames': 'test_string'} +response = sg.client.subusers.reputations.get(query_params=params) +print response.status_code +print response.body +print response.headers +Retrieve email statistics for your subusers. + +This endpoint allows you to retrieve the email statistics for the given subusers. + +You may retrieve statistics for up to 10 different subusers by including an additional subusers parameter for each additional subuser. + +While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. + +For more information, see our User Guide. + +GET /subusers/stats + + +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 +Retrieve monthly stats for all subusers + +This endpoint allows you to retrieve the monthly email statistics for all subusers over the given date range. + +While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats for your subusers. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. + +When using the sort_by_metric to sort your stats by a specific metric, you can not sort by the following metrics: +bounce_drops, deferred, invalid_emails, processed, spam_report_drops, spam_reports, or unsubscribe_drops. + +For more information, see our User Guide. + +GET /subusers/stats/monthly + + +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 +Retrieve the totals for each email statistic metric for all subusers. + +This endpoint allows you to retrieve the total sums of each email statistic metric for all subusers over the given date range. + +While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. + +For more information, see our User Guide. + +GET /subusers/stats/sums + + +params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} +response = sg.client.subusers.stats.sums.get(query_params=params) +print response.status_code +print response.body +print response.headers +Enable/disable a subuser + +This endpoint allows you to enable or disable a subuser. + +For more information about Subusers: + +User Guide > Subusers + +Classroom > How do I add more subusers to my account? + +PATCH /subusers/{subuser_name} + + +data = { + "disabled": False +} +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).patch(request_body=data) +print response.status_code +print response.body +print response.headers +Delete a subuser + +This endpoint allows you to delete a subuser. This is a permanent action, once you delete a subuser it cannot be retrieved. + +For more information about Subusers: + +User Guide > Subusers + +Classroom > How do I add more subusers to my account? + +DELETE /subusers/{subuser_name} + + +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).delete() +print response.status_code +print response.body +print response.headers +Update IPs assigned to a subuser + +Each subuser should be assigned to an IP address, from which all of this subuser's mail will be sent. Often, this is the same IP as the parent account, but each subuser can have their own, or multiple, IP addresses as well. + +More information: + +How to request more IPs + +IPs can be whitelabeled + +PUT /subusers/{subuser_name}/ips + + +data = [ + "127.0.0.1" +] +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).ips.put(request_body=data) +print response.status_code +print response.body +print response.headers +Update Monitor Settings for a subuser + +Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. + +PUT /subusers/{subuser_name}/monitor + + +data = { + "email": "example@example.com", + "frequency": 500 +} +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.put(request_body=data) +print response.status_code +print response.body +print response.headers +Create monitor settings + +Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. + +POST /subusers/{subuser_name}/monitor + + +data = { + "email": "example@example.com", + "frequency": 50000 +} +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.post(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve monitor settings for a subuser + +Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. + +GET /subusers/{subuser_name}/monitor + + +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.get() +print response.status_code +print response.body +print response.headers +Delete monitor settings + +Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. + +DELETE /subusers/{subuser_name}/monitor + + +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.delete() +print response.status_code +print response.body +print response.headers +Retrieve the monthly email statistics for a single subuser + +This endpoint allows you to retrieve the monthly email statistics for a specific subuser. + +While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats for your subusers. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. + +When using the sort_by_metric to sort your stats by a specific metric, you can not sort by the following metrics: +bounce_drops, deferred, invalid_emails, processed, spam_report_drops, spam_reports, or unsubscribe_drops. + +For more information, see our User Guide. + +GET /subusers/{subuser_name}/stats/monthly + + +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 + + +SUPPRESSION + +Retrieve all blocks + +This endpoint allows you to retrieve a list of all email addresses that are currently on your blocks list. + +Blocks happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. + +For more information, please see our User Guide. + +GET /suppression/blocks + + +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 +Delete blocks + +This endpoint allows you to delete all email addresses on your blocks list. + +There are two options for deleting blocked emails: + +You can delete all blocked emails by setting delete_all to true in the request body. + +You can delete some blocked emails by specifying the email addresses in an array in the request body. + +Blocks happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. + +For more information, please see our User Guide. + +DELETE /suppression/blocks + + +data = { + "delete_all": False, + "emails": [ +​ "example1@example.com", +​ "example2@example.com" + ] +} +response = sg.client.suppression.blocks.delete(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve a specific block + +This endpoint allows you to retrieve a specific email address from your blocks list. + +Blocks happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. + +For more information, please see our User Guide. + +GET /suppression/blocks/{email} + + +email = "test_url_param" +response = sg.client.suppression.blocks._(email).get() +print response.status_code +print response.body +print response.headers +Delete a specific block + +This endpoint allows you to delete a specific email address from your blocks list. + +Blocks happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. + +For more information, please see our User Guide. + +DELETE /suppression/blocks/{email} + + +email = "test_url_param" +response = sg.client.suppression.blocks._(email).delete() +print response.status_code +print response.body +print response.headers +Retrieve all bounces + +This endpoint allows you to retrieve all of your bounces. + +Bounces are messages that are returned to the server that sent it. + +For more information see: + +User Guide > Bounces for more information + +Glossary > Bounces + +GET /suppression/bounces + + +params = {'start_time': 1, 'end_time': 1} +response = sg.client.suppression.bounces.get(query_params=params) +print response.status_code +print response.body +print response.headers +Delete bounces + +This endpoint allows you to delete all of your bounces. You can also use this endpoint to remove a specific email address from your bounce list. + +Bounces are messages that are returned to the server that sent it. + +For more information see: + +User Guide > Bounces for more information + +Glossary > Bounces + +Classroom > List Scrubbing Guide + +Note: the delete_all and emails parameters should be used independently of each other as they have different purposes. + +DELETE /suppression/bounces + + +data = { + "delete_all": True, + "emails": [ +​ "example@example.com", +​ "example2@example.com" + ] +} +response = sg.client.suppression.bounces.delete(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve a Bounce + +This endpoint allows you to retrieve a specific bounce for a given email address. + +Bounces are messages that are returned to the server that sent it. + +For more information see: + +User Guide > Bounces for more information + +Glossary > Bounces + +Classroom > List Scrubbing Guide + +GET /suppression/bounces/{email} + + +email = "test_url_param" +response = sg.client.suppression.bounces._(email).get() +print response.status_code +print response.body +print response.headers +Delete a bounce + +This endpoint allows you to remove an email address from your bounce list. + +Bounces are messages that are returned to the server that sent it. This endpoint allows you to delete a single email address from your bounce list. + +For more information see: + +User Guide > Bounces for more information + +Glossary > Bounces + +Classroom > List Scrubbing Guide + +DELETE /suppression/bounces/{email} + + +params = {'email_address': 'example@example.com'} +email = "test_url_param" +response = sg.client.suppression.bounces._(email).delete(query_params=params) +print response.status_code +print response.body +print response.headers +Retrieve all invalid emails + +This endpoint allows you to retrieve a list of all invalid email addresses. + +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. + +Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. + +For more information, please see our User Guide. + +GET /suppression/invalid_emails + + +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 +Delete invalid emails + +This endpoint allows you to remove email addresses from your invalid email address list. + +There are two options for deleting invalid email addresses: + +1) You can delete all invalid email addresses by setting delete_all to true in the request body. +2) You can delete some invalid email addresses by specifying certain addresses in an array in the request body. + +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. + +Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. + +For more information, please see our User Guide. + +DELETE /suppression/invalid_emails + + +data = { + "delete_all": False, + "emails": [ +​ "example1@example.com", +​ "example2@example.com" + ] +} +response = sg.client.suppression.invalid_emails.delete(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve a specific invalid email + +This endpoint allows you to retrieve a specific invalid email addresses. + +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. + +Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. + +For more information, please see our User Guide. + +GET /suppression/invalid_emails/{email} + + +email = "test_url_param" +response = sg.client.suppression.invalid_emails._(email).get() +print response.status_code +print response.body +print response.headers +Delete a specific invalid email + +This endpoint allows you to remove a specific email address from the invalid email address list. + +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. + +Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. + +For more information, please see our User Guide. + +DELETE /suppression/invalid_emails/{email} + + +email = "test_url_param" +response = sg.client.suppression.invalid_emails._(email).delete() +print response.status_code +print response.body +print response.headers +Retrieve a specific spam report + +This endpoint allows you to retrieve a specific spam report. + +Spam reports happen when a recipient indicates that they think your email is spam and then their email provider reports this to SendGrid. + +For more information, please see our User Guide. + +GET /suppression/spam_report/{email} + + +email = "test_url_param" +response = sg.client.suppression.spam_report._(email).get() +print response.status_code +print response.body +print response.headers +Delete a specific spam report + +This endpoint allows you to delete a specific spam report. + +Spam reports happen when a recipient indicates that they think your email is spam and then their email provider reports this to SendGrid. + +For more information, please see our User Guide. + +DELETE /suppression/spam_report/{email} + + +email = "test_url_param" +response = sg.client.suppression.spam_report._(email).delete() +print response.status_code +print response.body +print response.headers +Retrieve all spam reports + +This endpoint allows you to retrieve all spam reports. + +Spam reports happen when a recipient indicates that they think your email is spam and then their email provider reports this to SendGrid. + +For more information, please see our User Guide. + +GET /suppression/spam_reports + + +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 +Delete spam reports + +This endpoint allows you to delete your spam reports. + +There are two options for deleting spam reports: + +1) You can delete all spam reports by setting "delete_all" to true in the request body. +2) You can delete some spam reports by specifying the email addresses in an array in the request body. + +Spam reports happen when a recipient indicates that they think your email is spam and then their email provider reports this to SendGrid. + +For more information, please see our User Guide. + +DELETE /suppression/spam_reports + + +data = { + "delete_all": False, + "emails": [ +​ "example1@example.com", +​ "example2@example.com" + ] +} +response = sg.client.suppression.spam_reports.delete(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve all global suppressions + +This endpoint allows you to retrieve a list of all email address that are globally suppressed. + +A global suppression (or global unsubscribe) is an email address of a recipient who does not want to receive any of your messages. A globally suppressed recipient will be removed from any email you send. For more information, please see our User Guide. + +GET /suppression/unsubscribes + + +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 + + +TEMPLATES + +Create a transactional template. + +This endpoint allows you to create a transactional template. + +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. + +Transactional templates are templates created specifically for transactional email and are not to be confused with Marketing Campaigns templates. For more information about transactional templates, please see our User Guide. + +POST /templates + + +data = { + "name": "example_name" +} +response = sg.client.templates.post(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve all transactional templates. + +This endpoint allows you to retrieve all transactional templates. + +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. + +Transactional templates are templates created specifically for transactional email and are not to be confused with Marketing Campaigns templates. For more information about transactional templates, please see our User Guide. + +GET /templates + + +response = sg.client.templates.get() +print response.status_code +print response.body +print response.headers +Edit a transactional template. + +This endpoint allows you to edit a transactional template. + +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. + +Transactional templates are templates created specifically for transactional email and are not to be confused with Marketing Campaigns templates. For more information about transactional templates, please see our User Guide. + +PATCH /templates/{template_id} + + +data = { + "name": "new_example_name" +} +template_id = "test_url_param" +response = sg.client.templates._(template_id).patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve a single transactional template. + +This endpoint allows you to retrieve a single transactional template. + +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. + +Transactional templates are templates created specifically for transactional email and are not to be confused with Marketing Campaigns templates. For more information about transactional templates, please see our User Guide. + +GET /templates/{template_id} + + +template_id = "test_url_param" +response = sg.client.templates._(template_id).get() +print response.status_code +print response.body +print response.headers +Delete a template. + +This endpoint allows you to delete a transactional template. + +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. + +Transactional templates are templates created specifically for transactional email and are not to be confused with Marketing Campaigns templates. For more information about transactional templates, please see our User Guide. + +DELETE /templates/{template_id} + + +template_id = "test_url_param" +response = sg.client.templates._(template_id).delete() +print response.status_code +print response.body +print response.headers +Create a new transactional template version. + +This endpoint allows you to create a new version of a template. + +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. + +For more information about transactional templates, please see our User Guide. + +POST /templates/{template_id}/versions + + +data = { + "active": 1, + "html_content": "<%body%>", + "name": "example_version_name", + "plain_content": "<%body%>", + "subject": "<%subject%>", + "template_id": "ddb96bbc-9b92-425e-8979-99464621b543" +} +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 +Edit a transactional template version. + +This endpoint allows you to edit a version of one of your transactional templates. + +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. + +For more information about transactional templates, please see our User Guide. + +URI Parameters + +URI PARAMETER TYPE DESCRIPTION +template_id string The ID of the original template +version_id string The ID of the template version +PATCH /templates/{template_id}/versions/{version_id} + + +data = { + "active": 1, + "html_content": "<%body%>", + "name": "updated_example_name", + "plain_content": "<%body%>", + "subject": "<%subject%>" +} +template_id = "test_url_param" +version_id = "test_url_param" +response = sg.client.templates._(template_id).versions._(version_id).patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve a specific transactional template version. + +This endpoint allows you to retrieve a specific version of a template. + +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. + +For more information about transactional templates, please see our User Guide. + +URI Parameters + +URI PARAMETER TYPE DESCRIPTION +template_id string The ID of the original template +version_id string The ID of the template version +GET /templates/{template_id}/versions/{version_id} + + +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 +Delete a transactional template version. + +This endpoint allows you to delete one of your transactional template versions. + +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. + +For more information about transactional templates, please see our User Guide. + +URI Parameters + +URI PARAMETER TYPE DESCRIPTION +template_id string The ID of the original template +version_id string The ID of the template version +DELETE /templates/{template_id}/versions/{version_id} + + +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 +Activate a transactional template version. + +This endpoint allows you to activate a version of one of your templates. + +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. + +For more information about transactional templates, please see our User Guide. + +URI Parameters + +URI PARAMETER TYPE DESCRIPTION +template_id string The ID of the original template +version_id string The ID of the template version +POST /templates/{template_id}/versions/{version_id}/activate + + +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 + + +TRACKING SETTINGS + +Retrieve Tracking Settings + +This endpoint allows you to retrieve a list of all tracking settings that you can enable on your account. + +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. + +For more information about tracking, please see our User Guide. + +GET /tracking_settings + + +params = {'limit': 1, 'offset': 1} +response = sg.client.tracking_settings.get(query_params=params) +print response.status_code +print response.body +print response.headers +Update Click Tracking Settings + +This endpoint allows you to change your current click tracking setting. You can enable, or disable, click tracking using this endpoint. + +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. + +For more information about tracking, please see our User Guide. + +PATCH /tracking_settings/click + + +data = { + "enabled": True +} +response = sg.client.tracking_settings.click.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve Click Track Settings + +This endpoint allows you to retrieve your current click tracking setting. + +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. + +For more information about tracking, please see our User Guide. + +GET /tracking_settings/click + + +response = sg.client.tracking_settings.click.get() +print response.status_code +print response.body +print response.headers +Update Google Analytics Settings + +This endpoint allows you to update your current setting for Google Analytics. + +For more information about using Google Analytics, please refer to Googles URL Builder and their article on "Best Practices for Campaign Building". + +We default the settings to Googles recommendations. For more information, see Google Analytics Demystified. + +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. + +For more information about tracking, please see our User Guide. + +PATCH /tracking_settings/google_analytics + + +data = { + "enabled": True, + "utm_campaign": "website", + "utm_content": "", + "utm_medium": "email", + "utm_source": "sendgrid.com", + "utm_term": "" +} +response = sg.client.tracking_settings.google_analytics.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve Google Analytics Settings + +This endpoint allows you to retrieve your current setting for Google Analytics. + +For more information about using Google Analytics, please refer to Googles URL Builder and their article on "Best Practices for Campaign Building". + +We default the settings to Googles recommendations. For more information, see Google Analytics Demystified. + +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. + +For more information about tracking, please see our User Guide. + +GET /tracking_settings/google_analytics + + +response = sg.client.tracking_settings.google_analytics.get() +print response.status_code +print response.body +print response.headers +Update Open Tracking Settings + +This endpoint allows you to update your current settings for open tracking. + +Open Tracking adds an invisible image at the end of the email which can track email opens. If the email recipient has images enabled on their email client, a request to SendGrids server for the invisible image is executed and an open event is logged. These events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook. + +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. + +For more information about tracking, please see our User Guide. + +PATCH /tracking_settings/open + + +data = { + "enabled": True +} +response = sg.client.tracking_settings.open.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Get Open Tracking Settings + +This endpoint allows you to retrieve your current settings for open tracking. + +Open Tracking adds an invisible image at the end of the email which can track email opens. If the email recipient has images enabled on their email client, a request to SendGrids server for the invisible image is executed and an open event is logged. These events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook. + +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. + +For more information about tracking, please see our User Guide. + +GET /tracking_settings/open + + +response = sg.client.tracking_settings.open.get() +print response.status_code +print response.body +print response.headers +Update Subscription Tracking Settings + +This endpoint allows you to update your current settings for subscription tracking. + +Subscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails. + +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. + +For more information about tracking, please see our User Guide. + +PATCH /tracking_settings/subscription + + +data = { + "enabled": True, + "html_content": "html content", + "landing": "landing page html", + "plain_content": "text content", + "replace": "replacement tag", + "url": "url" +} +response = sg.client.tracking_settings.subscription.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve Subscription Tracking Settings + +This endpoint allows you to retrieve your current settings for subscription tracking. + +Subscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails. + +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. + +For more information about tracking, please see our User Guide. + +GET /tracking_settings/subscription + + +response = sg.client.tracking_settings.subscription.get() +print response.status_code +print response.body +print response.headers + + +USER + +Get a user's account information. + +This endpoint allows you to retrieve your user account details. + +Your user's account information includes the user's account type and reputation. + +Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. + +For more information about your user profile: + +SendGrid Account Settings + +GET /user/account + + +response = sg.client.user.account.get() +print response.status_code +print response.body +print response.headers +Retrieve your credit balance + +This endpoint allows you to retrieve the current credit balance for your account. + +Your monthly credit allotment limits the number of emails you may send before incurring overage charges. For more information about credits and billing, please visit our Classroom. + +GET /user/credits + + +response = sg.client.user.credits.get() +print response.status_code +print response.body +print response.headers +Update your account email address + +This endpoint allows you to update the email address currently on file for your account. + +Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. + +For more information about your user profile: + +SendGrid Account Settings + +PUT /user/email + + +data = { + "email": "example@example.com" +} +response = sg.client.user.email.put(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve your account email address + +This endpoint allows you to retrieve the email address currently on file for your account. + +Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. + +For more information about your user profile: + +SendGrid Account Settings + +GET /user/email + + +response = sg.client.user.email.get() +print response.status_code +print response.body +print response.headers +Update your password + +This endpoint allows you to update your password. + +Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. + +For more information about your user profile: + +SendGrid Account Settings + +PUT /user/password + + +data = { + "new_password": "new_password", + "old_password": "old_password" +} +response = sg.client.user.password.put(request_body=data) +print response.status_code +print response.body +print response.headers +Update a user's profile + +This endpoint allows you to update your current profile details. + +Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. + +For more information about your user profile: + +SendGrid Account Settings + +It should be noted that any one or more of the parameters can be updated via the PATCH /user/profile endpoint. The only requirement is that you include at least one when you PATCH. + +PATCH /user/profile + + +data = { + "city": "Orange", + "first_name": "Example", + "last_name": "User" +} +response = sg.client.user.profile.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Get a user's profile + +Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. + +For more information about your user profile: + +SendGrid Account Settings + +GET /user/profile + + +response = sg.client.user.profile.get() +print response.status_code +print response.body +print response.headers +Cancel or pause a scheduled send + +This endpoint allows you to cancel or pause an email that has been scheduled to be sent. + +If the maximum number of cancellations/pauses are added, HTTP 400 will +be returned. + +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. + +POST /user/scheduled_sends + + +data = { + "batch_id": "YOUR_BATCH_ID", + "status": "pause" +} +response = sg.client.user.scheduled_sends.post(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve all scheduled sends + +This endpoint allows you to retrieve all cancel/paused scheduled send information. + +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. + +GET /user/scheduled_sends + + +response = sg.client.user.scheduled_sends.get() +print response.status_code +print response.body +print response.headers +Update user scheduled send information + +This endpoint allows you to update the status of a scheduled send for the given batch_id. + +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. + +PATCH /user/scheduled_sends/{batch_id} + + +data = { + "status": "pause" +} +batch_id = "test_url_param" +response = sg.client.user.scheduled_sends._(batch_id).patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve scheduled send + +This endpoint allows you to retrieve the cancel/paused scheduled send information for a specific batch_id. + +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. + +GET /user/scheduled_sends/{batch_id} + + +batch_id = "test_url_param" +response = sg.client.user.scheduled_sends._(batch_id).get() +print response.status_code +print response.body +print response.headers +Delete a cancellation or pause of a scheduled send + +This endpoint allows you to delete the cancellation/pause of a scheduled send. + +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. + +DELETE /user/scheduled_sends/{batch_id} + + +batch_id = "test_url_param" +response = sg.client.user.scheduled_sends._(batch_id).delete() +print response.status_code +print response.body +print response.headers +Update Enforced TLS settings + +This endpoint allows you to update your current Enforced TLS settings. + +The Enforced TLS settings specify whether or not the recipient is required to support TLS or have a valid certificate. See the SMTP Ports User Guide for more information on opportunistic TLS. + +Note: If either setting is enabled and the recipient does not support TLS or have a valid certificate, we drop the message and send a block event with TLS required but not supported as the description. + +PATCH /user/settings/enforced_tls + + +data = { + "require_tls": True, + "require_valid_cert": False +} +response = sg.client.user.settings.enforced_tls.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve current Enforced TLS settings. + +This endpoint allows you to retrieve your current Enforced TLS settings. + +The Enforced TLS settings specify whether or not the recipient is required to support TLS or have a valid certificate. See the SMTP Ports User Guide for more information on opportunistic TLS. + +Note: If either setting is enabled and the recipient does not support TLS or have a valid certificate, we drop the message and send a block event with TLS required but not supported as the description. + +GET /user/settings/enforced_tls + + +response = sg.client.user.settings.enforced_tls.get() +print response.status_code +print response.body +print response.headers +Update your username + +This endpoint allows you to update the username for your account. + +Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. + +For more information about your user profile: + +SendGrid Account Settings + +PUT /user/username + + +data = { + "username": "test_username" +} +response = sg.client.user.username.put(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve your username + +This endpoint allows you to retrieve your current account username. + +Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. + +For more information about your user profile: + +SendGrid Account Settings + +GET /user/username + + +response = sg.client.user.username.get() +print response.status_code +print response.body +print response.headers +Update Event Notification Settings + +This endpoint allows you to update your current event webhook settings. + +If an event type is marked as true, then the event webhook will include information about that event. + +SendGrids Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email. + +Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. + +PATCH /user/webhooks/event/settings + + +data = { + "bounce": True, + "click": True, + "deferred": True, + "delivered": True, + "dropped": True, + "enabled": True, + "group_resubscribe": True, + "group_unsubscribe": True, + "open": True, + "processed": True, + "spam_report": True, + "unsubscribe": True, + "url": "url" +} +response = sg.client.user.webhooks.event.settings.patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve Event Webhook settings + +This endpoint allows you to retrieve your current event webhook settings. + +If an event type is marked as true, then the event webhook will include information about that event. + +SendGrids Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email. + +Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. + +GET /user/webhooks/event/settings + + +response = sg.client.user.webhooks.event.settings.get() +print response.status_code +print response.body +print response.headers +Test Event Notification Settings + +This endpoint allows you to test your event webhook by sending a fake event notification post to the provided URL. + +SendGrids Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email. + +Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. + +POST /user/webhooks/event/test + + +data = { + "url": "url" +} +response = sg.client.user.webhooks.event.test.post(request_body=data) +print response.status_code +print response.body +print response.headers +Create a parse setting + +This endpoint allows you to create a new inbound parse setting. + +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our User Guide. + +POST /user/webhooks/parse/settings + + +data = { + "hostname": "myhostname.com", + "send_raw": False, + "spam_check": True, + "url": "http://email.myhosthame.com" +} +response = sg.client.user.webhooks.parse.settings.post(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve all parse settings + +This endpoint allows you to retrieve all of your current inbound parse settings. + +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our User Guide. + +GET /user/webhooks/parse/settings + + +response = sg.client.user.webhooks.parse.settings.get() +print response.status_code +print response.body +print response.headers +Update a parse setting + +This endpoint allows you to update a specific inbound parse setting. + +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our User Guide. + +PATCH /user/webhooks/parse/settings/{hostname} + + +data = { + "send_raw": True, + "spam_check": False, + "url": "http://newdomain.com/parse" +} +hostname = "test_url_param" +response = sg.client.user.webhooks.parse.settings._(hostname).patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve a specific parse setting + +This endpoint allows you to retrieve a specific inbound parse setting. + +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our User Guide. + +GET /user/webhooks/parse/settings/{hostname} + + +hostname = "test_url_param" +response = sg.client.user.webhooks.parse.settings._(hostname).get() +print response.status_code +print response.body +print response.headers +Delete a parse setting + +This endpoint allows you to delete a specific inbound parse setting. + +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our User Guide. + +DELETE /user/webhooks/parse/settings/{hostname} + + +hostname = "test_url_param" +response = sg.client.user.webhooks.parse.settings._(hostname).delete() +print response.status_code +print response.body +print response.headers +Retrieves Inbound Parse Webhook statistics. + +This endpoint allows you to retrieve the statistics for your Parse Webhook usage. + +SendGrid's Inbound Parse Webhook allows you to parse the contents and attachments of incoming emails. The Parse API can then POST the parsed emails to a URL that you specify. The Inbound Parse Webhook cannot parse messages greater than 20MB in size, including all attachments. + +There are a number of pre-made integrations for the SendGrid Parse Webhook which make processing events easy. You can find these integrations in the Library Index. + +GET /user/webhooks/parse/stats + + +params = {'aggregated_by': 'day', 'limit': 'test_string', 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 'test_string'} +response = sg.client.user.webhooks.parse.stats.get(query_params=params) +print response.status_code +print response.body +print response.headers + + +WHITELABEL + +Create a domain whitelabel. + +This endpoint allows you to create a whitelabel for one of your domains. + +If you are creating a domain whitelabel that you would like a subuser to use, you have two options: + +Use the "username" parameter. This allows you to create a whitelabel on behalf of your subuser. This means the subuser is able to see and modify the created whitelabel. + +Use the Association workflow (see Associate Domain section). This allows you to assign a whitelabel created by the parent to a subuser. This means the subuser will default to the assigned whitelabel, but will not be able to see or modify that whitelabel. However, if the subuser creates their own whitelabel it will overwrite the assigned whitelabel. + +A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +For more information on whitelabeling, please see our User Guide + +POST /whitelabel/domains + + +data = { + "automatic_security": False, + "custom_spf": True, + "default": True, + "domain": "example.com", + "ips": [ +​ "192.168.1.1", +​ "192.168.1.2" + ], + "subdomain": "news", + "username": "john@example.com" +} +response = sg.client.whitelabel.domains.post(request_body=data) +print response.status_code +print response.body +print response.headers +List all domain whitelabels. + +This endpoint allows you to retrieve a list of all domain whitelabels you have created. + +A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +For more information on whitelabeling, please see our User Guide + +GET /whitelabel/domains + + +params = {'username': 'test_string', 'domain': 'test_string', 'exclude_subusers': 'true', 'limit': 1, 'offset': 1} +response = sg.client.whitelabel.domains.get(query_params=params) +print response.status_code +print response.body +print response.headers +Get the default domain whitelabel. + +This endpoint allows you to retrieve the default whitelabel for a domain. + +A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +For more information on whitelabeling, please see our User Guide + +URI Parameters + +URI PARAMETER TYPE DESCRIPTION +domain string The domain to find a default domain whitelabel for. +GET /whitelabel/domains/default + + +response = sg.client.whitelabel.domains.default.get() +print response.status_code +print response.body +print response.headers +List the domain whitelabel associated with the given user. + +This endpoint allows you to retrieve all of the whitelabels that have been assigned to a specific subuser. + +A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +Domain whitelabels can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's whitelabels. To associate a whitelabel with a subuser, the parent account must first create the whitelabel and validate it. The parent may then associate the whitelabel via the subuser management tools. + +For more information on whitelabeling, please see our User Guide + +URI Parameters + +URI PARAMETER TYPE DESCRIPTION +username string Username of the subuser to find associated whitelabels for. +GET /whitelabel/domains/subuser + + +response = sg.client.whitelabel.domains.subuser.get() +print response.status_code +print response.body +print response.headers +Disassociate a domain whitelabel from a given user. + +This endpoint allows you to disassociate a specific whitelabel from a subuser. + +A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +Domain whitelabels can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's whitelabels. To associate a whitelabel with a subuser, the parent account must first create the whitelabel and validate it. The parent may then associate the whitelabel via the subuser management tools. + +For more information on whitelabeling, please see our User Guide + +URI Parameters + +URI PARAMETER TYPE REQUIRED? DESCRIPTION +username string required Username for the subuser to find associated whitelabels for. +DELETE /whitelabel/domains/subuser + + +response = sg.client.whitelabel.domains.subuser.delete() +print response.status_code +print response.body +print response.headers +Update a domain whitelabel. + +This endpoint allows you to update the settings for a domain whitelabel. + +A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +For more information on whitelabeling, please see our User Guide + +PATCH /whitelabel/domains/{domain_id} + + +data = { + "custom_spf": True, + "default": False +} +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 +Retrieve a domain whitelabel. + +This endpoint allows you to retrieve a specific domain whitelabel. + +A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +For more information on whitelabeling, please see our User Guide + +GET /whitelabel/domains/{domain_id} + + +domain_id = "test_url_param" +response = sg.client.whitelabel.domains._(domain_id).get() +print response.status_code +print response.body +print response.headers +Delete a domain whitelabel. + +This endpoint allows you to delete a domain whitelabel. + +A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +For more information on whitelabeling, please see our User Guide + +DELETE /whitelabel/domains/{domain_id} + + +domain_id = "test_url_param" +response = sg.client.whitelabel.domains._(domain_id).delete() +print response.status_code +print response.body +print response.headers +Associate a domain whitelabel with a given user. + +This endpoint allows you to associate a specific domain whitelabel with a subuser. + +A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +Domain whitelabels can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's whitelabels. To associate a whitelabel with a subuser, the parent account must first create the whitelabel and validate it. The parent may then associate the whitelabel via the subuser management tools. + +For more information on whitelabeling, please see our User Guide + +URI Parameters + +URI PARAMETER TYPE DESCRIPTION +domain_id integer ID of the domain whitelabel to associate with the subuser. +POST /whitelabel/domains/{domain_id}/subuser + + +data = { + "username": "jane@example.com" +} +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 +Add an IP to a domain whitelabel. + +This endpoint allows you to add an IP address to a domain whitelabel. + +A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +For more information on whitelabeling, please see our User Guide + +URI Parameters + +URI PARAMETER TYPE DESCRIPTION +id integer ID of the domain to which you are adding an IP +POST /whitelabel/domains/{id}/ips + + +data = { + "ip": "192.168.0.1" +} +id = "test_url_param" +response = sg.client.whitelabel.domains._(id).ips.post(request_body=data) +print response.status_code +print response.body +print response.headers +Remove an IP from a domain whitelabel. + +This endpoint allows you to remove a domain's IP address from that domain's whitelabel. + +A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +For more information on whitelabeling, please see our User Guide + +URI Parameters + +URI PARAMETER TYPE DESCRIPTION +id integer ID of the domain whitelabel to delete the IP from. +ip string IP to remove from the domain whitelabel. +DELETE /whitelabel/domains/{id}/ips/{ip} + + +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 +Validate a domain whitelabel. + +This endpoint allows you to validate a domain whitelabel. If it fails, it will return an error message describing why the whitelabel could not be validated. + +A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +For more information on whitelabeling, please see our User Guide + +URI Parameters + +URI PARAMETER TYPE DESCRIPTION +id integer ID of the domain whitelabel to validate. +POST /whitelabel/domains/{id}/validate + + +id = "test_url_param" +response = sg.client.whitelabel.domains._(id).validate.post() +print response.status_code +print response.body +print response.headers +Create an IP whitelabel + +This endpoint allows you to create an IP whitelabel. + +When creating an IP whitelable, you should use the same subdomain that you used when you created a domain whitelabel. + +A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. + +For more information, please see our User Guide. + +POST /whitelabel/ips + + +data = { + "domain": "example.com", + "ip": "192.168.1.1", + "subdomain": "email" +} +response = sg.client.whitelabel.ips.post(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve all IP whitelabels + +This endpoint allows you to retrieve all of the IP whitelabels that have been created by this account. + +You may include a search key by using the "ip" parameter. This enables you to perform a prefix search for a given IP segment (e.g. "192."). + +A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. + +For more information, please see our User Guide. + +GET /whitelabel/ips + + +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 +Retrieve an IP whitelabel + +This endpoint allows you to retrieve an IP whitelabel. + +A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. + +For more information, please see our User Guide. + +GET /whitelabel/ips/{id} + + +id = "test_url_param" +response = sg.client.whitelabel.ips._(id).get() +print response.status_code +print response.body +print response.headers +Delete an IP whitelabel + +This endpoint allows you to delete an IP whitelabel. + +A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. + +For more information, please see our User Guide. + +DELETE /whitelabel/ips/{id} + + +id = "test_url_param" +response = sg.client.whitelabel.ips._(id).delete() +print response.status_code +print response.body +print response.headers +Validate an IP whitelabel + +This endpoint allows you to validate an IP whitelabel. + +A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. + +For more information, please see our User Guide. + +POST /whitelabel/ips/{id}/validate + + +id = "test_url_param" +response = sg.client.whitelabel.ips._(id).validate.post() +print response.status_code +print response.body +print response.headers +Create a Link Whitelabel + +This endpoint allows you to create a new link whitelabel. + +Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. + +For more information, please see our User Guide. + +POST /whitelabel/links + + +data = { + "default": True, + "domain": "example.com", + "subdomain": "mail" +} +params = {'limit': 1, 'offset': 1} +response = sg.client.whitelabel.links.post(request_body=data, query_params=params) +print response.status_code +print response.body +print response.headers +Retrieve all link whitelabels + +This endpoint allows you to retrieve all link whitelabels. + +Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. + +For more information, please see our User Guide. + +GET /whitelabel/links + + +params = {'limit': 1} +response = sg.client.whitelabel.links.get(query_params=params) +print response.status_code +print response.body +print response.headers +Retrieve a Default Link Whitelabel + +This endpoint allows you to retrieve the default link whitelabel. + +Default link whitelabel is the actual link whitelabel to be used when sending messages. If there are multiple link whitelabels, the default is determined by the following order: + + +Validated link whitelabels marked as "default" +Legacy link whitelabels (migrated from the whitelabel wizard) +Default SendGrid link whitelabel (i.e. 100.ct.sendgrid.net) + +Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. + +For more information, please see our User Guide. + +GET /whitelabel/links/default + + +params = {'domain': 'test_string'} +response = sg.client.whitelabel.links.default.get(query_params=params) +print response.status_code +print response.body +print response.headers +Retrieve Associated Link Whitelabel + +This endpoint allows you to retrieve the associated link whitelabel for a subuser. + +Link whitelables can be associated with subusers from the parent account. This functionality allows +subusers to send mail using their parent's link whitelabels. To associate a link whitelabel, the parent account +must first create a whitelabel and validate it. The parent may then associate that whitelabel with a subuser via the API or the Subuser Management page in the user interface. + +Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. + +For more information, please see our User Guide. + +GET /whitelabel/links/subuser + + +params = {'username': 'test_string'} +response = sg.client.whitelabel.links.subuser.get(query_params=params) +print response.status_code +print response.body +print response.headers +Disassociate a Link Whitelabel + +This endpoint allows you to disassociate a link whitelabel from a subuser. + +Link whitelables can be associated with subusers from the parent account. This functionality allows +subusers to send mail using their parent's link whitelabels. To associate a link whitelabel, the parent account +must first create a whitelabel and validate it. The parent may then associate that whitelabel with a subuser via the API or the Subuser Management page in the user interface. + +Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. + +For more information, please see our User Guide. + +DELETE /whitelabel/links/subuser + + +params = {'username': 'test_string'} +response = sg.client.whitelabel.links.subuser.delete(query_params=params) +print response.status_code +print response.body +print response.headers +Update a Link Whitelabel + +This endpoint allows you to update a specific link whitelabel. You can use this endpoint to change a link whitelabel's default status. + +Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. + +For more information, please see our User Guide. + +PATCH /whitelabel/links/{id} + + +data = { + "default": True +} +id = "test_url_param" +response = sg.client.whitelabel.links._(id).patch(request_body=data) +print response.status_code +print response.body +print response.headers +Retrieve a Link Whitelabel + +This endpoint allows you to retrieve a specific link whitelabel. + +Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. + +For more information, please see our User Guide. + +GET /whitelabel/links/{id} + + +id = "test_url_param" +response = sg.client.whitelabel.links._(id).get() +print response.status_code +print response.body +print response.headers +Delete a Link Whitelabel + +This endpoint allows you to delete a link whitelabel. + +Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. + +For more information, please see our User Guide. + +DELETE /whitelabel/links/{id} + + +id = "test_url_param" +response = sg.client.whitelabel.links._(id).delete() +print response.status_code +print response.body +print response.headers +Validate a Link Whitelabel + +This endpoint allows you to validate a link whitelabel. + +Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. + +For more information, please see our User Guide. + +POST /whitelabel/links/{id}/validate + + +id = "test_url_param" +response = sg.client.whitelabel.links._(id).validate.post() +print response.status_code +print response.body +print response.headers +Associate a Link Whitelabel + +This endpoint allows you to associate a link whitelabel with a subuser account. + +Link whitelables can be associated with subusers from the parent account. This functionality allows +subusers to send mail using their parent's link whitelabels. To associate a link whitelabel, the parent account +must first create a whitelabel and validate it. The parent may then associate that whitelabel with a subuser via the API or the Subuser Management page in the user interface. + +Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. + +For more information, please see our User Guide. + +POST /whitelabel/links/{link_id}/subuser + + +data = { + "username": "jane@example.com" +} +link_id = "test_url_param" +response = sg.client.whitelabel.links._(link_id).subuser.post(request_body=data) +print response.status_code +print response.body +print response.headersPOST /access_settings/whitelist ```python diff --git a/docker-test/README.md b/docker-test/README.md index d44ebf759..487a17ee6 100644 --- a/docker-test/README.md +++ b/docker-test/README.md @@ -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/USAGE.md b/docker/USAGE.md index d869a77ce..0d3f90b90 100644 --- a/docker/USAGE.md +++ b/docker/USAGE.md @@ -77,7 +77,7 @@ Note that the paths you specify in `-v` must be absolute. 1. Install docker-compose on your machine. 2. Must copy sendgrid.env to .env file. -3. Edit .env file for yours versions and paths. +3. Edit .env file for your versions and paths. 4. Must create env folder for clone yours repo. 5. Have fun! :D diff --git a/proposals/mail-helper-refactor.md b/proposals/mail-helper-refactor.md index 5a0127fd6..a057b38e0 100644 --- a/proposals/mail-helper-refactor.md +++ b/proposals/mail-helper-refactor.md @@ -196,7 +196,7 @@ msg.custom_arg = [ 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') diff --git a/sendgrid/helpers/inbound/README.md b/sendgrid/helpers/inbound/README.md index bc69d1189..f8bec75ce 100644 --- a/sendgrid/helpers/inbound/README.md +++ b/sendgrid/helpers/inbound/README.md @@ -1,4 +1,4 @@ -**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 diff --git a/use_cases/asynchronous_mail_send.md b/use_cases/asynchronous_mail_send.md index 57dd61b2a..253b07a0c 100644 --- a/use_cases/asynchronous_mail_send.md +++ b/use_cases/asynchronous_mail_send.md @@ -2,7 +2,7 @@ ## Using `asyncio` (3.5+) -The built-in `asyncio` library can be used to send email in a non-blocking manner. `asyncio` helps us execute mail sending in a separate context, allowing us to continue execution of business logic without waiting for all our emails to send first. +The built-in `asyncio` library can be used to send email in a non-blocking manner. `asyncio` helps us execute mail sending in a separate context, allowing us to continue the execution of business logic without waiting for all our emails to send first. ```python import sendgrid diff --git a/use_cases/aws.md b/use_cases/aws.md index d07d5769c..83f3ca61c 100644 --- a/use_cases/aws.md +++ b/use_cases/aws.md @@ -2,14 +2,14 @@ This tutorial explains how to set up a simple "Hello Email" app on AWS, using the AWS CodeStar service. -We'll be creating a basic web service to send email via SendGrid. The application will run on AWS Lambda, and the "endpoint" will be via AWS API Gateway. +We'll be creating a basic web service to send an email via SendGrid. The application will run on AWS Lambda, and the "endpoint" will be via AWS API Gateway. -The neat thing is that CodeStar provides all of this in a pre-configured package. We just have to make some config changes, and push our code. +The neat thing is that CodeStar provides all of this in a pre-configured package. We just have to make some config changes and push our code. -Once this tutorial is complete, you'll have a basic web service for sending email that can be invoked via a link to your newly created API endpoint. +Once this tutorial is complete, you'll have a basic web service for sending an 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.6, 2.7, 3.4, or 3.5 are supported by the sendgrid Python library, however, I was able to utilize 3.6 with no issue. Before starting this tutorial, you will need to have access to an AWS account in which you are allowed to provision resources. This tutorial also assumes you've already created a SendGrid account with free-tier access. Finally, it is highly recommended you utilize [virtualenv](https://virtualenv.pypa.io/en/stable/). @@ -19,18 +19,18 @@ Before starting this tutorial, you will need to have access to an AWS account in ## 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 the "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. +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, changelog, build a 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 +Log in to your SendGrid account. Click on your username on the left-hand side of the UI and choose "Setup Guide" from the drop-down menu. On the "Welcome" menu, choose "Send Your First Email", and then "Integrate using our Web API or SMTP relay." Choose "Web API" as the recommended option on the next screen, as we'll be using that for this tutorial. For more information about creating API keys, see https://sendgrid.com/docs/Classroom/Send/How_Emails_Are_Sent/api_keys.html On the next menu, you have the option to choose what programming language you'll be using. The obvious choice for this tutorial will be Python. @@ -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 idiosyncrasies 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: ``` @@ -169,4 +169,4 @@ SENDGRID_API_KEY Now, go back to your project dashboard in CodeStar. Click on the link under "Application endpoints". After a moment, you should be greeted with JSON output indicating an email was successfully sent. -Congratulations, you've just used serverless technology to create an email sending app in AWS! \ No newline at end of file +Congratulations, you've just used serverless technology to create an email sending the app in AWS! \ No newline at end of file diff --git a/use_cases/django.md b/use_cases/django.md index 554e5fa21..c6b14dad2 100644 --- a/use_cases/django.md +++ b/use_cases/django.md @@ -1,4 +1,4 @@ -# Create a Django app to send email with SendGrid +# Create a Django app to send an email with SendGrid This tutorial explains how we set up a simple Django app to send an email with the SendGrid Python SDK and how we deploy our app to Heroku. @@ -75,7 +75,7 @@ def index(request): return HttpResponse('Email Sent!') ``` -**Note:** It would be best to change your to email from `test@example.com` to your own email, so that you can see the email you receive. +**Note:** It would be best to change your to email from `test@example.com` to your own email so that you can see the email you receive. Now the folder structure should look like this: @@ -92,7 +92,7 @@ hello-sendgrid └── requirements.txt ``` -Next we open the file `urls.py` in order to add the view we have just created to the Django URL dispatcher. +Next, we open the file `urls.py` in order to add the view we have just created to the Django URL dispatcher. ```python from django.conf.urls import url @@ -119,7 +119,7 @@ $ python manage.py migrate $ python manage.py runserver ``` -By default, it starts the development server at `http://127.0.0.1:8000/`. To test if we can send email or not, go to `http://127.0.0.1:8000/sendgrid/`. If it works, we should see the page says "Email Sent!". +By default, it starts the development server at `http://127.0.0.1:8000/`. To test if we can send an email or not, go to `http://127.0.0.1:8000/sendgrid/`. If it works, we should see the page says "Email Sent!". **Note:** If you use `test@example.com` as your from email, it's likely to go to your spam folder. To have the emails show up in your inbox, try using an email address at the domain you registered your SendGrid account. diff --git a/use_cases/flask_heroku.md b/use_cases/flask_heroku.md index ef0fa599a..fd55c77b5 100644 --- a/use_cases/flask_heroku.md +++ b/use_cases/flask_heroku.md @@ -1,4 +1,4 @@ -# Create a Flask app to send email with SendGrid +# Create a Flask app to send an email with SendGrid This tutorial explains how to deploy a simple Flask app, to send an email using the SendGrid Python SDK, on Heroku. From f90ece888dd55b4137aa9ee3853c0b1c8c563927 Mon Sep 17 00:00:00 2001 From: zkan Date: Sat, 6 Oct 2018 15:31:47 +0700 Subject: [PATCH 132/462] Test when env format is invalid, it should not set env --- test/test_config.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/test_config.py b/test/test_config.py index 301bacc92..fe720ad5c 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -41,7 +41,7 @@ def test_initialization(self): for key in keys: self.assertTrue(key in self.config.keys) - def test_init_environment(self): + def test_init_environment_should_set_env_from_dotenv(self): config_file = sendgrid.helpers.inbound.config.__file__ env_file_path = os.path.abspath(os.path.dirname(config_file)) + '/.env' with open(env_file_path, 'w') as f: @@ -49,3 +49,12 @@ def test_init_environment(self): Config() os.remove(env_file_path) self.assertEqual('RANDOM_VALUE', os.environ['RANDOM_VARIABLE']) + + def test_init_environment_should_not_set_env_if_format_is_invalid(self): + config_file = sendgrid.helpers.inbound.config.__file__ + env_file_path = os.path.abspath(os.path.dirname(config_file)) + '/.env' + with open(env_file_path, 'w') as f: + f.write('RANDOM_VARIABLE=RANDOM_VALUE=ANOTHER_RANDOM_VALUE') + Config() + os.remove(env_file_path) + self.assertIsNone(os.environ.get('RANDOM_VARIABLE')) From 2ba466632fdd3b7c14fe73f3c1820d8f0ea07e4e Mon Sep 17 00:00:00 2001 From: zkan Date: Sat, 6 Oct 2018 15:57:43 +0700 Subject: [PATCH 133/462] Correct assertion and improve Python coding style a bit --- test/test_config.py | 48 +++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/test/test_config.py b/test/test_config.py index fe720ad5c..658018858 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -15,29 +15,31 @@ def setUp(self): def test_initialization(self): endpoint = '/inbound' port = 5000 - debug_mode = True - keys = ['from', - 'attachments', - 'headers', - 'text', - 'envelope', - 'to', - 'html', - 'sender_ip', - 'attachment-info', - 'subject', - 'dkim', - 'SPF', - 'charsets', - 'content-ids', - 'spam_report', - 'spam_score', - 'email'] + keys = [ + 'from', + 'attachments', + 'headers', + 'text', + 'envelope', + 'to', + 'html', + 'sender_ip', + 'attachment-info', + 'subject', + 'dkim', + 'SPF', + 'charsets', + 'content-ids', + 'spam_report', + 'spam_score', + 'email', + ] host = 'http://127.0.0.1:5000/inbound' - self.assertTrue(debug_mode, self.config.debug_mode) - self.assertTrue(endpoint, self.config.endpoint) - self.assertTrue(host, self.config.host) - self.assertTrue(port, self.config.port) + + self.assertTrue(self.config.debug_mode) + self.assertEqual(self.config.endpoint, endpoint) + self.assertEqual(self.config.host, host) + self.assertEqual(self.config.port, port) for key in keys: self.assertTrue(key in self.config.keys) @@ -48,7 +50,7 @@ def test_init_environment_should_set_env_from_dotenv(self): f.write('RANDOM_VARIABLE=RANDOM_VALUE') Config() os.remove(env_file_path) - self.assertEqual('RANDOM_VALUE', os.environ['RANDOM_VARIABLE']) + self.assertEqual(os.environ['RANDOM_VARIABLE'], 'RANDOM_VALUE') def test_init_environment_should_not_set_env_if_format_is_invalid(self): config_file = sendgrid.helpers.inbound.config.__file__ From fb8d334ca7ce72aa228611e81bd9935426314b0f Mon Sep 17 00:00:00 2001 From: Arshad Kazmi Date: Sun, 7 Oct 2018 00:16:21 +0530 Subject: [PATCH 134/462] troubleshooting broken link fix --- TROUBLESHOOTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 1298ab7d1..1b71b4852 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -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 From fe4f43b3554e38ff6cd2ae8f378d71a0cb205cfe Mon Sep 17 00:00:00 2001 From: Joshua de Guzman Date: Mon, 8 Oct 2018 19:36:58 +0800 Subject: [PATCH 135/462] Fix helper mail_example redirection link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 023a289ec..4132b62b5 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ pip install sendgrid ## Hello Email -The following is the minimum needed code to send an email with the [/mail/send Helper](https://github.com/sendgrid/sendgrid-python/tree/master/sendgrid/helpers/mail) ([here](https://github.com/sendgrid/sendgrid-python/blob/master/examples/helpers/mail/mail_example.py#L20) is a full example): +The following is the minimum needed code to send an email with the [/mail/send Helper](https://github.com/sendgrid/sendgrid-python/tree/master/sendgrid/helpers/mail) ([here](https://github.com/sendgrid/sendgrid-python/blob/master/examples/helpers/mail_example.py) is a full example): ### With Mail Helper Class From 5fc2fa4502c7700ddb6e005fcd9abdb8a877f4ed Mon Sep 17 00:00:00 2001 From: StrikerRUS Date: Tue, 9 Oct 2018 13:16:10 +0300 Subject: [PATCH 136/462] converted README and version as plain text --- .gitignore | 2 +- .travis.yml | 3 - MANIFEST.in | 2 + README.md | 221 ----------------------------- README.rst | 317 ++++++++++++++++++++++++++++++++++++++++++ VERSION.txt | 1 + register.py | 20 --- sendgrid/__init__.py | 7 +- sendgrid/sendgrid.py | 3 +- sendgrid/version.py | 2 - setup.py | 25 ++-- test/test_project.py | 8 +- test/test_sendgrid.py | 3 +- 13 files changed, 348 insertions(+), 266 deletions(-) delete mode 100644 README.md create mode 100644 README.rst create mode 100644 VERSION.txt delete mode 100644 register.py delete mode 100644 sendgrid/version.py diff --git a/.gitignore b/.gitignore index 96232a5bf..4ac2623b4 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,4 @@ htmlcov temp*.py sendgrid.env .vscode - +sendgrid/VERSION.txt diff --git a/.travis.yml b/.travis.yml index 91c45daab..7897e2df2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,6 @@ 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 @@ -32,8 +31,6 @@ script: after_script: - codecov - ./cc-test-reporter after-build --exit-code $? -before_deploy: -- python ./register.py deploy: provider: pypi user: thinkingserious 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 ba6468f23..000000000 --- a/README.md +++ /dev/null @@ -1,221 +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) - -**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.7 and 3.4+ -- 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), 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..035ffaa82 --- /dev/null +++ b/README.rst @@ -0,0 +1,317 @@ +.. 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. +- 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.6, 2.7, 3.4, 3.5 or 3.6 +- 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 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`_ 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 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`_) +--------------------------------------------------- + +.. code:: 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`_) +------------------------------------------------------ + +.. code:: 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`_ 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/VERSION.txt b/VERSION.txt new file mode 100644 index 000000000..ade65226e --- /dev/null +++ b/VERSION.txt @@ -0,0 +1 @@ +5.4.1 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/sendgrid/__init__.py b/sendgrid/__init__.py index 2bbd38b59..050e798e9 100644 --- a/sendgrid/__init__.py +++ b/sendgrid/__init__.py @@ -15,7 +15,12 @@ Modules to help with common tasks. """ -from .version import __version__ # noqa +import os # v3 API from .sendgrid import SendGridAPIClient # noqa from .helpers.mail import Email # 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/sendgrid.py b/sendgrid/sendgrid.py index 5be0a651f..b1665cf61 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -18,8 +18,6 @@ import python_http_client -from .version import __version__ - class SendGridAPIClient(object): """The SendGrid API Client. @@ -62,6 +60,7 @@ def __init__( :param opts: dispatcher for deprecated arguments. Added for backward-compatibility with `path` parameter. Should be removed during 6.x release """ + from . import __version__ if opts: warnings.warn( 'Unsupported argument(s) provided: {}'.format(list(opts.keys())), 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 5ccaa10f5..6f3c4f95a 100644 --- a/setup.py +++ b/setup.py @@ -1,32 +1,33 @@ +import io import os -from io import open +import sys +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'] 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, 'VERSION.txt'), encoding='utf-8').read().strip() +copy_file(os.path.join(dir_path, '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=[ diff --git a/test/test_project.py b/test/test_project.py index a0b87908a..340388af6 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -56,9 +56,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): @@ -68,6 +68,10 @@ def test_troubleshooting(self): def test_usage(self): self.assertTrue(os.path.isfile('./USAGE.md')) + # ./VERSION.txt + def test_usage(self): + self.assertTrue(os.path.isfile('./VERSION.txt')) + # ./use-cases/README.md def test_use_cases(self): self.assertTrue(os.path.isfile('./use_cases/README.md')) diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index 214eb2de9..78976edb0 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -1,6 +1,5 @@ import sendgrid from sendgrid.helpers.mail import * -from sendgrid.version import __version__ import os import datetime import unittest @@ -95,7 +94,7 @@ def test_impersonate_subuser_init(self): self.assertEqual(sg_impersonate.impersonate_subuser, temp_subuser) def test_useragent(self): - useragent = '{}{}{}'.format('sendgrid/', __version__, ';python') + useragent = '{}{}{}'.format('sendgrid/', sendgrid.__version__, ';python') self.assertEqual(self.sg.useragent, useragent) def test_host(self): From 6807e5c02cd63149039c6933f9064d76778eb712 Mon Sep 17 00:00:00 2001 From: StrikerRUS Date: Tue, 9 Oct 2018 13:22:21 +0300 Subject: [PATCH 137/462] updated Python versions in README from v4 branch --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 035ffaa82..3cce78559 100644 --- a/README.rst +++ b/README.rst @@ -43,7 +43,7 @@ Installation Prerequisites ------------- -- Python version 2.6, 2.7, 3.4, 3.5 or 3.6 +- Python version 2.7 and 3.4+ - The SendGrid service, starting at the `free level`_ Setup Environment Variables From 3debda7566cb56a5a9457b3c5ebdc6a841e31837 Mon Sep 17 00:00:00 2001 From: StrikerRUS Date: Tue, 9 Oct 2018 13:40:57 +0300 Subject: [PATCH 138/462] hotfix for excess import --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 6f3c4f95a..9d4b87d7c 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,5 @@ import io import os -import sys from distutils.file_util import copy_file from setuptools import setup, find_packages From 03564f7b5512a0cfa34d034b256ddc6a4603655c Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Thu, 4 Oct 2018 08:54:06 -0400 Subject: [PATCH 139/462] Allow creation of Mail from EmailMessage --- sendgrid/helpers/mail/mail.py | 20 ++++++++++++++++++++ test/test_mail.py | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index f374e6ad1..de627b84f 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -141,3 +141,23 @@ def get(self): 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_email=Email(message.get('To')), + ) + mail.add_content(Content( + message.get_content_type(), + message.get_content() + )) + for k, v in message.items(): + mail.add_header(Header(k, v)) + return mail diff --git a/test/test_mail.py b/test/test_mail.py index 98af29a09..57a9c1008 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -2,6 +2,8 @@ import json import unittest +from email.message import EmailMessage + from sendgrid.helpers.mail import ( ASM, APIKeyIncludedException, @@ -557,3 +559,21 @@ def test_disable_tracking(self): def test_directly_setting_substitutions(self): personalization = Personalization() personalization.substitutions = [{'a': 0}] + + def test_from_emailmessage(self): + message = EmailMessage() + message.set_content('message that is not urgent') + message.set_default_type('text/plain') + message['Subject'] = 'URGENT TITLE' + message['From'] = 'test@example.com' + message['To'] = 'test@sendgrid.com' + mail = Mail.from_EmailMessage(message) + self.assertEqual(mail.subject, 'URGENT TITLE') + self.assertEqual(mail.from_email.email, 'test@example.com') + self.assertEqual(len(mail.personalizations), 1) + self.assertEqual(len(mail.personalizations[0].tos), 1) + self.assertDictEqual(mail.personalizations[0].tos[0], {'email': 'test@sendgrid.com'}) + self.assertEqual(len(mail.contents), 1) + content = mail.contents[0] + self.assertEqual(content.type, 'text/plain') + self.assertEqual(content.value, 'message that is not urgent\n') From 8e4e721f5c285bfe1cd36f7aaa5e1da99d0a877b Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Thu, 4 Oct 2018 09:01:03 -0400 Subject: [PATCH 140/462] Python2.7 import compatibility fix --- sendgrid/helpers/mail/mail.py | 9 ++++++++- test/test_mail.py | 16 +++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index de627b84f..c27fc72d7 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -1,6 +1,8 @@ """v3/mail/send response body builder""" from .personalization import Personalization from .header import Header +from .email import Email +from .content import Content class Mail(object): @@ -154,9 +156,14 @@ def from_EmailMessage(cls, message): subject=message.get('Subject'), to_email=Email(message.get('To')), ) + try: + body = message.get_content() + except AttributeError: + # Python2 + body = message.get_payload() mail.add_content(Content( message.get_content_type(), - message.get_content() + body.strip() )) for k, v in message.items(): mail.add_header(Header(k, v)) diff --git a/test/test_mail.py b/test/test_mail.py index 57a9c1008..d2cb37af9 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -2,7 +2,12 @@ import json import unittest -from email.message import EmailMessage +try: + from email.message import EmailMessage +except ImportError: + # Python2 + from email import message + EmailMessage = message.Message from sendgrid.helpers.mail import ( ASM, @@ -562,7 +567,12 @@ def test_directly_setting_substitutions(self): def test_from_emailmessage(self): message = EmailMessage() - message.set_content('message that is not urgent') + body = 'message that is not urgent' + try: + message.set_content(body) + except AttributeError: + # Python2 + message.set_payload(body) message.set_default_type('text/plain') message['Subject'] = 'URGENT TITLE' message['From'] = 'test@example.com' @@ -576,4 +586,4 @@ def test_from_emailmessage(self): self.assertEqual(len(mail.contents), 1) content = mail.contents[0] self.assertEqual(content.type, 'text/plain') - self.assertEqual(content.value, 'message that is not urgent\n') + self.assertEqual(content.value, 'message that is not urgent') From 935a4d70a0259b557452bb49278ef8d6947fa91d Mon Sep 17 00:00:00 2001 From: Xeon Zolt Date: Mon, 15 Oct 2018 15:28:20 +0530 Subject: [PATCH 141/462] fixed changes suggested --- CODE_OF_CONDUCT.md | 16 ++++++++-------- CONTRIBUTING.md | 8 ++++---- TROUBLESHOOTING.md | 2 +- use_cases/aws.md | 20 ++++++++++---------- use_cases/django.md | 6 +++--- use_cases/flask_heroku.md | 2 +- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 670a82f62..31743bc0e 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. @@ -9,15 +9,15 @@ Members of the community are considerate of their peers, which include other contributors and users of SendGrid. We're thoughtful when addressing the efforts of others, keeping in mind that often the labor was completed with the intent of the good of the community. We're attentive in our communications, whether in person or online, and we're tactful when approaching differing views. ### Be Respectful - Members of the community are respectful. We're respectful of others, their positions, their skills, their commitments ,and their efforts. We're respectful of the volunteer efforts that permeate the SendGrid community. We're respectful of the processes outlined in the community, and we work within them. When we disagree, we are courteous in raising our issues. Overall, we're good with each other. We contribute to this community not because we have to, but because we want to. If we remember that, these guidelines will come naturally. + Members of the community are respectful. We're respectful of others, their positions, their skills, their commitments, and their efforts. We're respectful of the volunteer efforts that permeate the SendGrid community. We're respectful of the processes outlined in the community, and we work within them. When we disagree, we are courteous in raising our issues. Overall, we're good with each other. We contribute to this community not because we have to, but because we want to. If we remember that, these guidelines will come naturally. - ## Additional Guidance + ## Additional Guidance ### Disclose Potential Conflicts of Interest Community discussions often involve interested parties. We expect participants to be aware when they are conflicted due to employment or other projects they are involved in and disclose those interests to other project members. When in doubt, over-disclose. Perceived conflicts of interest are important to address so that the community’s decisions are credible even when unpopular, difficult or favorable to the interests of one group over another. ### Interpretation - This Code is not exhaustive or complete. It is not a rulebook; it serves to distill our common understanding of a collaborative, shared environment and goals. We expect it to be followed in spirit as much as in the letter. When in doubt, try to abide by [SendGrid’s cultural values](https://sendgrid.com/blog/employee-engagement-the-4h-way) defined by our “4H’s”: Happy, Hungry, Humble and Honest. + 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 90f8a1f42..f3bba6d65 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,7 +62,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 - #### @@ -191,10 +191,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 ``` @@ -213,7 +213,7 @@ Please run your code through: git checkout -b ``` -4. Commit your changes in logical chunks. Please adhere to these [git commits +4. Commit your changes in logical chunks. Please adhere to these [git commit message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) or your code is unlikely to be merged into the main project. Use Git's [interactive rebase](https://help.github.com/articles/interactive-rebase) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index a33919a26..97eca9b55 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -95,7 +95,7 @@ If you are using a [requirements file](https://pip.readthedocs.io/en/1.1/require ## Versioning Convention -We follow the MAJOR.MINOR.PATCH versioning scheme as described by [SemVer.org](http://semver.org). Therefore, we recommend that you always pin (or vendor) the particular version you are working with your code and never auto-update to the latest version. Especially when there is a MAJOR point release since that is guaranteed to be a breaking change. Changes are documented in the [CHANGELOG](https://github.com/sendgrid/sendgrid-python/blob/master/CHANGELOG.md) and [releases](https://github.com/sendgrid/sendgrid-python/releases) section. +We follow the MAJOR.MINOR.PATCH versioning scheme as described by [SemVer.org](http://semver.org). Therefore, we recommend that you always pin (or vendor) the particular version you are working with in your code and never auto-update to the latest version. Especially when there is a MAJOR point release since that is guaranteed to be a breaking change. Changes are documented in the [CHANGELOG](https://github.com/sendgrid/sendgrid-python/blob/master/CHANGELOG.md) and [releases](https://github.com/sendgrid/sendgrid-python/releases) section. ## Viewing the Request Body diff --git a/use_cases/aws.md b/use_cases/aws.md index 83f3ca61c..2d8d25b00 100644 --- a/use_cases/aws.md +++ b/use_cases/aws.md @@ -2,11 +2,11 @@ This tutorial explains how to set up a simple "Hello Email" app on AWS, using the AWS CodeStar service. -We'll be creating a basic web service to send an email via SendGrid. The application will run on AWS Lambda, and the "endpoint" will be via AWS API Gateway. +We'll be creating a basic web service to send email via SendGrid. The application will run on AWS Lambda, and the "endpoint" will be via AWS API Gateway. The neat thing is that CodeStar provides all of this in a pre-configured package. We just have to make some config changes and push our code. -Once this tutorial is complete, you'll have a basic web service for sending an email that can be invoked via a link to your newly created API endpoint. +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. @@ -21,13 +21,13 @@ Before starting this tutorial, you will need to have access to an AWS account in ### Create AWS CodeStar Project Log in to your AWS account and go to the AWS CodeStar service. Click "Start a project". For this tutorial we're going to choose a Python Web service, utilizing AWS Lambda. You can use the filters on the left-hand side of the UI to narrow down the available choices. -After you've selected the template, you're asked to provide a name for your project. Go ahead and name it "hello-email". Once you've entered a name, click "Create Project" in the lower right-hand corner. You can then choose which tools you want to use to interact with the project. For this tutorial, we'll be choosing the "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, changelog, build a 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, changelog, build pipeline, and application endpoint. ### Create SendGrid API Key Log in to your SendGrid account. Click on your username on the left-hand side of the UI and choose "Setup Guide" from the drop-down menu. On the "Welcome" menu, choose "Send Your First Email", and then "Integrate using our Web API or SMTP relay." Choose "Web API" as the recommended option on the next screen, as we'll be using that for this tutorial. For more information about creating API keys, see https://sendgrid.com/docs/Classroom/Send/How_Emails_Are_Sent/api_keys.html @@ -44,7 +44,7 @@ For the rest of the tutorial, we'll be working out of the Git repository we clon ``` $ 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 idiosyncrasies 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 idiosyncrasies 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: ``` @@ -157,7 +157,7 @@ $ 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. @@ -167,6 +167,6 @@ Scroll down to the "Environment Variables" section. Here we need to populate our 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 the 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 c6b14dad2..aae2d3c04 100644 --- a/use_cases/django.md +++ b/use_cases/django.md @@ -1,4 +1,4 @@ -# Create a Django app to send an email with SendGrid +# Create a Django app to send email with SendGrid This tutorial explains how we set up a simple Django app to send an email with the SendGrid Python SDK and how we deploy our app to Heroku. @@ -119,7 +119,7 @@ $ python manage.py migrate $ python manage.py runserver ``` -By default, it starts the development server at `http://127.0.0.1:8000/`. To test if we can send an email or not, go to `http://127.0.0.1:8000/sendgrid/`. If it works, we should see the page says "Email Sent!". +By default, it starts the development server at `http://127.0.0.1:8000/`. To test if we can send email or not, go to `http://127.0.0.1:8000/sendgrid/`. If it works, we should see the page says "Email Sent!". **Note:** If you use `test@example.com` as your from email, it's likely to go to your spam folder. To have the emails show up in your inbox, try using an email address at the domain you registered your SendGrid account. @@ -198,4 +198,4 @@ $ git commit -am "Create simple Hello Email Django app using SendGrid" $ git push heroku master ``` -After that, let's verify if our app is working or not by accessing the root domain of your Heroku app. You should see the page says "Email Sent!" and on the Activity Feed page in the SendGrid dashboard, you should see a new feed with the email you set in the code. \ No newline at end of file +After that, let's verify if our app is working or not by accessing the root domain of your Heroku app. You should see the page says "Email Sent!" and on the Activity Feed page in the SendGrid dashboard, you should see a new feed with the email you set in the code. diff --git a/use_cases/flask_heroku.md b/use_cases/flask_heroku.md index fd55c77b5..ef0fa599a 100644 --- a/use_cases/flask_heroku.md +++ b/use_cases/flask_heroku.md @@ -1,4 +1,4 @@ -# Create a Flask app to send an email with SendGrid +# 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. From d2224e4819d5746b9fafcb5ad2701aabcbabafdb Mon Sep 17 00:00:00 2001 From: Vinayak <32881538+vinayak42@users.noreply.github.com> Date: Tue, 16 Oct 2018 22:39:07 +0530 Subject: [PATCH 142/462] Updated link to direct to #L9 @misterdorm The link to /mail/send Helper looked fine. Please provide details as to where it should direct to. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4132b62b5..e6901f233 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ pip install sendgrid ## 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_example.py) is a full example): +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_example.py#L9) is a full example): ### With Mail Helper Class From 9ffe918a842c2a3eb933a71df4553a86ab944549 Mon Sep 17 00:00:00 2001 From: Tulika Date: Tue, 16 Oct 2018 23:49:42 +0530 Subject: [PATCH 143/462] Create README.md --- examples/helpers/README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 examples/helpers/README.md diff --git a/examples/helpers/README.md b/examples/helpers/README.md new file mode 100644 index 000000000..917aebb16 --- /dev/null +++ b/examples/helpers/README.md @@ -0,0 +1,37 @@ +## 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) +``` From 46b7e4df5af08c333e897a4dd471bc7c08858d1a Mon Sep 17 00:00:00 2001 From: Chandler Weiner Date: Wed, 17 Oct 2018 12:52:51 -0400 Subject: [PATCH 144/462] Update verbiage from to "Authentication" --- use_cases/domain_authentication.md | 5 +++++ use_cases/domain_whitelabel.md | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 use_cases/domain_authentication.md delete mode 100644 use_cases/domain_whitelabel.md diff --git a/use_cases/domain_authentication.md b/use_cases/domain_authentication.md new file mode 100644 index 000000000..ea3ea008c --- /dev/null +++ b/use_cases/domain_authentication.md @@ -0,0 +1,5 @@ +# How to Setup a Domain Authentication + +You can find documentation for how to setup Sender 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 Authenticating related documentation [here](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/). 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 From 8ea9a44f4251a6e947a411725c6211f2883a8d61 Mon Sep 17 00:00:00 2001 From: Chandler Weiner Date: Wed, 17 Oct 2018 12:54:04 -0400 Subject: [PATCH 145/462] Change link to Domain Authentication --- use_cases/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/use_cases/README.md b/use_cases/README.md index 9966616e5..f3aa7e361 100644 --- a/use_cases/README.md +++ b/use_cases/README.md @@ -9,7 +9,7 @@ This directory provides examples for specific use cases of this library. Please * [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 Deploy a simple Flask app, to send Email with SendGrid, on Heroku](flask_heroku.md) -* [How to Setup a Domain Whitelabel](domain_whitelabel.md) +* [How to Setup a Domain Authentication](domain_authentication.md) * [How to View Email Statistics](email_stats.md) ### Working with Mail @@ -19,4 +19,4 @@ This directory provides examples for specific use cases of this library. Please * [Integrate with Slack Events API](slack_event_api_integration.md) ### Library Features -* [Error Handling](error_handling.md) \ No newline at end of file +* [Error Handling](error_handling.md) From 2793955714386fb0312dfdc51018487f95dfd8e1 Mon Sep 17 00:00:00 2001 From: Harsh Lathwal Date: Thu, 18 Oct 2018 01:16:12 +0530 Subject: [PATCH 146/462] removed similar data and kept fixes --- USAGE.md | 4711 +----------------------------------------------------- 1 file changed, 45 insertions(+), 4666 deletions(-) diff --git a/USAGE.md b/USAGE.md index ea2beae39..40f0472f8 100644 --- a/USAGE.md +++ b/USAGE.md @@ -70,4628 +70,7 @@ IP Access Management allows you to control which IP addresses can be used to acc For more information, please see our [User Guide](http://sendgrid.com/docs/User_Guide/Settings/ip_access_management.html). -This documentation is based on our OAI specification. - -INITIALIZATION - - -import sendgrid -import os - - -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) -Table of Contents - -ACCESS SETTINGS - -ALERTS - -API KEYS - -ASM - -BROWSERS - -CAMPAIGNS - -CATEGORIES - -CLIENTS - -CONTACTDB - -DEVICES - -GEO - -IPS - -MAIL - -MAIL SETTINGS - -MAILBOX PROVIDERS - -PARTNER SETTINGS - -SCOPES - -SENDERS - -STATS - -SUBUSERS - -SUPPRESSION - -TEMPLATES - -TRACKING SETTINGS - -USER - -WHITELABEL - - - -ACCESS SETTINGS - -Retrieve all recent access attempts - -This endpoint allows you to retrieve a list of all of the IP addresses that recently attempted to access your account either through the User Interface or the API. - -IP Access Management allows you to control which IP addresses can be used to access your account, either through the User Interface or the API. There is no limit to the number of IP addresses that you can add to your whitelist. It is possible to remove your own IP address from the whitelist, thus preventing yourself from accessing your account. - -For more information, please see our User Guide. - -GET /access_settings/activity - - -params = {'limit': 1} -response = sg.client.access_settings.activity.get(query_params=params) -print response.status_code -print response.body -print response.headers -Add one or more IPs to the whitelist - -This endpoint allows you to add one or more IP addresses to your IP whitelist. - -When adding an IP to your whitelist, include the IP address in an array. You can whitelist one IP at a time, or you can whitelist multiple IPs at once. - -IP Access Management allows you to control which IP addresses can be used to access your account, either through the User Interface or the API. There is no limit to the number of IP addresses that you can add to your whitelist. It is possible to remove your own IP address from the whitelist, thus preventing yourself from accessing your account. - -For more information, please see our User Guide. - -POST /access_settings/whitelist - - -data = { - "ips": [ -​ { -​ "ip": "192.168.1.1" -​ }, -​ { -​ "ip": "192.*.*.*" -​ }, -​ { -​ "ip": "192.168.1.3/32" -​ } - ] -} -response = sg.client.access_settings.whitelist.post(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve a list of currently whitelisted IPs - -This endpoint allows you to retrieve a list of IP addresses that are currently whitelisted. - -IP Access Management allows you to control which IP addresses can be used to access your account, either through the User Interface or the API. There is no limit to the number of IP addresses that you can add to your whitelist. It is possible to remove your own IP address from the whitelist, thus preventing yourself from accessing your account. - -For more information, please see our User Guide. - -GET /access_settings/whitelist - - -response = sg.client.access_settings.whitelist.get() -print response.status_code -print response.body -print response.headers -Remove one or more IPs from the whitelist - -This endpoint allows you to remove one or more IPs from your IP whitelist. - -You can remove one IP at a time, or you can remove multiple IP addresses. - -IP Access Management allows you to control which IP addresses can be used to access your account, either through the User Interface or the API. There is no limit to the number of IP addresses that you can add to your whitelist. It is possible to remove your own IP address from the whitelist, thus preventing yourself from accessing your account. - -For more information, please see our User Guide. - -DELETE /access_settings/whitelist - - -data = { - "ids": [ -​ 1, -​ 2, -​ 3 - ] -} -response = sg.client.access_settings.whitelist.delete(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve a specific whitelisted IP - -This endpoint allows you to retrieve a specific IP address that has been whitelisted. - -You must include the ID for the specific IP address you want to retrieve in your call. - -IP Access Management allows you to control which IP addresses can be used to access your account, either through the User Interface or the API. There is no limit to the number of IP addresses that you can add to your whitelist. It is possible to remove your own IP address from the whitelist, thus preventing yourself from accessing your account. - -For more information, please see our User Guide. - -GET /access_settings/whitelist/{rule_id} - - -rule_id = "test_url_param" -response = sg.client.access_settings.whitelist._(rule_id).get() -print response.status_code -print response.body -print response.headers -Remove a specific IP from the whitelist - -This endpoint allows you to remove a specific IP address from your IP whitelist. - -When removing a specific IP address from your whitelist, you must include the ID in your call. - -IP Access Management allows you to control which IP addresses can be used to access your account, either through the User Interface or the API. There is no limit to the number of IP addresses that you can add to your whitelist. It is possible to remove your own IP address from the whitelist, thus preventing yourself from accessing your account. - -For more information, please see our User Guide. - -DELETE /access_settings/whitelist/{rule_id} - - -rule_id = "test_url_param" -response = sg.client.access_settings.whitelist._(rule_id).delete() -print response.status_code -print response.body -print response.headers - - -ALERTS - -Create a new Alert - -This endpoint allows you to create a new alert. - -Alerts allow you to specify an email address to receive notifications regarding your email usage or statistics. - -Usage alerts allow you to set the threshold at which an alert will be sent. - -Stats notifications allow you to set how frequently you would like to receive email statistics reports. For example, "daily", "weekly", or "monthly". - -For more information about alerts, please see our User Guide. - -POST /alerts - - -data = { - "email_to": "example@example.com", - "frequency": "daily", - "type": "stats_notification" -} -response = sg.client.alerts.post(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve all alerts - -GET /alerts - - -response = sg.client.alerts.get() -print response.status_code -print response.body -print response.headers -Update an alert - -This endpoint allows you to update an alert. - -Alerts allow you to specify an email address to receive notifications regarding your email usage or statistics. - -Usage alerts allow you to set the threshold at which an alert will be sent. - -Stats notifications allow you to set how frequently you would like to receive email statistics reports. For example, "daily", "weekly", or "monthly". - -For more information about alerts, please see our User Guide. - -PATCH /alerts/{alert_id} - - -data = { - "email_to": "example@example.com" -} -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 -Retrieve a specific alert - -This endpoint allows you to retrieve a specific alert. - -Alerts allow you to specify an email address to receive notifications regarding your email usage or statistics. - -Usage alerts allow you to set the threshold at which an alert will be sent. - -Stats notifications allow you to set how frequently you would like to receive email statistics reports. For example, "daily", "weekly", or "monthly". - -For more information about alerts, please see our User Guide. - -GET /alerts/{alert_id} - - -alert_id = "test_url_param" -response = sg.client.alerts._(alert_id).get() -print response.status_code -print response.body -print response.headers -Delete an alert - -This endpoint allows you to delete an alert. - -Alerts allow you to specify an email address to receive notifications regarding your email usage or statistics. - -Usage alerts allow you to set the threshold at which an alert will be sent. - -Stats notifications allow you to set how frequently you would like to receive email statistics reports. For example, "daily", "weekly", or "monthly". - -For more information about alerts, please see our User Guide. - -DELETE /alerts/{alert_id} - - -alert_id = "test_url_param" -response = sg.client.alerts._(alert_id).delete() -print response.status_code -print response.body -print response.headers - - -API KEYS - -Create API keys - -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. - -There is a limit of 100 API Keys on your account. - -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. - -See the API Key Permissions List for a list of all available scopes. - -POST /api_keys - - -data = { - "name": "My API Key", - "sample": "data", - "scopes": [ -​ "mail.send", -​ "alerts.create", -​ "alerts.read" - ] -} -response = sg.client.api_keys.post(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve all API Keys belonging to the authenticated user - -This endpoint allows you to retrieve all API Keys that belong to the authenticated user. - -The API Keys feature allows customers to generate an API Key credential which can be used for authentication with the SendGrid v3 Web API or the Mail API Endpoint. - -GET /api_keys - - -params = {'limit': 1} -response = sg.client.api_keys.get(query_params=params) -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. - -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. - -PUT /api_keys/{api_key_id} - - -data = { - "name": "A New Hope", - "scopes": [ -​ "user.profile.read", -​ "user.profile.update" - ] -} -api_key_id = "test_url_param" -response = sg.client.api_keys._(api_key_id).put(request_body=data) -print response.status_code -print response.body -print response.headers -Update API keys - -This endpoint allows you to update the name of an existing API Key. - -A JSON request body with a "name" property is required. - -The API Keys feature allows customers to be able to generate an API Key credential which can be used for authentication with the SendGrid v3 Web API or the Mail API Endpoint. - -URI Parameters - -URI PARAMETER TYPE REQUIRED? DESCRIPTION -api_key_id string required The ID of the API Key you are updating. -PATCH /api_keys/{api_key_id} - - -data = { - "name": "A New Hope" -} -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 -Retrieve an existing API Key - -This endpoint allows you to retrieve a single API key. - -If the API Key ID does not exist an HTTP 404 will be returned. - -GET /api_keys/{api_key_id} - - -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 -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. - -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. - -URI Parameters - -URI PARAMETER TYPE REQUIRED? DESCRIPTION -api_key_id string required The ID of the API Key you are deleting. -DELETE /api_keys/{api_key_id} - - -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 - - -ASM - -Create a new suppression group - -This endpoint allows you to create a new suppression group. - -Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. - -The name and description of the unsubscribe group will be visible by recipients when they are managing their subscriptions. - -Each user can create up to 25 different suppression groups. - -POST /asm/groups - - -data = { - "description": "Suggestions for products our users might like.", - "is_default": True, - "name": "Product Suggestions" -} -response = sg.client.asm.groups.post(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve information about multiple suppression groups - -This endpoint allows you to retrieve information about multiple suppression groups. - -This endpoint will return information for each group ID that you include in your request. To add a group ID to your request, simply append &id= followed by the group ID. - -Suppressions are a list of email addresses that will not receive content sent under a given group. - -Suppression groups, or unsubscribe groups, allow you to label a category of content that you regularly send. This gives your recipients the ability to opt out of a specific set of your email. For example, you might define a group for your transactional email, and one for your marketing email so that your users can continue receiving your transactional email without having to receive your marketing content. - -GET /asm/groups - - -params = {'id': 1} -response = sg.client.asm.groups.get(query_params=params) -print response.status_code -print response.body -print response.headers -Update a suppression group. - -This endpoint allows you to update or change a suppression group. - -Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. - -The name and description of the unsubscribe group will be visible by recipients when they are managing their subscriptions. - -Each user can create up to 25 different suppression groups. - -PATCH /asm/groups/{group_id} - - -data = { - "description": "Suggestions for items our users might like.", - "id": 103, - "name": "Item Suggestions" -} -group_id = "test_url_param" -response = sg.client.asm.groups._(group_id).patch(request_body=data) -print response.status_code -print response.body -print response.headers -Get information on a single suppression group. - -This endpoint allows you to retrieve a single suppression group. - -Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. - -The name and description of the unsubscribe group will be visible by recipients when they are managing their subscriptions. - -Each user can create up to 25 different suppression groups. - -GET /asm/groups/{group_id} - - -group_id = "test_url_param" -response = sg.client.asm.groups._(group_id).get() -print response.status_code -print response.body -print response.headers -Delete a suppression group. - -This endpoint allows you to delete a suppression group. - -You can only delete groups that have not been attached to sent mail in the last 60 days. If a recipient uses the "one-click unsubscribe" option on an email associated with a deleted group, that recipient will be added to the global suppression list. - -Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. - -The name and description of the unsubscribe group will be visible by recipients when they are managing their subscriptions. - -Each user can create up to 25 different suppression groups. - -DELETE /asm/groups/{group_id} - - -group_id = "test_url_param" -response = sg.client.asm.groups._(group_id).delete() -print response.status_code -print response.body -print response.headers -Add suppressions to a suppression group - -This endpoint allows you to add email addresses to an unsubscribe group. - -If you attempt to add suppressions to a group that has been deleted or does not exist, the suppressions will be added to the global suppressions list. - -Suppressions are recipient email addresses that are added to unsubscribe groups. Once a recipient's address is on the suppressions list for an unsubscribe group, they will not receive any emails that are tagged with that unsubscribe group. - -POST /asm/groups/{group_id}/suppressions - - -data = { - "recipient_emails": [ -​ "test1@example.com", -​ "test2@example.com" - ] -} -group_id = "test_url_param" -response = sg.client.asm.groups._(group_id).suppressions.post(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve all suppressions for a suppression group - -This endpoint allows you to retrieve all suppressed email addresses belonging to the given group. - -Suppressions are recipient email addresses that are added to unsubscribe groups. Once a recipient's address is on the suppressions list for an unsubscribe group, they will not receive any emails that are tagged with that unsubscribe group. - -GET /asm/groups/{group_id}/suppressions - - -group_id = "test_url_param" -response = sg.client.asm.groups._(group_id).suppressions.get() -print response.status_code -print response.body -print response.headers -Search for suppressions within a group - -This endpoint allows you to search a suppression group for multiple suppressions. - -When given a list of email addresses and a group ID, this endpoint will return only the email addresses that have been unsubscribed from the given group. - -Suppressions are a list of email addresses that will not receive content sent under a given group. - -POST /asm/groups/{group_id}/suppressions/search - - -data = { - "recipient_emails": [ -​ "exists1@example.com", -​ "exists2@example.com", -​ "doesnotexists@example.com" - ] -} -group_id = "test_url_param" -response = sg.client.asm.groups._(group_id).suppressions.search.post(request_body=data) -print response.status_code -print response.body -print response.headers -Delete a suppression from a suppression group - -This endpoint allows you to remove a suppressed email address from the given suppression group. - -Suppressions are recipient email addresses that are added to unsubscribe groups. Once a recipient's address is on the suppressions list for an unsubscribe group, they will not receive any emails that are tagged with that unsubscribe group. - -DELETE /asm/groups/{group_id}/suppressions/{email} - - -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 -Retrieve all suppressions - -This endpoint allows you to retrieve a list of all suppressions. - -Suppressions are a list of email addresses that will not receive content sent under a given group. - -GET /asm/suppressions - - -response = sg.client.asm.suppressions.get() -print response.status_code -print response.body -print response.headers -Add recipient addresses to the global suppression group. - -This endpoint allows you to add one or more email addresses to the global suppressions group. - -A global suppression (or global unsubscribe) is an email address of a recipient who does not want to receive any of your messages. A globally suppressed recipient will be removed from any email you send. For more information, please see our User Guide. - -POST /asm/suppressions/global - - -data = { - "recipient_emails": [ -​ "test1@example.com", -​ "test2@example.com" - ] -} -response = sg.client.asm.suppressions._("global").post(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve a Global Suppression - -This endpoint allows you to retrieve a global suppression. You can also use this endpoint to confirm if an email address is already globally suppressed. - -If the email address you include in the URL path parameter {email} is already globally suppressed, the response will include that email address. If the address you enter for {email} is not globally suppressed, an empty JSON object {} will be returned. - -A global suppression (or global unsubscribe) is an email address of a recipient who does not want to receive any of your messages. A globally suppressed recipient will be removed from any email you send. For more information, please see our User Guide. - -GET /asm/suppressions/global/{email} - - -email = "test_url_param" -response = sg.client.asm.suppressions._("global")._(email).get() -print response.status_code -print response.body -print response.headers -Delete a Global Suppression - -This endpoint allows you to remove an email address from the global suppressions group. - -A global suppression (or global unsubscribe) is an email address of a recipient who does not want to receive any of your messages. A globally suppressed recipient will be removed from any email you send. For more information, please see our User Guide. - -DELETE /asm/suppressions/global/{email} - - -email = "test_url_param" -response = sg.client.asm.suppressions._("global")._(email).delete() -print response.status_code -print response.body -print response.headers -Retrieve all suppression groups for an email address - -This endpoint returns the list of all groups that the given email address has been unsubscribed from. - -Suppressions are a list of email addresses that will not receive content sent under a given group. - -GET /asm/suppressions/{email} - - -email = "test_url_param" -response = sg.client.asm.suppressions._(email).get() -print response.status_code -print response.body -print response.headers - - -BROWSERS - -Retrieve email statistics by browser. - -This endpoint allows you to retrieve your email statistics segmented by browser type. - -We only store up to 7 days of email activity in our database. By default, 500 items will be returned per request via the Advanced Stats API endpoints. - -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. - -GET /browsers/stats - - -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'browsers': 'test_string', 'limit': 'test_string', 'offset': 'test_string', 'start_date': '2016-01-01'} -response = sg.client.browsers.stats.get(query_params=params) -print response.status_code -print response.body -print response.headers - - -CAMPAIGNS - -Create a Campaign - -This endpoint allows you to create a campaign. - -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. - -For more information: - -User Guide > Marketing Campaigns - -POST /campaigns - - -data = { - "categories": [ -​ "spring line" - ], - "custom_unsubscribe_url": "", - "html_content": "

Check out our spring line!

", - "ip_pool": "marketing", - "list_ids": [ -​ 110, -​ 124 - ], - "plain_content": "Check out our spring line!", - "segment_ids": [ -​ 110 - ], - "sender_id": 124451, - "subject": "New Products for Spring!", - "suppression_group_id": 42, - "title": "March Newsletter" -} -response = sg.client.campaigns.post(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve all Campaigns - -This endpoint allows you to retrieve a list of all of your campaigns. - -Returns campaigns in reverse order they were created (newest first). - -Returns an empty array if no campaigns exist. - -For more information: - -User Guide > Marketing Campaigns - -GET /campaigns - - -params = {'limit': 10, 'offset': 0} -response = sg.client.campaigns.get(query_params=params) -print response.status_code -print response.body -print response.headers -Update a Campaign - -Update a campaign. This is especially useful if you only set up the campaign using POST /campaigns, but didn't set many of the parameters. - -For more information: - -User Guide > Marketing Campaigns - -PATCH /campaigns/{campaign_id} - - -data = { - "categories": [ -​ "summer line" - ], - "html_content": "

Check out our summer line!

", - "plain_content": "Check out our summer line!", - "subject": "New Products for Summer!", - "title": "May Newsletter" -} -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 -Retrieve a single campaign - -This endpoint allows you to retrieve a specific campaign. - -Our Marketing Campaigns API lets you create, manage, send, and schedule campaigns. - -For more information: - -User Guide > Marketing Campaigns - -GET /campaigns/{campaign_id} - - -campaign_id = "test_url_param" -response = sg.client.campaigns._(campaign_id).get() -print response.status_code -print response.body -print response.headers -Delete a Campaign - -This endpoint allows you to delete a specific campaign. - -Our Marketing Campaigns API lets you create, manage, send, and schedule campaigns. - -For more information: - -User Guide > Marketing Campaigns - -DELETE /campaigns/{campaign_id} - - -campaign_id = "test_url_param" -response = sg.client.campaigns._(campaign_id).delete() -print response.status_code -print response.body -print response.headers -Update a Scheduled Campaign - -This endpoint allows to you change the scheduled time and date for a campaign to be sent. - -For more information: - -User Guide > Marketing Campaigns - -PATCH /campaigns/{campaign_id}/schedules - - -data = { - "send_at": 1489451436 -} -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 -Schedule a Campaign - -This endpoint allows you to schedule a specific date and time for your campaign to be sent. - -For more information: - -User Guide > Marketing Campaigns - -POST /campaigns/{campaign_id}/schedules - - -data = { - "send_at": 1489771528 -} -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 -View Scheduled Time of a Campaign - -This endpoint allows you to retrieve the date and time that the given campaign has been scheduled to be sent. - -For more information: - -User Guide > Marketing Campaigns - -GET /campaigns/{campaign_id}/schedules - - -campaign_id = "test_url_param" -response = sg.client.campaigns._(campaign_id).schedules.get() -print response.status_code -print response.body -print response.headers -Unschedule a Scheduled Campaign - -This endpoint allows you to unschedule a campaign that has already been scheduled to be sent. - -A successful unschedule will return a 204. -If the specified campaign is in the process of being sent, the only option is to cancel (a different method). - -For more information: - -User Guide > Marketing Campaigns - -DELETE /campaigns/{campaign_id}/schedules - - -campaign_id = "test_url_param" -response = sg.client.campaigns._(campaign_id).schedules.delete() -print response.status_code -print response.body -print response.headers -Send a Campaign - -This endpoint allows you to immediately send a campaign at the time you make the API call. - -Normally a POST would have a request body, but since this endpoint is telling us to send a resource that is already created, a request body is not needed. - -For more information: - -User Guide > Marketing Campaigns - -POST /campaigns/{campaign_id}/schedules/now - - -campaign_id = "test_url_param" -response = sg.client.campaigns._(campaign_id).schedules.now.post() -print response.status_code -print response.body -print response.headers -Send a Test Campaign - -This endpoint allows you to send a test campaign. - -To send to multiple addresses, use an array for the JSON "to" value ["one@address","two@address"] - -For more information: - -User Guide > Marketing Campaigns - -POST /campaigns/{campaign_id}/schedules/test - - -data = { - "to": "your.email@example.com" -} -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 - - -CATEGORIES - -Retrieve all categories - -This endpoint allows you to retrieve a list of all of your categories. - -Categories can help organize your email analytics by enabling you to tag emails by type or broad topic. You can define your own custom categories. For more information, please see our User Guide. - -GET /categories - - -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 -Retrieve Email Statistics for Categories - -This endpoint allows you to retrieve all of your email statistics for each of your categories. - -If you do not define any query parameters, this endpoint will return a sum for each category in groups of 10. - -Categories allow you to group your emails together according to broad topics that you define. For more information, please see our User Guide. - -GET /categories/stats - - -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 -Retrieve sums of email stats for each category [Needs: Stats object defined, has category ID?] - -This endpoint allows you to retrieve the total sum of each email statistic for every category over the given date range. - -If you do not define any query parameters, this endpoint will return a sum for each category in groups of 10. - -Categories allow you to group your emails together according to broad topics that you define. For more information, please see our User Guide. - -GET /categories/stats/sums - - -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} -response = sg.client.categories.stats.sums.get(query_params=params) -print response.status_code -print response.body -print response.headers - - -CLIENTS - -Retrieve email statistics by client type. - -This endpoint allows you to retrieve your email statistics segmented by client type. - -We only store up to 7 days of email activity in our database. By default, 500 items will be returned per request via the Advanced Stats API endpoints. - -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. - -GET /clients/stats - - -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 -Retrieve stats by a specific client type. - -This endpoint allows you to retrieve your email statistics segmented by a specific client type. - -We only store up to 7 days of email activity in our database. By default, 500 items will be returned per request via the Advanced Stats API endpoints. - -Available Client Types - -phone - -tablet - -webmail - -desktop - -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. - -GET /clients/{client_type}/stats - - -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 - - -CONTACTDB - -Create a Custom Field - -This endpoint allows you to create a custom field. - -The contactdb is a database of your contacts for SendGrid Marketing Campaigns. - -POST /contactdb/custom_fields - - -data = { - "name": "pet", - "type": "text" -} -response = sg.client.contactdb.custom_fields.post(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve all custom fields - -This endpoint allows you to retrieve all custom fields. - -The contactdb is a database of your contacts for SendGrid Marketing Campaigns. - -GET /contactdb/custom_fields - - -response = sg.client.contactdb.custom_fields.get() -print response.status_code -print response.body -print response.headers -Retrieve a Custom Field - -This endpoint allows you to retrieve a custom field by ID. - -The contactdb is a database of your contacts for SendGrid Marketing Campaigns. - -GET /contactdb/custom_fields/{custom_field_id} - - -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 -Delete a Custom Field - -This endpoint allows you to delete a custom field by ID. - -The contactdb is a database of your contacts for SendGrid Marketing Campaigns. - -DELETE /contactdb/custom_fields/{custom_field_id} - - -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 -Create a List - -This endpoint allows you to create a list for your recipients. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -POST /contactdb/lists - - -data = { - "name": "your list name" -} -response = sg.client.contactdb.lists.post(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve all lists - -This endpoint allows you to retrieve all of your recipient lists. If you don't have any lists, an empty array will be returned. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -GET /contactdb/lists - - -response = sg.client.contactdb.lists.get() -print response.status_code -print response.body -print response.headers -Delete Multiple lists - -This endpoint allows you to delete multiple recipient lists. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -DELETE /contactdb/lists - - -data = [ - 1, - 2, - 3, - 4 -] -response = sg.client.contactdb.lists.delete(request_body=data) -print response.status_code -print response.body -print response.headers -Update a List - -This endpoint allows you to update the name of one of your recipient lists. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -PATCH /contactdb/lists/{list_id} - - -data = { - "name": "newlistname" -} -params = {'list_id': 1} -list_id = "test_url_param" -response = sg.client.contactdb.lists._(list_id).patch(request_body=data, query_params=params) -print response.status_code -print response.body -print response.headers -Retrieve a single list - -This endpoint allows you to retrieve a single recipient list. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -GET /contactdb/lists/{list_id} - - -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 -Delete a List - -This endpoint allows you to delete a specific recipient list with the given ID. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -DELETE /contactdb/lists/{list_id} - - -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 -Add Multiple Recipients to a List - -This endpoint allows you to add multiple recipients to a list. - -Adds existing recipients to a list, passing in the recipient IDs to add. Recipient IDs should be passed exactly as they are returned from recipient endpoints. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -POST /contactdb/lists/{list_id}/recipients - - -data = [ - "recipient_id1", - "recipient_id2" -] -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 -Retrieve all recipients on a List - -This endpoint allows you to retrieve all recipients on the list with the given ID. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -GET /contactdb/lists/{list_id}/recipients - - -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 -Add a Single Recipient to a List - -This endpoint allows you to add a single recipient to a list. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -POST /contactdb/lists/{list_id}/recipients/{recipient_id} - - -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 -Delete a Single Recipient from a Single List - -This endpoint allows you to delete a single recipient from a list. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -DELETE /contactdb/lists/{list_id}/recipients/{recipient_id} - - -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 -Update Recipient - -This endpoint allows you to update one or more recipients. - -The body of an API call to this endpoint must include an array of one or more recipient objects. - -It is of note that you can add custom field data as parameters on recipient objects. We have provided an example using some of the default custom fields SendGrid provides. - -The contactdb is a database of your contacts for SendGrid Marketing Campaigns. - -PATCH /contactdb/recipients - - -data = [ - { -​ "email": "jones@example.com", -​ "first_name": "Guy", -​ "last_name": "Jones" - } -] -response = sg.client.contactdb.recipients.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Add recipients - -This endpoint allows you to add a Marketing Campaigns recipient. - -It is of note that you can add custom field data as a parameter on this endpoint. We have provided an example using some of the default custom fields SendGrid provides. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -POST /contactdb/recipients - - -data = [ - { -​ "age": 25, -​ "email": "example@example.com", -​ "first_name": "", -​ "last_name": "User" - }, - { -​ "age": 25, -​ "email": "example2@example.com", -​ "first_name": "Example", -​ "last_name": "User" - } -] -response = sg.client.contactdb.recipients.post(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve recipients - -This endpoint allows you to retrieve all of your Marketing Campaigns recipients. - -Batch deletion of a page makes it possible to receive an empty page of recipients before reaching the end of -the list of recipients. To avoid this issue; iterate over pages until a 404 is retrieved. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -GET /contactdb/recipients - - -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 -Delete Recipient - -This endpoint allows you to deletes one or more recipients. - -The body of an API call to this endpoint must include an array of recipient IDs of the recipients you want to delete. - -The contactdb is a database of your contacts for SendGrid Marketing Campaigns. - -DELETE /contactdb/recipients - - -data = [ - "recipient_id1", - "recipient_id2" -] -response = sg.client.contactdb.recipients.delete(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve the count of billable recipients - -This endpoint allows you to retrieve the number of Marketing Campaigns recipients that you will be billed for. - -You are billed for marketing campaigns based on the highest number of recipients you have had in your account at one time. This endpoint will allow you to know the current billable count value. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -GET /contactdb/recipients/billable_count - - -response = sg.client.contactdb.recipients.billable_count.get() -print response.status_code -print response.body -print response.headers -Retrieve a Count of Recipients - -This endpoint allows you to retrieve the total number of Marketing Campaigns recipients. - -The contactdb is a database of your contacts for SendGrid Marketing Campaigns. - -GET /contactdb/recipients/count - - -response = sg.client.contactdb.recipients.count.get() -print response.status_code -print response.body -print response.headers -Retrieve recipients matching search criteria - -This endpoint allows you to perform a search on all of your Marketing Campaigns recipients. - -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) - -If field_name is a 'reserved' date field, such as created_at or updated_at, the system will internally convert -your epoch time to a date range encompassing the entire day. For example, an epoch time of 1422835600 converts to -Mon, 02 Feb 2015 00:06:40 GMT, but internally the system will search from Mon, 02 Feb 2015 00:00:00 GMT through -Mon, 02 Feb 2015 23:59:59 GMT. - -The contactdb is a database of your contacts for SendGrid Marketing Campaigns. - -GET /contactdb/recipients/search - - -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 -Retrieve a single recipient - -This endpoint allows you to retrieve a single recipient by ID from your contact database. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -GET /contactdb/recipients/{recipient_id} - - -recipient_id = "test_url_param" -response = sg.client.contactdb.recipients._(recipient_id).get() -print response.status_code -print response.body -print response.headers -Delete a Recipient - -This endpoint allows you to delete a single recipient with the given ID from your contact database. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -DELETE /contactdb/recipients/{recipient_id} - - -recipient_id = "test_url_param" -response = sg.client.contactdb.recipients._(recipient_id).delete() -print response.status_code -print response.body -print response.headers -Retrieve the lists that a recipient is on - -This endpoint allows you to retrieve the lists that a given recipient belongs to. - -Each recipient can be on many lists. This endpoint gives you all of the lists that any one recipient has been added to. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -GET /contactdb/recipients/{recipient_id}/lists - - -recipient_id = "test_url_param" -response = sg.client.contactdb.recipients._(recipient_id).lists.get() -print response.status_code -print response.body -print response.headers -Retrieve reserved fields - -This endpoint allows you to list all fields that are reserved and can't be used for custom field names. - -The contactdb is a database of your contacts for SendGrid Marketing Campaigns. - -GET /contactdb/reserved_fields - - -response = sg.client.contactdb.reserved_fields.get() -print response.status_code -print response.body -print response.headers -Create a Segment - -This endpoint allows you to create a segment. - -All recipients in your contactdb will be added or removed automatically depending on whether they match the criteria for this segment. - -List Id: - -Send this to segment from an existing list - -Don't send this in order to segment from your entire contactdb. - -Valid operators for create and update depend on the type of the field you are segmenting: - -Dates: "eq", "ne", "lt" (before), "gt" (after) - -Text: "contains", "eq" (is - matches the full field), "ne" (is not - matches any field where the entire field is not the condition value) - -Numbers: "eq", "lt", "gt" - -Email Clicks and Opens: "eq" (opened), "ne" (not opened) - -Segment conditions using "eq" or "ne" for email clicks and opens should provide a "field" of either clicks.campaign_identifier or opens.campaign_identifier. The condition value should be a string containing the id of a completed campaign. - -Segments may contain multiple conditions, joined by an "and" or "or" in the "and_or" field. The first condition in the conditions list must have an empty "and_or", and subsequent conditions must all specify an "and_or". - -The Contacts API helps you manage your Marketing Campaigns recipients. - -For more information about segments in Marketing Campaigns, please see our User Guide. - -POST /contactdb/segments - - -data = { - "conditions": [ -​ { -​ "and_or": "", -​ "field": "last_name", -​ "operator": "eq", -​ "value": "Miller" -​ }, -​ { -​ "and_or": "and", -​ "field": "last_clicked", -​ "operator": "gt", -​ "value": "01/02/2015" -​ }, -​ { -​ "and_or": "or", -​ "field": "clicks.campaign_identifier", -​ "operator": "eq", -​ "value": "513" -​ } - ], - "list_id": 4, - "name": "Last Name Miller" -} -response = sg.client.contactdb.segments.post(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve all segments - -This endpoint allows you to retrieve all of your segments. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -For more information about segments in Marketing Campaigns, please see our User Guide. - -GET /contactdb/segments - - -response = sg.client.contactdb.segments.get() -print response.status_code -print response.body -print response.headers -Update a segment - -This endpoint allows you to update a segment. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -For more information about segments in Marketing Campaigns, please see our User Guide. - -PATCH /contactdb/segments/{segment_id} - - -data = { - "conditions": [ -​ { -​ "and_or": "", -​ "field": "last_name", -​ "operator": "eq", -​ "value": "Miller" -​ } - ], - "list_id": 5, - "name": "The Millers" -} -params = {'segment_id': 'test_string'} -segment_id = "test_url_param" -response = sg.client.contactdb.segments._(segment_id).patch(request_body=data, query_params=params) -print response.status_code -print response.body -print response.headers -Retrieve a segment - -This endpoint allows you to retrieve a single segment with the given ID. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -For more information about segments in Marketing Campaigns, please see our User Guide. - -GET /contactdb/segments/{segment_id} - - -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 -Delete a segment - -This endpoint allows you to delete a segment from your recipients database. - -You also have the option to delete all the contacts from your Marketing Campaigns recipient database who were in this segment. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -For more information about segments in Marketing Campaigns, please see our User Guide. - -DELETE /contactdb/segments/{segment_id} - - -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 -Retrieve recipients on a segment - -This endpoint allows you to retrieve all of the recipients in a segment with the given ID. - -The Contacts API helps you manage your Marketing Campaigns recipients. - -For more information about segments in Marketing Campaigns, please see our User Guide. - -GET /contactdb/segments/{segment_id}/recipients - - -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 - - -DEVICES - -Retrieve email statistics by device type. - -This endpoint allows you to retrieve your email statistics segmented by the device type. - -We only store up to 7 days of email activity in our database. By default, 500 items will be returned per request via the Advanced Stats API endpoints. - -Available Device Types - -DEVICE DESCRIPTION EXAMPLE -Desktop Email software on 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. | -| 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. - -GET /devices/stats - - -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 - - -GEO - -Retrieve email statistics by country and state/province. - -This endpoint allows you to retrieve your email statistics segmented by country and state/province. - -We only store up to 7 days of email activity in our database. By default, 500 items will be returned per request via the Advanced Stats API endpoints. - -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. - -GET /geo/stats - - -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 - - -IPS - -Retrieve all IP addresses - -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. - -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. - -GET /ips - - -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 -Retrieve all assigned IPs - -This endpoint allows you to retrieve only assigned IP addresses. - -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. - -GET /ips/assigned - - -response = sg.client.ips.assigned.get() -print response.status_code -print response.body -print response.headers -Create an IP pool. - -This endpoint allows you to create an IP pool. - -Each user can create up to 10 different IP pools. - -IP Pools allow you to group your dedicated SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. - -IP pools can only be used with whitelabeled IP addresses. - -If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. - -POST /ips/pools - - -data = { - "name": "marketing" -} -response = sg.client.ips.pools.post(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve all IP pools. - -This endpoint allows you to retrieve all of your IP pools. - -IP Pools allow you to group your dedicated SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. - -IP pools can only be used with whitelabeled IP addresses. - -If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. - -GET /ips/pools - - -response = sg.client.ips.pools.get() -print response.status_code -print response.body -print response.headers -Update an IP pools name. - -This endpoint allows you to update the name of an IP pool. - -IP Pools allow you to group your dedicated SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. - -IP pools can only be used with whitelabeled IP addresses. - -If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. - -PUT /ips/pools/{pool_name} - - -data = { - "name": "new_pool_name" -} -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 -Retrieve all IPs in a specified pool. - -This endpoint allows you to list all of the IP addresses that are in a specific IP pool. - -IP Pools allow you to group your dedicated SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. - -IP pools can only be used with whitelabeled IP addresses. - -If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. - -GET /ips/pools/{pool_name} - - -pool_name = "test_url_param" -response = sg.client.ips.pools._(pool_name).get() -print response.status_code -print response.body -print response.headers -Delete an IP pool. - -This endpoint allows you to delete an IP pool. - -IP Pools allow you to group your dedicated SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. - -IP pools can only be used with whitelabeled IP addresses. - -If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. - -DELETE /ips/pools/{pool_name} - - -pool_name = "test_url_param" -response = sg.client.ips.pools._(pool_name).delete() -print response.status_code -print response.body -print response.headers -Add an IP address to a pool - -This endpoint allows you to add an IP address to an IP pool. - -You can add the same IP address to multiple pools. It may take up to 60 seconds for your IP address to be added to a pool after your request is made. - -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. - -POST /ips/pools/{pool_name}/ips - - -data = { - "ip": "0.0.0.0" -} -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 -Remove an IP address from a pool. - -This endpoint allows you to remove an IP address from an IP pool. - -The same IP address can be added to multiple IP pools. - -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. - -DELETE /ips/pools/{pool_name}/ips/{ip} - - -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 -Add an IP to warmup - -This endpoint allows you to enter an IP address into warmup mode. - -SendGrid can automatically warm up dedicated IP addresses by limiting the amount of mail that can be sent through them per hour, with the limit determined by how long the IP address has been in warmup. See the warmup schedule for more details on how SendGrid limits your email traffic for IPs in warmup. - -For more general information about warming up IPs, please see our Classroom. - -POST /ips/warmup - - -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 -Retrieve all IPs currently in warmup - -This endpoint allows you to retrieve all of your IP addresses that are currently warming up. - -SendGrid can automatically warm up dedicated IP addresses by limiting the amount of mail that can be sent through them per hour, with the limit determined by how long the IP address has been in warmup. See the warmup schedule for more details on how SendGrid limits your email traffic for IPs in warmup. - -For more general information about warming up IPs, please see our Classroom. - -GET /ips/warmup - - -response = sg.client.ips.warmup.get() -print response.status_code -print response.body -print response.headers -Retrieve warmup status for a specific IP address - -This endpoint allows you to retrieve the warmup status for a specific IP address. - -SendGrid can automatically warm up dedicated IP addresses by limiting the amount of mail that can be sent through them per hour, with the limit determined by how long the IP address has been in warmup. See the warmup schedule for more details on how SendGrid limits your email traffic for IPs in warmup. - -For more general information about warming up IPs, please see our Classroom. - -GET /ips/warmup/{ip_address} - - -ip_address = "test_url_param" -response = sg.client.ips.warmup._(ip_address).get() -print response.status_code -print response.body -print response.headers -Remove an IP from warmup - -This endpoint allows you to remove an IP address from warmup mode. - -SendGrid can automatically warm up dedicated IP addresses by limiting the amount of mail that can be sent through them per hour, with the limit determined by how long the IP address has been in warmup. See the warmup schedule for more details on how SendGrid limits your email traffic for IPs in warmup. - -For more general information about warming up IPs, please see our Classroom. - -DELETE /ips/warmup/{ip_address} - - -ip_address = "test_url_param" -response = sg.client.ips.warmup._(ip_address).delete() -print response.status_code -print response.body -print response.headers -Retrieve all IP pools an IP address belongs to - -This endpoint allows you to see which IP pools a particular IP address has been added to. - -The same IP address can be added to multiple IP pools. - -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. - -GET /ips/{ip_address} - - -ip_address = "test_url_param" -response = sg.client.ips._(ip_address).get() -print response.status_code -print response.body -print response.headers - - -MAIL - -Create a batch ID - -This endpoint allows you to generate a new batch ID. This batch ID can be associated with scheduled sends via the mail/send endpoint. - -If you set the SMTPAPI header batch_id, it allows you to then associate multiple scheduled mail/send requests together with the same ID. Then at anytime up to 10 minutes before the schedule date, you can cancel all of the mail/send requests that have this batch ID by calling the Cancel Scheduled Send endpoint. - -More Information: - -Scheduling Parameters > Batch ID - -POST /mail/batch - - -response = sg.client.mail.batch.post() -print response.status_code -print response.body -print response.headers -Validate batch ID - -This endpoint allows you to validate a batch ID. - -If you set the SMTPAPI header batch_id, it allows you to then associate multiple scheduled mail/send requests together with the same ID. Then at anytime up to 10 minutes before the schedule date, you can cancel all of the mail/send requests that have this batch ID by calling the Cancel Scheduled Send endpoint. - -More Information: - -Scheduling Parameters > Batch ID - -GET /mail/batch/{batch_id} - - -batch_id = "test_url_param" -response = sg.client.mail.batch._(batch_id).get() -print response.status_code -print response.body -print response.headers -v3 Mail Send - -This endpoint allows you to send email over SendGrids v3 Web API, the most recent version of our API. If you are looking for documentation about the v2 Mail Send endpoint, please see our v2 API Reference. - -Top level parameters are referred to as "global". - -Individual fields within the personalizations array will override any other global, or message level, parameters that are defined outside of personalizations. - -For an overview of the v3 Mail Send endpoint, please visit our v3 API Reference - -For more detailed information about how to use the v3 Mail Send endpoint, please visit our Classroom. - -POST /mail/send - -This endpoint has a helper, check it out here. - - -data = { - "asm": { -​ "group_id": 1, -​ "groups_to_display": [ -​ 1, -​ 2, -​ 3 -​ ] - }, - "attachments": [ -​ { -​ "content": "[BASE64 encoded content block here]", -​ "content_id": "ii_139db99fdb5c3704", -​ "disposition": "inline", -​ "filename": "file1.jpg", -​ "name": "file1", -​ "type": "jpg" -​ } - ], - "batch_id": "[YOUR BATCH ID GOES HERE]", - "categories": [ -​ "category1", -​ "category2" - ], - "content": [ -​ { -​ "type": "text/html", -​ "value": "

Hello, world!

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

Thanks
The SendGrid Team

", -​ "text": "Thanks,/n The SendGrid Team" -​ }, -​ "sandbox_mode": { -​ "enable": False -​ }, -​ "spam_check": { -​ "enable": True, -​ "post_to_url": "http://example.com/compliance", -​ "threshold": 3 -​ } - }, - "personalizations": [ -​ { -​ "bcc": [ -​ { -​ "email": "sam.doe@example.com", -​ "name": "Sam Doe" -​ } -​ ], -​ "cc": [ -​ { -​ "email": "jane.doe@example.com", -​ "name": "Jane Doe" -​ } -​ ], -​ "custom_args": { -​ "New Argument 1": "New Value 1", -​ "activationAttempt": "1", -​ "customerAccountNumber": "[CUSTOMER ACCOUNT NUMBER GOES HERE]" -​ }, -​ "headers": { -​ "X-Accept-Language": "en", -​ "X-Mailer": "MyApp" -​ }, -​ "send_at": 1409348513, -​ "subject": "Hello, World!", -​ "substitutions": { -​ "id": "substitutions", -​ "type": "object" -​ }, -​ "to": [ -​ { -​ "email": "john.doe@example.com", -​ "name": "John Doe" -​ } -​ ] -​ } - ], - "reply_to": { -​ "email": "sam.smith@example.com", -​ "name": "Sam Smith" - }, - "sections": { -​ "section": { -​ ":sectionName1": "section 1 text", -​ ":sectionName2": "section 2 text" -​ } - }, - "send_at": 1409348513, - "subject": "Hello, World!", - "template_id": "[YOUR TEMPLATE ID GOES HERE]", - "tracking_settings": { -​ "click_tracking": { -​ "enable": True, -​ "enable_text": True -​ }, -​ "ganalytics": { -​ "enable": True, -​ "utm_campaign": "[NAME OF YOUR REFERRER SOURCE]", -​ "utm_content": "[USE THIS SPACE TO DIFFERENTIATE YOUR EMAIL FROM ADS]", -​ "utm_medium": "[NAME OF YOUR MARKETING MEDIUM e.g. email]", -​ "utm_name": "[NAME OF YOUR CAMPAIGN]", -​ "utm_term": "[IDENTIFY PAID KEYWORDS HERE]" -​ }, -​ "open_tracking": { -​ "enable": True, -​ "substitution_tag": "%opentrack" -​ }, -​ "subscription_tracking": { -​ "enable": True, -​ "html": "If you would like to unsubscribe and stop receiving these emails <% clickhere %>.", -​ "substitution_tag": "<%click here%>", -​ "text": "If you would like to unsubscribe and stop receiving these emails <% click here %>." -​ } - } -} -response = sg.client.mail.send.post(request_body=data) -print response.status_code -print response.body -print response.headers - - -MAIL SETTINGS - -Retrieve all mail settings - -This endpoint allows you to retrieve a list of all mail settings. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -GET /mail_settings - - -params = {'limit': 1, 'offset': 1} -response = sg.client.mail_settings.get(query_params=params) -print response.status_code -print response.body -print response.headers -Update address whitelist mail settings - -This endpoint allows you to update your current email address whitelist settings. - -The address whitelist setting whitelists a specified email address or domain for which mail should never be suppressed. For example, you own the domain example.com, and one or more of your recipients use email@example.com addresses, by placing example.com in the address whitelist setting, all bounces, blocks, and unsubscribes logged for that domain will be ignored and sent as if under normal sending conditions. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -PATCH /mail_settings/address_whitelist - - -data = { - "enabled": True, - "list": [ -​ "email1@example.com", -​ "example.com" - ] -} -response = sg.client.mail_settings.address_whitelist.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve address whitelist mail settings - -This endpoint allows you to retrieve your current email address whitelist settings. - -The address whitelist setting whitelists a specified email address or domain for which mail should never be suppressed. For example, you own the domain example.com, and one or more of your recipients use email@example.com addresses, by placing example.com in the address whitelist setting, all bounces, blocks, and unsubscribes logged for that domain will be ignored and sent as if under normal sending conditions. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -GET /mail_settings/address_whitelist - - -response = sg.client.mail_settings.address_whitelist.get() -print response.status_code -print response.body -print response.headers -Update BCC mail settings - -This endpoint allows you to update your current BCC mail settings. - -When the BCC mail setting is enabled, SendGrid will automatically send a blind carbon copy (BCC) to an address for every email sent without adding that address to the header. Please note that only one email address may be entered in this field, if you wish to distribute BCCs to multiple addresses you will need to create a distribution group or use forwarding rules. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -PATCH /mail_settings/bcc - - -data = { - "email": "email@example.com", - "enabled": False -} -response = sg.client.mail_settings.bcc.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve all BCC mail settings - -This endpoint allows you to retrieve your current BCC mail settings. - -When the BCC mail setting is enabled, SendGrid will automatically send a blind carbon copy (BCC) to an address for every email sent without adding that address to the header. Please note that only one email address may be entered in this field, if you wish to distribute BCCs to multiple addresses you will need to create a distribution group or use forwarding rules. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -GET /mail_settings/bcc - - -response = sg.client.mail_settings.bcc.get() -print response.status_code -print response.body -print response.headers -Update bounce purge mail settings - -This endpoint allows you to update your current bounce purge settings. - -This setting allows you to set a schedule for SendGrid to automatically delete contacts from your soft and hard bounce suppression lists. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -PATCH /mail_settings/bounce_purge - - -data = { - "enabled": True, - "hard_bounces": 5, - "soft_bounces": 5 -} -response = sg.client.mail_settings.bounce_purge.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve bounce purge mail settings - -This endpoint allows you to retrieve your current bounce purge settings. - -This setting allows you to set a schedule for SendGrid to automatically delete contacts from your soft and hard bounce suppression lists. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -GET /mail_settings/bounce_purge - - -response = sg.client.mail_settings.bounce_purge.get() -print response.status_code -print response.body -print response.headers -Update footer mail settings - -This endpoint allows you to update your current Footer mail settings. - -The footer setting will insert a custom footer at the bottom of the text and HTML bodies. Use the embedded HTML editor and plain text entry fields to create the content of the footers to be inserted into your emails. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -PATCH /mail_settings/footer - - -data = { - "enabled": True, - "html_content": "...", - "plain_content": "..." -} -response = sg.client.mail_settings.footer.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve footer mail settings - -This endpoint allows you to retrieve your current Footer mail settings. - -The footer setting will insert a custom footer at the bottom of the text and HTML bodies. Use the embedded HTML editor and plain text entry fields to create the content of the footers to be inserted into your emails. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -GET /mail_settings/footer - - -response = sg.client.mail_settings.footer.get() -print response.status_code -print response.body -print response.headers -Update forward bounce mail settings - -This endpoint allows you to update your current bounce forwarding mail settings. - -Activating this setting allows you to specify an email address to which bounce reports are forwarded. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -PATCH /mail_settings/forward_bounce - - -data = { - "email": "example@example.com", - "enabled": True -} -response = sg.client.mail_settings.forward_bounce.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve forward bounce mail settings - -This endpoint allows you to retrieve your current bounce forwarding mail settings. - -Activating this setting allows you to specify an email address to which bounce reports are forwarded. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -GET /mail_settings/forward_bounce - - -response = sg.client.mail_settings.forward_bounce.get() -print response.status_code -print response.body -print response.headers -Update forward spam mail settings - -This endpoint allows you to update your current Forward Spam mail settings. - -Enabling the forward spam setting allows you to specify an email address to which spam reports will be forwarded. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -PATCH /mail_settings/forward_spam - - -data = { - "email": "", - "enabled": False -} -response = sg.client.mail_settings.forward_spam.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve forward spam mail settings - -This endpoint allows you to retrieve your current Forward Spam mail settings. - -Enabling the forward spam setting allows you to specify an email address to which spam reports will be forwarded. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -GET /mail_settings/forward_spam - - -response = sg.client.mail_settings.forward_spam.get() -print response.status_code -print response.body -print response.headers -Update plain content mail settings - -This endpoint allows you to update your current Plain Content mail settings. - -The plain content setting will automatically convert any plain text emails that you send to HTML before sending. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -PATCH /mail_settings/plain_content - - -data = { - "enabled": False -} -response = sg.client.mail_settings.plain_content.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve plain content mail settings - -This endpoint allows you to retrieve your current Plain Content mail settings. - -The plain content setting will automatically convert any plain text emails that you send to HTML before sending. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -GET /mail_settings/plain_content - - -response = sg.client.mail_settings.plain_content.get() -print response.status_code -print response.body -print response.headers -Update spam check mail settings - -This endpoint allows you to update your current spam checker mail settings. - -The spam checker filter notifies you when emails are detected that exceed a predefined spam threshold. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -PATCH /mail_settings/spam_check - - -data = { - "enabled": True, - "max_score": 5, - "url": "url" -} -response = sg.client.mail_settings.spam_check.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve spam check mail settings - -This endpoint allows you to retrieve your current Spam Checker mail settings. - -The spam checker filter notifies you when emails are detected that exceed a predefined spam threshold. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -GET /mail_settings/spam_check - - -response = sg.client.mail_settings.spam_check.get() -print response.status_code -print response.body -print response.headers -Update template mail settings - -This endpoint allows you to update your current legacy email template settings. - -This setting refers to our original email templates. We currently support more fully featured transactional templates. - -The legacy email template setting wraps an HTML template around your email content. This can be useful for sending out marketing email and/or other HTML formatted messages. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -PATCH /mail_settings/template - - -data = { - "enabled": True, - "html_content": "<% body %>" -} -response = sg.client.mail_settings.template.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve legacy template mail settings - -This endpoint allows you to retrieve your current legacy email template settings. - -This setting refers to our original email templates. We currently support more fully featured transactional templates. - -The legacy email template setting wraps an HTML template around your email content. This can be useful for sending out marketing email and/or other HTML formatted messages. - -Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids Web API or SMTP Relay. - -GET /mail_settings/template - - -response = sg.client.mail_settings.template.get() -print response.status_code -print response.body -print response.headers - - -MAILBOX PROVIDERS - -Retrieve email statistics by mailbox provider. - -This endpoint allows you to retrieve your email statistics segmented by recipient mailbox provider. - -We only store up to 7 days of email activity in our database. By default, 500 items will be returned per request via the Advanced Stats API endpoints. - -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. - -GET /mailbox_providers/stats - - -params = {'end_date': '2016-04-01', 'mailbox_providers': 'test_string', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01'} -response = sg.client.mailbox_providers.stats.get(query_params=params) -print response.status_code -print response.body -print response.headers - - -PARTNER SETTINGS - -Returns a list of all partner settings. - -This endpoint allows you to retrieve a list of all partner settings that you can enable. - -Our partner settings allow you to integrate your SendGrid account with our partners to increase your SendGrid experience and functionality. For more information about our partners, and how you can begin integrating with them, please visit our User Guide. - -GET /partner_settings - - -params = {'limit': 1, 'offset': 1} -response = sg.client.partner_settings.get(query_params=params) -print response.status_code -print response.body -print response.headers -Updates New Relic partner settings. - -This endpoint allows you to update or change your New Relic partner settings. - -Our partner settings allow you to integrate your SendGrid account with our partners to increase your SendGrid experience and functionality. For more information about our partners, and how you can begin integrating with them, please visit our User Guide. - -By integrating with New Relic, you can send your SendGrid email statistics to your New Relic Dashboard. If you enable this setting, your stats will be sent to New Relic every 5 minutes. You will need your New Relic License Key to enable this setting. For more information, please see our Classroom. - -PATCH /partner_settings/new_relic - - -data = { - "enable_subuser_statistics": True, - "enabled": True, - "license_key": "" -} -response = sg.client.partner_settings.new_relic.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Returns all New Relic partner settings. - -This endpoint allows you to retrieve your current New Relic partner settings. - -Our partner settings allow you to integrate your SendGrid account with our partners to increase your SendGrid experience and functionality. For more information about our partners, and how you can begin integrating with them, please visit our User Guide. - -By integrating with New Relic, you can send your SendGrid email statistics to your New Relic Dashboard. If you enable this setting, your stats will be sent to New Relic every 5 minutes. You will need your New Relic License Key to enable this setting. For more information, please see our Classroom. - -GET /partner_settings/new_relic - - -response = sg.client.partner_settings.new_relic.get() -print response.status_code -print response.body -print response.headers - - -SCOPES - -Retrieve a list of scopes for which this user has access. - -This endpoint returns a list of all scopes that this user has access to. - -API Keys can be used to authenticate the use of SendGrids v3 Web API, or the Mail API Endpoint. API Keys may be assigned certain permissions, or scopes, that limit which API endpoints they are able to access. For a more detailed explanation of how you can use API Key permissions, please visit our User Guide or Classroom. - -GET /scopes - - -response = sg.client.scopes.get() -print response.status_code -print response.body -print response.headers - - -SENDERS - -Create a Sender Identity - -This endpoint allows you to create a new sender identity. - -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. - -POST /senders - - -data = { - "address": "123 Elm St.", - "address_2": "Apt. 456", - "city": "Denver", - "country": "United States", - "from": { -​ "email": "from@example.com", -​ "name": "Example INC" - }, - "nickname": "My Sender ID", - "reply_to": { -​ "email": "replyto@example.com", -​ "name": "Example INC" - }, - "state": "Colorado", - "zip": "80202" -} -response = sg.client.senders.post(request_body=data) -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. - -GET /senders - - -response = sg.client.senders.get() -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. - -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. - -PATCH /senders/{sender_id} - - -data = { - "address": "123 Elm St.", - "address_2": "Apt. 456", - "city": "Denver", - "country": "United States", - "from": { -​ "email": "from@example.com", -​ "name": "Example INC" - }, - "nickname": "My Sender ID", - "reply_to": { -​ "email": "replyto@example.com", -​ "name": "Example INC" - }, - "state": "Colorado", - "zip": "80202" -} -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 -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. - -GET /senders/{sender_id} - - -sender_id = "test_url_param" -response = sg.client.senders._(sender_id).get() -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. - -DELETE /senders/{sender_id} - - -sender_id = "test_url_param" -response = sg.client.senders._(sender_id).delete() -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. - -POST /senders/{sender_id}/resend_verification - - -sender_id = "test_url_param" -response = sg.client.senders._(sender_id).resend_verification.post() -print response.status_code -print response.body -print response.headers - - -STATS - -Retrieve global email statistics - -This endpoint allows you to retrieve all of your global email statistics between a given date range. - -Parent accounts will see aggregated stats for their account and all subuser accounts. Subuser accounts will only see their own stats. - -GET /stats - - -params = {'aggregated_by': 'day', 'limit': 1, 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} -response = sg.client.stats.get(query_params=params) -print response.status_code -print response.body -print response.headers - - -SUBUSERS - -Create Subuser - -This endpoint allows you to retrieve a list of all of your subusers. You can choose to retrieve specific subusers as well as limit the results that come back from the API. - -For more information about Subusers: - -User Guide > Subusers - -Classroom > How do I add more subusers to my account? - -POST /subusers - - -data = { - "email": "John@example.com", - "ips": [ -​ "1.1.1.1", -​ "2.2.2.2" - ], - "password": "johns_password", - "username": "John@example.com" -} -response = sg.client.subusers.post(request_body=data) -print response.status_code -print response.body -print response.headers -List all Subusers - -This endpoint allows you to retrieve a list of all of your subusers. You can choose to retrieve specific subusers as well as limit the results that come back from the API. - -For more information about Subusers: - -User Guide > Subusers - -Classroom > How do I add more subusers to my account? - -GET /subusers - - -params = {'username': 'test_string', 'limit': 1, 'offset': 1} -response = sg.client.subusers.get(query_params=params) -print response.status_code -print response.body -print response.headers -Retrieve Subuser Reputations - -Subuser sender reputations give a good idea how well a sender is doing with regards to how recipients and recipient servers react to the mail that is being received. When a bounce, spam report, or other negative action happens on a sent email, it will effect your sender rating. - -This endpoint allows you to request the reputations for your subusers. - -GET /subusers/reputations - - -params = {'usernames': 'test_string'} -response = sg.client.subusers.reputations.get(query_params=params) -print response.status_code -print response.body -print response.headers -Retrieve email statistics for your subusers. - -This endpoint allows you to retrieve the email statistics for the given subusers. - -You may retrieve statistics for up to 10 different subusers by including an additional subusers parameter for each additional subuser. - -While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. - -For more information, see our User Guide. - -GET /subusers/stats - - -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 -Retrieve monthly stats for all subusers - -This endpoint allows you to retrieve the monthly email statistics for all subusers over the given date range. - -While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats for your subusers. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. - -When using the sort_by_metric to sort your stats by a specific metric, you can not sort by the following metrics: -bounce_drops, deferred, invalid_emails, processed, spam_report_drops, spam_reports, or unsubscribe_drops. - -For more information, see our User Guide. - -GET /subusers/stats/monthly - - -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 -Retrieve the totals for each email statistic metric for all subusers. - -This endpoint allows you to retrieve the total sums of each email statistic metric for all subusers over the given date range. - -While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. - -For more information, see our User Guide. - -GET /subusers/stats/sums - - -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} -response = sg.client.subusers.stats.sums.get(query_params=params) -print response.status_code -print response.body -print response.headers -Enable/disable a subuser - -This endpoint allows you to enable or disable a subuser. - -For more information about Subusers: - -User Guide > Subusers - -Classroom > How do I add more subusers to my account? - -PATCH /subusers/{subuser_name} - - -data = { - "disabled": False -} -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).patch(request_body=data) -print response.status_code -print response.body -print response.headers -Delete a subuser - -This endpoint allows you to delete a subuser. This is a permanent action, once you delete a subuser it cannot be retrieved. - -For more information about Subusers: - -User Guide > Subusers - -Classroom > How do I add more subusers to my account? - -DELETE /subusers/{subuser_name} - - -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).delete() -print response.status_code -print response.body -print response.headers -Update IPs assigned to a subuser - -Each subuser should be assigned to an IP address, from which all of this subuser's mail will be sent. Often, this is the same IP as the parent account, but each subuser can have their own, or multiple, IP addresses as well. - -More information: - -How to request more IPs - -IPs can be whitelabeled - -PUT /subusers/{subuser_name}/ips - - -data = [ - "127.0.0.1" -] -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).ips.put(request_body=data) -print response.status_code -print response.body -print response.headers -Update Monitor Settings for a subuser - -Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. - -PUT /subusers/{subuser_name}/monitor - - -data = { - "email": "example@example.com", - "frequency": 500 -} -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.put(request_body=data) -print response.status_code -print response.body -print response.headers -Create monitor settings - -Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. - -POST /subusers/{subuser_name}/monitor - - -data = { - "email": "example@example.com", - "frequency": 50000 -} -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.post(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve monitor settings for a subuser - -Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. - -GET /subusers/{subuser_name}/monitor - - -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.get() -print response.status_code -print response.body -print response.headers -Delete monitor settings - -Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. - -DELETE /subusers/{subuser_name}/monitor - - -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.delete() -print response.status_code -print response.body -print response.headers -Retrieve the monthly email statistics for a single subuser - -This endpoint allows you to retrieve the monthly email statistics for a specific subuser. - -While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats for your subusers. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. - -When using the sort_by_metric to sort your stats by a specific metric, you can not sort by the following metrics: -bounce_drops, deferred, invalid_emails, processed, spam_report_drops, spam_reports, or unsubscribe_drops. - -For more information, see our User Guide. - -GET /subusers/{subuser_name}/stats/monthly - - -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 - - -SUPPRESSION - -Retrieve all blocks - -This endpoint allows you to retrieve a list of all email addresses that are currently on your blocks list. - -Blocks happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. - -For more information, please see our User Guide. - -GET /suppression/blocks - - -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 -Delete blocks - -This endpoint allows you to delete all email addresses on your blocks list. - -There are two options for deleting blocked emails: - -You can delete all blocked emails by setting delete_all to true in the request body. - -You can delete some blocked emails by specifying the email addresses in an array in the request body. - -Blocks happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. - -For more information, please see our User Guide. - -DELETE /suppression/blocks - - -data = { - "delete_all": False, - "emails": [ -​ "example1@example.com", -​ "example2@example.com" - ] -} -response = sg.client.suppression.blocks.delete(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve a specific block - -This endpoint allows you to retrieve a specific email address from your blocks list. - -Blocks happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. - -For more information, please see our User Guide. - -GET /suppression/blocks/{email} - - -email = "test_url_param" -response = sg.client.suppression.blocks._(email).get() -print response.status_code -print response.body -print response.headers -Delete a specific block - -This endpoint allows you to delete a specific email address from your blocks list. - -Blocks happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. - -For more information, please see our User Guide. - -DELETE /suppression/blocks/{email} - - -email = "test_url_param" -response = sg.client.suppression.blocks._(email).delete() -print response.status_code -print response.body -print response.headers -Retrieve all bounces - -This endpoint allows you to retrieve all of your bounces. - -Bounces are messages that are returned to the server that sent it. - -For more information see: - -User Guide > Bounces for more information - -Glossary > Bounces - -GET /suppression/bounces - - -params = {'start_time': 1, 'end_time': 1} -response = sg.client.suppression.bounces.get(query_params=params) -print response.status_code -print response.body -print response.headers -Delete bounces - -This endpoint allows you to delete all of your bounces. You can also use this endpoint to remove a specific email address from your bounce list. - -Bounces are messages that are returned to the server that sent it. - -For more information see: - -User Guide > Bounces for more information - -Glossary > Bounces - -Classroom > List Scrubbing Guide - -Note: the delete_all and emails parameters should be used independently of each other as they have different purposes. - -DELETE /suppression/bounces - - -data = { - "delete_all": True, - "emails": [ -​ "example@example.com", -​ "example2@example.com" - ] -} -response = sg.client.suppression.bounces.delete(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve a Bounce - -This endpoint allows you to retrieve a specific bounce for a given email address. - -Bounces are messages that are returned to the server that sent it. - -For more information see: - -User Guide > Bounces for more information - -Glossary > Bounces - -Classroom > List Scrubbing Guide - -GET /suppression/bounces/{email} - - -email = "test_url_param" -response = sg.client.suppression.bounces._(email).get() -print response.status_code -print response.body -print response.headers -Delete a bounce - -This endpoint allows you to remove an email address from your bounce list. - -Bounces are messages that are returned to the server that sent it. This endpoint allows you to delete a single email address from your bounce list. - -For more information see: - -User Guide > Bounces for more information - -Glossary > Bounces - -Classroom > List Scrubbing Guide - -DELETE /suppression/bounces/{email} - - -params = {'email_address': 'example@example.com'} -email = "test_url_param" -response = sg.client.suppression.bounces._(email).delete(query_params=params) -print response.status_code -print response.body -print response.headers -Retrieve all invalid emails - -This endpoint allows you to retrieve a list of all invalid email addresses. - -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. - -Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. - -For more information, please see our User Guide. - -GET /suppression/invalid_emails - - -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 -Delete invalid emails - -This endpoint allows you to remove email addresses from your invalid email address list. - -There are two options for deleting invalid email addresses: - -1) You can delete all invalid email addresses by setting delete_all to true in the request body. -2) You can delete some invalid email addresses by specifying certain addresses in an array in the request body. - -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. - -Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. - -For more information, please see our User Guide. - -DELETE /suppression/invalid_emails - - -data = { - "delete_all": False, - "emails": [ -​ "example1@example.com", -​ "example2@example.com" - ] -} -response = sg.client.suppression.invalid_emails.delete(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve a specific invalid email - -This endpoint allows you to retrieve a specific invalid email addresses. - -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. - -Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. - -For more information, please see our User Guide. - -GET /suppression/invalid_emails/{email} - - -email = "test_url_param" -response = sg.client.suppression.invalid_emails._(email).get() -print response.status_code -print response.body -print response.headers -Delete a specific invalid email - -This endpoint allows you to remove a specific email address from the invalid email address list. - -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. - -Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. - -For more information, please see our User Guide. - -DELETE /suppression/invalid_emails/{email} - - -email = "test_url_param" -response = sg.client.suppression.invalid_emails._(email).delete() -print response.status_code -print response.body -print response.headers -Retrieve a specific spam report - -This endpoint allows you to retrieve a specific spam report. - -Spam reports happen when a recipient indicates that they think your email is spam and then their email provider reports this to SendGrid. - -For more information, please see our User Guide. - -GET /suppression/spam_report/{email} - - -email = "test_url_param" -response = sg.client.suppression.spam_report._(email).get() -print response.status_code -print response.body -print response.headers -Delete a specific spam report - -This endpoint allows you to delete a specific spam report. - -Spam reports happen when a recipient indicates that they think your email is spam and then their email provider reports this to SendGrid. - -For more information, please see our User Guide. - -DELETE /suppression/spam_report/{email} - - -email = "test_url_param" -response = sg.client.suppression.spam_report._(email).delete() -print response.status_code -print response.body -print response.headers -Retrieve all spam reports - -This endpoint allows you to retrieve all spam reports. - -Spam reports happen when a recipient indicates that they think your email is spam and then their email provider reports this to SendGrid. - -For more information, please see our User Guide. - -GET /suppression/spam_reports - - -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 -Delete spam reports - -This endpoint allows you to delete your spam reports. - -There are two options for deleting spam reports: - -1) You can delete all spam reports by setting "delete_all" to true in the request body. -2) You can delete some spam reports by specifying the email addresses in an array in the request body. - -Spam reports happen when a recipient indicates that they think your email is spam and then their email provider reports this to SendGrid. - -For more information, please see our User Guide. - -DELETE /suppression/spam_reports - - -data = { - "delete_all": False, - "emails": [ -​ "example1@example.com", -​ "example2@example.com" - ] -} -response = sg.client.suppression.spam_reports.delete(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve all global suppressions - -This endpoint allows you to retrieve a list of all email address that are globally suppressed. - -A global suppression (or global unsubscribe) is an email address of a recipient who does not want to receive any of your messages. A globally suppressed recipient will be removed from any email you send. For more information, please see our User Guide. - -GET /suppression/unsubscribes - - -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 - - -TEMPLATES - -Create a transactional template. - -This endpoint allows you to create a transactional template. - -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. - -Transactional templates are templates created specifically for transactional email and are not to be confused with Marketing Campaigns templates. For more information about transactional templates, please see our User Guide. - -POST /templates - - -data = { - "name": "example_name" -} -response = sg.client.templates.post(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve all transactional templates. - -This endpoint allows you to retrieve all transactional templates. - -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. - -Transactional templates are templates created specifically for transactional email and are not to be confused with Marketing Campaigns templates. For more information about transactional templates, please see our User Guide. - -GET /templates - - -response = sg.client.templates.get() -print response.status_code -print response.body -print response.headers -Edit a transactional template. - -This endpoint allows you to edit a transactional template. - -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. - -Transactional templates are templates created specifically for transactional email and are not to be confused with Marketing Campaigns templates. For more information about transactional templates, please see our User Guide. - -PATCH /templates/{template_id} - - -data = { - "name": "new_example_name" -} -template_id = "test_url_param" -response = sg.client.templates._(template_id).patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve a single transactional template. - -This endpoint allows you to retrieve a single transactional template. - -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. - -Transactional templates are templates created specifically for transactional email and are not to be confused with Marketing Campaigns templates. For more information about transactional templates, please see our User Guide. - -GET /templates/{template_id} - - -template_id = "test_url_param" -response = sg.client.templates._(template_id).get() -print response.status_code -print response.body -print response.headers -Delete a template. - -This endpoint allows you to delete a transactional template. - -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. - -Transactional templates are templates created specifically for transactional email and are not to be confused with Marketing Campaigns templates. For more information about transactional templates, please see our User Guide. - -DELETE /templates/{template_id} - - -template_id = "test_url_param" -response = sg.client.templates._(template_id).delete() -print response.status_code -print response.body -print response.headers -Create a new transactional template version. - -This endpoint allows you to create a new version of a template. - -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. - -For more information about transactional templates, please see our User Guide. - -POST /templates/{template_id}/versions - - -data = { - "active": 1, - "html_content": "<%body%>", - "name": "example_version_name", - "plain_content": "<%body%>", - "subject": "<%subject%>", - "template_id": "ddb96bbc-9b92-425e-8979-99464621b543" -} -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 -Edit a transactional template version. - -This endpoint allows you to edit a version of one of your transactional templates. - -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. - -For more information about transactional templates, please see our User Guide. - -URI Parameters - -URI PARAMETER TYPE DESCRIPTION -template_id string The ID of the original template -version_id string The ID of the template version -PATCH /templates/{template_id}/versions/{version_id} - - -data = { - "active": 1, - "html_content": "<%body%>", - "name": "updated_example_name", - "plain_content": "<%body%>", - "subject": "<%subject%>" -} -template_id = "test_url_param" -version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve a specific transactional template version. - -This endpoint allows you to retrieve a specific version of a template. - -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. - -For more information about transactional templates, please see our User Guide. - -URI Parameters - -URI PARAMETER TYPE DESCRIPTION -template_id string The ID of the original template -version_id string The ID of the template version -GET /templates/{template_id}/versions/{version_id} - - -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 -Delete a transactional template version. - -This endpoint allows you to delete one of your transactional template versions. - -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. - -For more information about transactional templates, please see our User Guide. - -URI Parameters - -URI PARAMETER TYPE DESCRIPTION -template_id string The ID of the original template -version_id string The ID of the template version -DELETE /templates/{template_id}/versions/{version_id} - - -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 -Activate a transactional template version. - -This endpoint allows you to activate a version of one of your templates. - -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. - -For more information about transactional templates, please see our User Guide. - -URI Parameters - -URI PARAMETER TYPE DESCRIPTION -template_id string The ID of the original template -version_id string The ID of the template version -POST /templates/{template_id}/versions/{version_id}/activate - - -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 - - -TRACKING SETTINGS - -Retrieve Tracking Settings - -This endpoint allows you to retrieve a list of all tracking settings that you can enable on your account. - -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. - -For more information about tracking, please see our User Guide. - -GET /tracking_settings - - -params = {'limit': 1, 'offset': 1} -response = sg.client.tracking_settings.get(query_params=params) -print response.status_code -print response.body -print response.headers -Update Click Tracking Settings - -This endpoint allows you to change your current click tracking setting. You can enable, or disable, click tracking using this endpoint. - -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. - -For more information about tracking, please see our User Guide. - -PATCH /tracking_settings/click - - -data = { - "enabled": True -} -response = sg.client.tracking_settings.click.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve Click Track Settings - -This endpoint allows you to retrieve your current click tracking setting. - -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. - -For more information about tracking, please see our User Guide. - -GET /tracking_settings/click - - -response = sg.client.tracking_settings.click.get() -print response.status_code -print response.body -print response.headers -Update Google Analytics Settings - -This endpoint allows you to update your current setting for Google Analytics. - -For more information about using Google Analytics, please refer to Googles URL Builder and their article on "Best Practices for Campaign Building". - -We default the settings to Googles recommendations. For more information, see Google Analytics Demystified. - -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. - -For more information about tracking, please see our User Guide. - -PATCH /tracking_settings/google_analytics - - -data = { - "enabled": True, - "utm_campaign": "website", - "utm_content": "", - "utm_medium": "email", - "utm_source": "sendgrid.com", - "utm_term": "" -} -response = sg.client.tracking_settings.google_analytics.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve Google Analytics Settings - -This endpoint allows you to retrieve your current setting for Google Analytics. - -For more information about using Google Analytics, please refer to Googles URL Builder and their article on "Best Practices for Campaign Building". - -We default the settings to Googles recommendations. For more information, see Google Analytics Demystified. - -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. - -For more information about tracking, please see our User Guide. - -GET /tracking_settings/google_analytics - - -response = sg.client.tracking_settings.google_analytics.get() -print response.status_code -print response.body -print response.headers -Update Open Tracking Settings - -This endpoint allows you to update your current settings for open tracking. - -Open Tracking adds an invisible image at the end of the email which can track email opens. If the email recipient has images enabled on their email client, a request to SendGrids server for the invisible image is executed and an open event is logged. These events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook. - -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. - -For more information about tracking, please see our User Guide. - -PATCH /tracking_settings/open - - -data = { - "enabled": True -} -response = sg.client.tracking_settings.open.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Get Open Tracking Settings - -This endpoint allows you to retrieve your current settings for open tracking. - -Open Tracking adds an invisible image at the end of the email which can track email opens. If the email recipient has images enabled on their email client, a request to SendGrids server for the invisible image is executed and an open event is logged. These events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook. - -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. - -For more information about tracking, please see our User Guide. - -GET /tracking_settings/open - - -response = sg.client.tracking_settings.open.get() -print response.status_code -print response.body -print response.headers -Update Subscription Tracking Settings - -This endpoint allows you to update your current settings for subscription tracking. - -Subscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails. - -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. - -For more information about tracking, please see our User Guide. - -PATCH /tracking_settings/subscription - - -data = { - "enabled": True, - "html_content": "html content", - "landing": "landing page html", - "plain_content": "text content", - "replace": "replacement tag", - "url": "url" -} -response = sg.client.tracking_settings.subscription.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve Subscription Tracking Settings - -This endpoint allows you to retrieve your current settings for subscription tracking. - -Subscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails. - -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. - -For more information about tracking, please see our User Guide. - -GET /tracking_settings/subscription - - -response = sg.client.tracking_settings.subscription.get() -print response.status_code -print response.body -print response.headers - - -USER - -Get a user's account information. - -This endpoint allows you to retrieve your user account details. - -Your user's account information includes the user's account type and reputation. - -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. - -For more information about your user profile: - -SendGrid Account Settings - -GET /user/account - - -response = sg.client.user.account.get() -print response.status_code -print response.body -print response.headers -Retrieve your credit balance - -This endpoint allows you to retrieve the current credit balance for your account. - -Your monthly credit allotment limits the number of emails you may send before incurring overage charges. For more information about credits and billing, please visit our Classroom. - -GET /user/credits - - -response = sg.client.user.credits.get() -print response.status_code -print response.body -print response.headers -Update your account email address - -This endpoint allows you to update the email address currently on file for your account. - -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. - -For more information about your user profile: - -SendGrid Account Settings - -PUT /user/email - - -data = { - "email": "example@example.com" -} -response = sg.client.user.email.put(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve your account email address - -This endpoint allows you to retrieve the email address currently on file for your account. - -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. - -For more information about your user profile: - -SendGrid Account Settings - -GET /user/email - - -response = sg.client.user.email.get() -print response.status_code -print response.body -print response.headers -Update your password - -This endpoint allows you to update your password. - -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. - -For more information about your user profile: - -SendGrid Account Settings - -PUT /user/password - - -data = { - "new_password": "new_password", - "old_password": "old_password" -} -response = sg.client.user.password.put(request_body=data) -print response.status_code -print response.body -print response.headers -Update a user's profile - -This endpoint allows you to update your current profile details. - -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. - -For more information about your user profile: - -SendGrid Account Settings - -It should be noted that any one or more of the parameters can be updated via the PATCH /user/profile endpoint. The only requirement is that you include at least one when you PATCH. - -PATCH /user/profile - - -data = { - "city": "Orange", - "first_name": "Example", - "last_name": "User" -} -response = sg.client.user.profile.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Get a user's profile - -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. - -For more information about your user profile: - -SendGrid Account Settings - -GET /user/profile - - -response = sg.client.user.profile.get() -print response.status_code -print response.body -print response.headers -Cancel or pause a scheduled send - -This endpoint allows you to cancel or pause an email that has been scheduled to be sent. - -If the maximum number of cancellations/pauses are added, HTTP 400 will -be returned. - -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. - -POST /user/scheduled_sends - - -data = { - "batch_id": "YOUR_BATCH_ID", - "status": "pause" -} -response = sg.client.user.scheduled_sends.post(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve all scheduled sends - -This endpoint allows you to retrieve all cancel/paused scheduled send information. - -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. - -GET /user/scheduled_sends - - -response = sg.client.user.scheduled_sends.get() -print response.status_code -print response.body -print response.headers -Update user scheduled send information - -This endpoint allows you to update the status of a scheduled send for the given batch_id. - -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. - -PATCH /user/scheduled_sends/{batch_id} - - -data = { - "status": "pause" -} -batch_id = "test_url_param" -response = sg.client.user.scheduled_sends._(batch_id).patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve scheduled send - -This endpoint allows you to retrieve the cancel/paused scheduled send information for a specific batch_id. - -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. - -GET /user/scheduled_sends/{batch_id} - - -batch_id = "test_url_param" -response = sg.client.user.scheduled_sends._(batch_id).get() -print response.status_code -print response.body -print response.headers -Delete a cancellation or pause of a scheduled send - -This endpoint allows you to delete the cancellation/pause of a scheduled send. - -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. - -DELETE /user/scheduled_sends/{batch_id} - - -batch_id = "test_url_param" -response = sg.client.user.scheduled_sends._(batch_id).delete() -print response.status_code -print response.body -print response.headers -Update Enforced TLS settings - -This endpoint allows you to update your current Enforced TLS settings. - -The Enforced TLS settings specify whether or not the recipient is required to support TLS or have a valid certificate. See the SMTP Ports User Guide for more information on opportunistic TLS. - -Note: If either setting is enabled and the recipient does not support TLS or have a valid certificate, we drop the message and send a block event with TLS required but not supported as the description. - -PATCH /user/settings/enforced_tls - - -data = { - "require_tls": True, - "require_valid_cert": False -} -response = sg.client.user.settings.enforced_tls.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve current Enforced TLS settings. - -This endpoint allows you to retrieve your current Enforced TLS settings. - -The Enforced TLS settings specify whether or not the recipient is required to support TLS or have a valid certificate. See the SMTP Ports User Guide for more information on opportunistic TLS. - -Note: If either setting is enabled and the recipient does not support TLS or have a valid certificate, we drop the message and send a block event with TLS required but not supported as the description. - -GET /user/settings/enforced_tls - - -response = sg.client.user.settings.enforced_tls.get() -print response.status_code -print response.body -print response.headers -Update your username - -This endpoint allows you to update the username for your account. - -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. - -For more information about your user profile: - -SendGrid Account Settings - -PUT /user/username - - -data = { - "username": "test_username" -} -response = sg.client.user.username.put(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve your username - -This endpoint allows you to retrieve your current account username. - -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. - -For more information about your user profile: - -SendGrid Account Settings - -GET /user/username - - -response = sg.client.user.username.get() -print response.status_code -print response.body -print response.headers -Update Event Notification Settings - -This endpoint allows you to update your current event webhook settings. - -If an event type is marked as true, then the event webhook will include information about that event. - -SendGrids Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email. - -Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. - -PATCH /user/webhooks/event/settings - - -data = { - "bounce": True, - "click": True, - "deferred": True, - "delivered": True, - "dropped": True, - "enabled": True, - "group_resubscribe": True, - "group_unsubscribe": True, - "open": True, - "processed": True, - "spam_report": True, - "unsubscribe": True, - "url": "url" -} -response = sg.client.user.webhooks.event.settings.patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve Event Webhook settings - -This endpoint allows you to retrieve your current event webhook settings. - -If an event type is marked as true, then the event webhook will include information about that event. - -SendGrids Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email. - -Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. - -GET /user/webhooks/event/settings - - -response = sg.client.user.webhooks.event.settings.get() -print response.status_code -print response.body -print response.headers -Test Event Notification Settings - -This endpoint allows you to test your event webhook by sending a fake event notification post to the provided URL. - -SendGrids Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email. - -Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. - -POST /user/webhooks/event/test - - -data = { - "url": "url" -} -response = sg.client.user.webhooks.event.test.post(request_body=data) -print response.status_code -print response.body -print response.headers -Create a parse setting - -This endpoint allows you to create a new inbound parse setting. - -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our User Guide. - -POST /user/webhooks/parse/settings - - -data = { - "hostname": "myhostname.com", - "send_raw": False, - "spam_check": True, - "url": "http://email.myhosthame.com" -} -response = sg.client.user.webhooks.parse.settings.post(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve all parse settings - -This endpoint allows you to retrieve all of your current inbound parse settings. - -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our User Guide. - -GET /user/webhooks/parse/settings - - -response = sg.client.user.webhooks.parse.settings.get() -print response.status_code -print response.body -print response.headers -Update a parse setting - -This endpoint allows you to update a specific inbound parse setting. - -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our User Guide. - -PATCH /user/webhooks/parse/settings/{hostname} - - -data = { - "send_raw": True, - "spam_check": False, - "url": "http://newdomain.com/parse" -} -hostname = "test_url_param" -response = sg.client.user.webhooks.parse.settings._(hostname).patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve a specific parse setting - -This endpoint allows you to retrieve a specific inbound parse setting. - -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our User Guide. - -GET /user/webhooks/parse/settings/{hostname} - - -hostname = "test_url_param" -response = sg.client.user.webhooks.parse.settings._(hostname).get() -print response.status_code -print response.body -print response.headers -Delete a parse setting - -This endpoint allows you to delete a specific inbound parse setting. - -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our User Guide. - -DELETE /user/webhooks/parse/settings/{hostname} - - -hostname = "test_url_param" -response = sg.client.user.webhooks.parse.settings._(hostname).delete() -print response.status_code -print response.body -print response.headers -Retrieves Inbound Parse Webhook statistics. - -This endpoint allows you to retrieve the statistics for your Parse Webhook usage. - -SendGrid's Inbound Parse Webhook allows you to parse the contents and attachments of incoming emails. The Parse API can then POST the parsed emails to a URL that you specify. The Inbound Parse Webhook cannot parse messages greater than 20MB in size, including all attachments. - -There are a number of pre-made integrations for the SendGrid Parse Webhook which make processing events easy. You can find these integrations in the Library Index. - -GET /user/webhooks/parse/stats - - -params = {'aggregated_by': 'day', 'limit': 'test_string', 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 'test_string'} -response = sg.client.user.webhooks.parse.stats.get(query_params=params) -print response.status_code -print response.body -print response.headers - - -WHITELABEL - -Create a domain whitelabel. - -This endpoint allows you to create a whitelabel for one of your domains. - -If you are creating a domain whitelabel that you would like a subuser to use, you have two options: - -Use the "username" parameter. This allows you to create a whitelabel on behalf of your subuser. This means the subuser is able to see and modify the created whitelabel. - -Use the Association workflow (see Associate Domain section). This allows you to assign a whitelabel created by the parent to a subuser. This means the subuser will default to the assigned whitelabel, but will not be able to see or modify that whitelabel. However, if the subuser creates their own whitelabel it will overwrite the assigned whitelabel. - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. - -For more information on whitelabeling, please see our User Guide - -POST /whitelabel/domains - - -data = { - "automatic_security": False, - "custom_spf": True, - "default": True, - "domain": "example.com", - "ips": [ -​ "192.168.1.1", -​ "192.168.1.2" - ], - "subdomain": "news", - "username": "john@example.com" -} -response = sg.client.whitelabel.domains.post(request_body=data) -print response.status_code -print response.body -print response.headers -List all domain whitelabels. - -This endpoint allows you to retrieve a list of all domain whitelabels you have created. - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. - -For more information on whitelabeling, please see our User Guide - -GET /whitelabel/domains - - -params = {'username': 'test_string', 'domain': 'test_string', 'exclude_subusers': 'true', 'limit': 1, 'offset': 1} -response = sg.client.whitelabel.domains.get(query_params=params) -print response.status_code -print response.body -print response.headers -Get the default domain whitelabel. - -This endpoint allows you to retrieve the default whitelabel for a domain. - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. - -For more information on whitelabeling, please see our User Guide - -URI Parameters - -URI PARAMETER TYPE DESCRIPTION -domain string The domain to find a default domain whitelabel for. -GET /whitelabel/domains/default - - -response = sg.client.whitelabel.domains.default.get() -print response.status_code -print response.body -print response.headers -List the domain whitelabel associated with the given user. - -This endpoint allows you to retrieve all of the whitelabels that have been assigned to a specific subuser. - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. - -Domain whitelabels can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's whitelabels. To associate a whitelabel with a subuser, the parent account must first create the whitelabel and validate it. The parent may then associate the whitelabel via the subuser management tools. - -For more information on whitelabeling, please see our User Guide - -URI Parameters - -URI PARAMETER TYPE DESCRIPTION -username string Username of the subuser to find associated whitelabels for. -GET /whitelabel/domains/subuser - - -response = sg.client.whitelabel.domains.subuser.get() -print response.status_code -print response.body -print response.headers -Disassociate a domain whitelabel from a given user. - -This endpoint allows you to disassociate a specific whitelabel from a subuser. - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. - -Domain whitelabels can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's whitelabels. To associate a whitelabel with a subuser, the parent account must first create the whitelabel and validate it. The parent may then associate the whitelabel via the subuser management tools. - -For more information on whitelabeling, please see our User Guide - -URI Parameters - -URI PARAMETER TYPE REQUIRED? DESCRIPTION -username string required Username for the subuser to find associated whitelabels for. -DELETE /whitelabel/domains/subuser - - -response = sg.client.whitelabel.domains.subuser.delete() -print response.status_code -print response.body -print response.headers -Update a domain whitelabel. - -This endpoint allows you to update the settings for a domain whitelabel. - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. - -For more information on whitelabeling, please see our User Guide - -PATCH /whitelabel/domains/{domain_id} - - -data = { - "custom_spf": True, - "default": False -} -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 -Retrieve a domain whitelabel. - -This endpoint allows you to retrieve a specific domain whitelabel. - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. - -For more information on whitelabeling, please see our User Guide - -GET /whitelabel/domains/{domain_id} - - -domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).get() -print response.status_code -print response.body -print response.headers -Delete a domain whitelabel. - -This endpoint allows you to delete a domain whitelabel. - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. - -For more information on whitelabeling, please see our User Guide - -DELETE /whitelabel/domains/{domain_id} - - -domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).delete() -print response.status_code -print response.body -print response.headers -Associate a domain whitelabel with a given user. - -This endpoint allows you to associate a specific domain whitelabel with a subuser. - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. - -Domain whitelabels can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's whitelabels. To associate a whitelabel with a subuser, the parent account must first create the whitelabel and validate it. The parent may then associate the whitelabel via the subuser management tools. - -For more information on whitelabeling, please see our User Guide - -URI Parameters - -URI PARAMETER TYPE DESCRIPTION -domain_id integer ID of the domain whitelabel to associate with the subuser. -POST /whitelabel/domains/{domain_id}/subuser - - -data = { - "username": "jane@example.com" -} -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 -Add an IP to a domain whitelabel. - -This endpoint allows you to add an IP address to a domain whitelabel. - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. - -For more information on whitelabeling, please see our User Guide - -URI Parameters - -URI PARAMETER TYPE DESCRIPTION -id integer ID of the domain to which you are adding an IP -POST /whitelabel/domains/{id}/ips - - -data = { - "ip": "192.168.0.1" -} -id = "test_url_param" -response = sg.client.whitelabel.domains._(id).ips.post(request_body=data) -print response.status_code -print response.body -print response.headers -Remove an IP from a domain whitelabel. - -This endpoint allows you to remove a domain's IP address from that domain's whitelabel. - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. - -For more information on whitelabeling, please see our User Guide - -URI Parameters - -URI PARAMETER TYPE DESCRIPTION -id integer ID of the domain whitelabel to delete the IP from. -ip string IP to remove from the domain whitelabel. -DELETE /whitelabel/domains/{id}/ips/{ip} - - -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 -Validate a domain whitelabel. - -This endpoint allows you to validate a domain whitelabel. If it fails, it will return an error message describing why the whitelabel could not be validated. - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. - -For more information on whitelabeling, please see our User Guide - -URI Parameters - -URI PARAMETER TYPE DESCRIPTION -id integer ID of the domain whitelabel to validate. -POST /whitelabel/domains/{id}/validate - - -id = "test_url_param" -response = sg.client.whitelabel.domains._(id).validate.post() -print response.status_code -print response.body -print response.headers -Create an IP whitelabel - -This endpoint allows you to create an IP whitelabel. - -When creating an IP whitelable, you should use the same subdomain that you used when you created a domain whitelabel. - -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. - -For more information, please see our User Guide. - -POST /whitelabel/ips - - -data = { - "domain": "example.com", - "ip": "192.168.1.1", - "subdomain": "email" -} -response = sg.client.whitelabel.ips.post(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve all IP whitelabels - -This endpoint allows you to retrieve all of the IP whitelabels that have been created by this account. - -You may include a search key by using the "ip" parameter. This enables you to perform a prefix search for a given IP segment (e.g. "192."). - -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. - -For more information, please see our User Guide. - -GET /whitelabel/ips - - -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 -Retrieve an IP whitelabel - -This endpoint allows you to retrieve an IP whitelabel. - -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. - -For more information, please see our User Guide. - -GET /whitelabel/ips/{id} - - -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).get() -print response.status_code -print response.body -print response.headers -Delete an IP whitelabel - -This endpoint allows you to delete an IP whitelabel. - -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. - -For more information, please see our User Guide. - -DELETE /whitelabel/ips/{id} - - -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).delete() -print response.status_code -print response.body -print response.headers -Validate an IP whitelabel - -This endpoint allows you to validate an IP whitelabel. - -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. - -For more information, please see our User Guide. - -POST /whitelabel/ips/{id}/validate - - -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).validate.post() -print response.status_code -print response.body -print response.headers -Create a Link Whitelabel - -This endpoint allows you to create a new link whitelabel. - -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. - -For more information, please see our User Guide. - -POST /whitelabel/links - - -data = { - "default": True, - "domain": "example.com", - "subdomain": "mail" -} -params = {'limit': 1, 'offset': 1} -response = sg.client.whitelabel.links.post(request_body=data, query_params=params) -print response.status_code -print response.body -print response.headers -Retrieve all link whitelabels - -This endpoint allows you to retrieve all link whitelabels. - -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. - -For more information, please see our User Guide. - -GET /whitelabel/links - - -params = {'limit': 1} -response = sg.client.whitelabel.links.get(query_params=params) -print response.status_code -print response.body -print response.headers -Retrieve a Default Link Whitelabel - -This endpoint allows you to retrieve the default link whitelabel. - -Default link whitelabel is the actual link whitelabel to be used when sending messages. If there are multiple link whitelabels, the default is determined by the following order: - - -Validated link whitelabels marked as "default" -Legacy link whitelabels (migrated from the whitelabel wizard) -Default SendGrid link whitelabel (i.e. 100.ct.sendgrid.net) - -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. - -For more information, please see our User Guide. - -GET /whitelabel/links/default - - -params = {'domain': 'test_string'} -response = sg.client.whitelabel.links.default.get(query_params=params) -print response.status_code -print response.body -print response.headers -Retrieve Associated Link Whitelabel - -This endpoint allows you to retrieve the associated link whitelabel for a subuser. - -Link whitelables can be associated with subusers from the parent account. This functionality allows -subusers to send mail using their parent's link whitelabels. To associate a link whitelabel, the parent account -must first create a whitelabel and validate it. The parent may then associate that whitelabel with a subuser via the API or the Subuser Management page in the user interface. - -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. - -For more information, please see our User Guide. - -GET /whitelabel/links/subuser - - -params = {'username': 'test_string'} -response = sg.client.whitelabel.links.subuser.get(query_params=params) -print response.status_code -print response.body -print response.headers -Disassociate a Link Whitelabel - -This endpoint allows you to disassociate a link whitelabel from a subuser. - -Link whitelables can be associated with subusers from the parent account. This functionality allows -subusers to send mail using their parent's link whitelabels. To associate a link whitelabel, the parent account -must first create a whitelabel and validate it. The parent may then associate that whitelabel with a subuser via the API or the Subuser Management page in the user interface. - -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. - -For more information, please see our User Guide. - -DELETE /whitelabel/links/subuser - - -params = {'username': 'test_string'} -response = sg.client.whitelabel.links.subuser.delete(query_params=params) -print response.status_code -print response.body -print response.headers -Update a Link Whitelabel - -This endpoint allows you to update a specific link whitelabel. You can use this endpoint to change a link whitelabel's default status. - -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. - -For more information, please see our User Guide. - -PATCH /whitelabel/links/{id} - - -data = { - "default": True -} -id = "test_url_param" -response = sg.client.whitelabel.links._(id).patch(request_body=data) -print response.status_code -print response.body -print response.headers -Retrieve a Link Whitelabel - -This endpoint allows you to retrieve a specific link whitelabel. - -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. - -For more information, please see our User Guide. - -GET /whitelabel/links/{id} - - -id = "test_url_param" -response = sg.client.whitelabel.links._(id).get() -print response.status_code -print response.body -print response.headers -Delete a Link Whitelabel - -This endpoint allows you to delete a link whitelabel. - -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. - -For more information, please see our User Guide. - -DELETE /whitelabel/links/{id} - - -id = "test_url_param" -response = sg.client.whitelabel.links._(id).delete() -print response.status_code -print response.body -print response.headers -Validate a Link Whitelabel - -This endpoint allows you to validate a link whitelabel. - -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. - -For more information, please see our User Guide. - -POST /whitelabel/links/{id}/validate - - -id = "test_url_param" -response = sg.client.whitelabel.links._(id).validate.post() -print response.status_code -print response.body -print response.headers -Associate a Link Whitelabel - -This endpoint allows you to associate a link whitelabel with a subuser account. - -Link whitelables can be associated with subusers from the parent account. This functionality allows -subusers to send mail using their parent's link whitelabels. To associate a link whitelabel, the parent account -must first create a whitelabel and validate it. The parent may then associate that whitelabel with a subuser via the API or the Subuser Management page in the user interface. - -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. - -For more information, please see our User Guide. - -POST /whitelabel/links/{link_id}/subuser - - -data = { - "username": "jane@example.com" -} -link_id = "test_url_param" -response = sg.client.whitelabel.links._(link_id).subuser.post(request_body=data) -print response.status_code -print response.body -print response.headersPOST /access_settings/whitelist +### POST /access_settings/whitelist ```python @@ -4912,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. @@ -4959,7 +338,7 @@ print response.headers **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). @@ -5028,7 +407,7 @@ print response.headers **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). @@ -5055,7 +434,7 @@ print response.headers **This endpoint allows you to create a new suppression group.** -Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. +Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example Daily Newsletters, Invoices, System Alerts. The **name** and **description** of the unsubscribe group will be visible by recipients when they are managing their subscriptions. @@ -5099,7 +478,7 @@ print response.headers **This endpoint allows you to update or change a suppression group.** -Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. +Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example Daily Newsletters, Invoices, System Alerts. The **name** and **description** of the unsubscribe group will be visible by recipients when they are managing their subscriptions. @@ -5124,7 +503,7 @@ print response.headers **This endpoint allows you to retrieve a single suppression group.** -Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. +Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example Daily Newsletters, Invoices, System Alerts. The **name** and **description** of the unsubscribe group will be visible by recipients when they are managing their subscriptions. @@ -5146,7 +525,7 @@ print response.headers You can only delete groups that have not been attached to sent mail in the last 60 days. If a recipient uses the "one-click unsubscribe" option on an email associated with a deleted group, that recipient will be added to the global suppression list. -Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. +Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example Daily Newsletters, Invoices, System Alerts. The **name** and **description** of the unsubscribe group will be visible by recipients when they are managing their subscriptions. @@ -5333,7 +712,7 @@ print response.headers # BROWSERS -## Retrieve email statistics by browser. +## Retrieve email statistics by the browser. **This endpoint allows you to retrieve your email statistics segmented by browser type.** @@ -5360,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: @@ -6039,7 +1418,7 @@ 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. @@ -6097,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 @@ -6151,7 +1530,7 @@ print response.headers **This endpoint allows you to retrieve the lists that a given recipient belongs to.** -Each recipient can be on many lists. This endpoint gives you all of the lists that any one recipient has been added to. +Each recipient can be on many lists. This endpoint gives you all of the lists that anyone recipient has been added to. The Contacts API helps you manage your [Marketing Campaigns](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/index.html) recipients. @@ -6308,7 +1687,7 @@ 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. @@ -6358,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). @@ -6404,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. @@ -6677,7 +2056,7 @@ print response.headers **This endpoint allows you to generate a new batch ID. This batch ID can be associated with scheduled sends via the mail/send endpoint.** -If you set the SMTPAPI header `batch_id`, it allows you to then associate multiple scheduled mail/send requests together with the same ID. Then at anytime up to 10 minutes before the schedule date, you can cancel all of the mail/send requests that have this batch ID by calling the Cancel Scheduled Send endpoint. +If you set the SMTPAPI header `batch_id`, it allows you to then associate multiple scheduled mails/send requests together with the same ID. Then at any time up to 10 minutes before the scheduled date, you can cancel all of the mail/send requests that have this batch ID by calling the Cancel Scheduled Send endpoint. More Information: @@ -6696,7 +2075,7 @@ print response.headers **This endpoint allows you to validate a batch ID.** -If you set the SMTPAPI header `batch_id`, it allows you to then associate multiple scheduled mail/send requests together with the same ID. Then at anytime up to 10 minutes before the schedule date, you can cancel all of the mail/send requests that have this batch ID by calling the Cancel Scheduled Send endpoint. +If you set the SMTPAPI header `batch_id`, it allows you to then associate multiple scheduled mails/send requests together with the same ID. Then at anytime up to 10 minutes before the scheduled date, you can cancel all of the mail/send requests that have this batch ID by calling the Cancel Scheduled Send endpoint. More Information: @@ -6935,7 +2314,7 @@ print response.headers **This endpoint allows you to update your current BCC mail settings.** -When the BCC mail setting is enabled, SendGrid will automatically send a blind carbon copy (BCC) to an address for every email sent without adding that address to the header. Please note that only one email address may be entered in this field, if you wish to distribute BCCs to multiple addresses you will need to create a distribution group or use forwarding rules. +When the BCC mail setting is enabled, SendGrid will automatically send a blind carbon copy (BCC) to an address for every email sent without adding that address to the header. Please note that only one email address may be entered in this field if you wish to distribute BCCs to multiple addresses you will need to create a distribution group or use forwarding rules. Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). @@ -6956,7 +2335,7 @@ print response.headers **This endpoint allows you to retrieve your current BCC mail settings.** -When the BCC mail setting is enabled, SendGrid will automatically send a blind carbon copy (BCC) to an address for every email sent without adding that address to the header. Please note that only one email address may be entered in this field, if you wish to distribute BCCs to multiple addresses you will need to create a distribution group or use forwarding rules. +When the BCC mail setting is enabled, SendGrid will automatically send a blind carbon copy (BCC) to an address for every email sent without adding that address to the header. Please note that only one email address may be entered in this field if you wish to distribute BCCs to multiple addresses you will need to create a distribution group or use forwarding rules. Mail settings allow you to tell SendGrid specific things to do to every email that you send to your recipients over SendGrids [Web API](https://sendgrid.com/docs/API_Reference/Web_API/mail.html) or [SMTP Relay](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html). @@ -7347,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 @@ -7379,7 +2758,7 @@ print response.headers **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 @@ -7394,7 +2773,7 @@ print response.headers **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. @@ -7429,7 +2808,7 @@ print response.headers **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} @@ -7445,7 +2824,7 @@ print response.headers **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} @@ -7461,7 +2840,7 @@ print response.headers **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 @@ -7543,7 +2922,7 @@ print response.headers ``` ## Retrieve Subuser Reputations -Subuser sender reputations give a good idea how well a sender is doing with regards to how recipients and recipient servers react to the mail that is being received. When a bounce, spam report, or other negative action happens on a sent email, it will effect your sender rating. +Subuser sender reputations give a good idea how well a sender is doing with regards to how recipients and recipient servers react to the mail that is being received. When a bounce, spam report, or other negative action happens on a sent email, it will affect your sender rating. This endpoint allows you to request the reputations for your subusers. @@ -7952,7 +3331,7 @@ print response.headers **This endpoint allows you to retrieve a list of all invalid email addresses.** -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipient's mail server. Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. @@ -7977,7 +3356,7 @@ There are two options for deleting invalid email addresses: 1) You can delete all invalid email addresses by setting `delete_all` to true in the request body. 2) You can delete some invalid email addresses by specifying certain addresses in an array in the request body. -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipient's mail server. Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. @@ -8001,9 +3380,9 @@ print response.headers ``` ## Retrieve a specific invalid email -**This endpoint allows you to retrieve a specific invalid email addresses.** +**This endpoint allows you to retrieve a specific invalid email addresse.** -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipient's mail server. Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. @@ -8023,7 +3402,7 @@ print response.headers **This endpoint allows you to remove a specific email address from the invalid email address list.** -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipient's mail server. Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. @@ -8124,7 +3503,7 @@ print response.headers ``` ## Retrieve all global suppressions -**This endpoint allows you to retrieve a list of all email address that are globally suppressed.** +**This endpoint allows you to retrieve a list of all email address that is globally suppressed.** A global suppression (or global unsubscribe) is an email address of a recipient who does not want to receive any of your messages. A globally suppressed recipient will be removed from any email you send. For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/global_unsubscribes.html). @@ -8242,7 +3621,7 @@ print response.headers **This endpoint allows you to create a new version of a template.** -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across all templates. For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). @@ -8269,7 +3648,7 @@ print response.headers **This endpoint allows you to edit a version of one of your transactional templates.** -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across all templates. For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). @@ -8301,7 +3680,7 @@ print response.headers **This endpoint allows you to retrieve a specific version of a template.** -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across all templates. For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). @@ -8326,7 +3705,7 @@ print response.headers **This endpoint allows you to delete one of your transactional template versions.** -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across all templates. For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). @@ -8351,7 +3730,7 @@ print response.headers **This endpoint allows you to activate a version of one of your templates.** -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across all templates. For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). @@ -8721,7 +4100,7 @@ print response.headers If the maximum number of cancellations/pauses are added, HTTP 400 will be returned. -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header. Scheduled sends canceled less than 10 minutes before the scheduled time are not guaranteed to be canceled. ### POST /user/scheduled_sends @@ -8740,7 +4119,7 @@ print response.headers **This endpoint allows you to retrieve all cancel/paused scheduled send information.** -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header. Scheduled sends canceled less than 10 minutes before the scheduled time are not guaranteed to be canceled. ### GET /user/scheduled_sends @@ -8755,7 +4134,7 @@ print response.headers **This endpoint allows you to update the status of a scheduled send for the given `batch_id`.** -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header. Scheduled sends canceled less than 10 minutes before the scheduled time are not guaranteed to be canceled. ### PATCH /user/scheduled_sends/{batch_id} @@ -8774,7 +4153,7 @@ print response.headers **This endpoint allows you to retrieve the cancel/paused scheduled send information for a specific `batch_id`.** -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header. Scheduled sends canceled less than 10 minutes before the scheduled time are not guaranteed to be canceled. ### GET /user/scheduled_sends/{batch_id} @@ -8790,7 +4169,7 @@ print response.headers **This endpoint allows you to delete the cancellation/pause of a scheduled send.** -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header. Scheduled sends canceled less than 10 minutes before the scheduled time are not guaranteed to be canceled. ### DELETE /user/scheduled_sends/{batch_id} From 6551f9a303c9e0c1ec0b2a40acd78132d7211192 Mon Sep 17 00:00:00 2001 From: Chandler Weiner Date: Wed, 17 Oct 2018 16:27:57 -0400 Subject: [PATCH 147/462] Remove "Whitelist" and replace with other terms Per the [SendGrid Docs](https://github.com/sendgrid/docs/blob/develop/content/docs/glossary/whitelabel.md), the term "whitelabel" is sunset and needs to be replaced with other words, even though the API still calls it Whitelabeling. --- USAGE.md | 2071 +++++++++++++++++++++++++++--------------------------- 1 file changed, 1037 insertions(+), 1034 deletions(-) diff --git a/USAGE.md b/USAGE.md index 09b78609a..b14656ba8 100644 --- a/USAGE.md +++ b/USAGE.md @@ -30,13 +30,13 @@ sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) * [PARTNER SETTINGS](#partner-settings) * [SCOPES](#scopes) * [SENDERS](#senders) +* [SENDER Authorization](#sender-authorization) * [STATS](#stats) * [SUBUSERS](#subusers) * [SUPPRESSION](#suppression) * [TEMPLATES](#templates) * [TRACKING SETTINGS](#tracking-settings) * [USER](#user) -* [WHITELABEL](#whitelabel) @@ -2852,2184 +2852,2187 @@ print response.status_code print response.body print response.headers ``` - -# STATS - -## Retrieve global email statistics - -**This endpoint allows you to retrieve all of your global email statistics between a given date range.** - -Parent accounts will see aggregated stats for their account and all subuser accounts. Subuser accounts will only see their own stats. + +# Sender Authentication -### GET /stats - - -```python -params = {'aggregated_by': 'day', 'limit': 1, 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} -response = sg.client.stats.get(query_params=params) -print response.status_code -print response.body -print response.headers -``` - -# SUBUSERS +## Create a new domain authentication. -## Create Subuser +**This endpoint allows you to create an authenticated domain.** -This endpoint allows you to retrieve a list of all of your subusers. You can choose to retrieve specific subusers as well as limit the results that come back from the API. +If you are creating a domain authentication that you would like a subuser to use, you have two options: +1. Use the "username" parameter. This allows you to create a domain authentication on behalf of your subuser. This means the subuser is able to see and modify the created domain authentication. +2. Use the Association workflow (see Associate Domain section). This allows you to assign a Domain Authentication created by the parent to a subuser. This means the subuser will default to the assigned authenticated domain, but will not be able to see or modify that Authentication. However, if the subuser creates their own Domain Authentication it will overwrite the assigned Domain Authentication. -For more information about Subusers: +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) -* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/) -### POST /subusers +### POST /whitelabel/domains ```python data = { - "email": "John@example.com", + "automatic_security": False, + "custom_spf": True, + "default": True, + "domain": "example.com", "ips": [ - "1.1.1.1", - "2.2.2.2" + "192.168.1.1", + "192.168.1.2" ], - "password": "johns_password", - "username": "John@example.com" + "subdomain": "news", + "username": "john@example.com" } -response = sg.client.subusers.post(request_body=data) +response = sg.client.whitelabel.domains.post(request_body=data) print response.status_code print response.body print response.headers ``` -## List all Subusers - -This endpoint allows you to retrieve a list of all of your subusers. You can choose to retrieve specific subusers as well as limit the results that come back from the API. - -For more information about Subusers: - -* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) -* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) - -### GET /subusers +## List all Domain Authentications. +**This endpoint allows you to retrieve a list of all domain authentications you have created.** -```python -params = {'username': 'test_string', 'limit': 1, 'offset': 1} -response = sg.client.subusers.get(query_params=params) -print response.status_code -print response.body -print response.headers -``` -## Retrieve Subuser Reputations +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -Subuser sender reputations give a good idea how well a sender is doing with regards to how recipients and recipient servers react to the mail that is being received. When a bounce, spam report, or other negative action happens on a sent email, it will effect your sender rating. +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/) -This endpoint allows you to request the reputations for your subusers. -### GET /subusers/reputations +### GET /whitelabel/domains ```python -params = {'usernames': 'test_string'} -response = sg.client.subusers.reputations.get(query_params=params) +params = {'username': 'test_string', 'domain': 'test_string', 'exclude_subusers': 'true', 'limit': 1, 'offset': 1} +response = sg.client.whitelabel.domains.get(query_params=params) print response.status_code print response.body print response.headers ``` -## Retrieve email statistics for your subusers. +## Get the default domain authentication. -**This endpoint allows you to retrieve the email statistics for the given subusers.** +**This endpoint allows you to retrieve the default authentication for a domain.** -You may retrieve statistics for up to 10 different subusers by including an additional _subusers_ parameter for each additional subuser. +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/) -For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| domain | string |The domain to find a default domain whitelabel for. | -### GET /subusers/stats +### GET /whitelabel/domains/default ```python -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01', 'subusers': 'test_string'} -response = sg.client.subusers.stats.get(query_params=params) +response = sg.client.whitelabel.domains.default.get() print response.status_code print response.body print response.headers ``` -## Retrieve monthly stats for all subusers +## List the domain authentications associated with the given user. -**This endpoint allows you to retrieve the monthly email statistics for all subusers over the given date range.** +**This endpoint allows you to retrieve all of the domain authentications that have been assigned to a specific subuser.** -While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats for your subusers. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -When using the `sort_by_metric` to sort your stats by a specific metric, you can not sort by the following metrics: -`bounce_drops`, `deferred`, `invalid_emails`, `processed`, `spam_report_drops`, `spam_reports`, or `unsubscribe_drops`. +Domain authentications can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's authenticated domains. To associate a domain authentication with a subuser, the parent account must first create the authentication and validate it. The parent may then associate the domain authentication via the subuser management tools. -For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/) -### GET /subusers/stats/monthly +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| username | string | Username of the subuser to find associated whitelabels for. | + +### GET /whitelabel/domains/subuser ```python -params = {'subuser': 'test_string', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'date': 'test_string', 'sort_by_direction': 'asc'} -response = sg.client.subusers.stats.monthly.get(query_params=params) +response = sg.client.whitelabel.domains.subuser.get() print response.status_code print response.body print response.headers ``` -## Retrieve the totals for each email statistic metric for all subusers. +## Disassociate a domain authentication from a given user. -**This endpoint allows you to retrieve the total sums of each email statistic metric for all subusers over the given date range.** +**This endpoint allows you to disassociate a specific authenticated domain from a subuser.** +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. +Domain authentications can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's authenticated domains. To associate a domain authentication with a subuser, the parent account must first create the authentication and validate it. The parent may then associate the domain authentication via the subuser management tools. -For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/) -### GET /subusers/stats/sums +## URI Parameters +| URI Parameter | Type | Required? | Description | +|---|---|---|---| +| username | string | required | Username for the subuser to find associated whitelabels for. | + +### DELETE /whitelabel/domains/subuser ```python -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} -response = sg.client.subusers.stats.sums.get(query_params=params) +response = sg.client.whitelabel.domains.subuser.delete() print response.status_code print response.body print response.headers ``` -## Enable/disable a subuser +## Update a domain authentication. -This endpoint allows you to enable or disable a subuser. +**This endpoint allows you to update the settings for a domain authentication.** -For more information about Subusers: +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) -* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/) -### PATCH /subusers/{subuser_name} +### PATCH /whitelabel/domains/{domain_id} ```python data = { - "disabled": False + "custom_spf": True, + "default": False } -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).patch(request_body=data) +domain_id = "test_url_param" +response = sg.client.whitelabel.domains._(domain_id).patch(request_body=data) print response.status_code print response.body print response.headers ``` -## Delete a subuser +## Retrieve a domain authentication. -This endpoint allows you to delete a subuser. This is a permanent action, once you delete a subuser it cannot be retrieved. +**This endpoint allows you to retrieve a specific domain authentication.** -For more information about Subusers: +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) -* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/) -### DELETE /subusers/{subuser_name} + +### GET /whitelabel/domains/{domain_id} ```python -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).delete() +domain_id = "test_url_param" +response = sg.client.whitelabel.domains._(domain_id).get() print response.status_code print response.body print response.headers ``` -## Update IPs assigned to a subuser +## Delete a domain authentication. -Each subuser should be assigned to an IP address, from which all of this subuser's mail will be sent. Often, this is the same IP as the parent account, but each subuser can have their own, or multiple, IP addresses as well. +**This endpoint allows you to delete a domain authentication.** -More information: +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -* [How to request more IPs](https://sendgrid.com/docs/Classroom/Basics/Account/adding_an_additional_dedicated_ip_to_your_account.html) -* [IPs can be whitelabeled](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/ips.html) +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/) -### PUT /subusers/{subuser_name}/ips +### DELETE /whitelabel/domains/{domain_id} ```python -data = [ - "127.0.0.1" -] -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).ips.put(request_body=data) +domain_id = "test_url_param" +response = sg.client.whitelabel.domains._(domain_id).delete() print response.status_code print response.body print response.headers ``` -## Update Monitor Settings for a subuser +## Associate a domain authentication with a given user. -Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. +**This endpoint allows you to associate a specific domain authentication with a subuser.** -### PUT /subusers/{subuser_name}/monitor +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +Domain authentications can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's authenticated domains. To associate a domain authentication with a subuser, the parent account must first create the authentication and validate it. The parent may then associate the domain authentication via the subuser management tools. + +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/) + +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| domain_id | integer | ID of the domain whitelabel to associate with the subuser. | + +### POST /whitelabel/domains/{domain_id}/subuser ```python data = { - "email": "example@example.com", - "frequency": 500 + "username": "jane@example.com" } -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.put(request_body=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 ``` -## Create monitor settings +## Add an IP to an authenticated domain. -Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. +**This endpoint allows you to add an IP address to an authenticated domain.** -### POST /subusers/{subuser_name}/monitor +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/) + +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| id | integer | ID of the domain to which you are adding an IP | + +### POST /whitelabel/domains/{id}/ips ```python data = { - "email": "example@example.com", - "frequency": 50000 + "ip": "192.168.0.1" } -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.post(request_body=data) +id = "test_url_param" +response = sg.client.whitelabel.domains._(id).ips.post(request_body=data) print response.status_code print response.body print response.headers ``` -## Retrieve monitor settings for a subuser +## Remove an IP from an authenticated domain. -Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. +**This endpoint allows you to remove a domain's IP address from an authenticated domain.** -### GET /subusers/{subuser_name}/monitor +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/)) + +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| id | integer | ID of the domain whitelabel to delete the IP from. | +| ip | string | IP to remove from the domain whitelabel. | + +### DELETE /whitelabel/domains/{id}/ips/{ip} ```python -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.get() +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 ``` -## Delete monitor settings +## Validate a domain authentication. -Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. +**This endpoint allows you to validate a domain authentication. If it fails, it will return an error message describing why the domain authentication could not be validated.** -### DELETE /subusers/{subuser_name}/monitor +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/) + + +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| id | integer |ID of the domain whitelabel to validate. | + +### POST /whitelabel/domains/{id}/validate ```python -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.delete() +id = "test_url_param" +response = sg.client.whitelabel.domains._(id).validate.post() print response.status_code print response.body print response.headers ``` -## Retrieve the monthly email statistics for a single subuser +## Setup reverse DNS -**This endpoint allows you to retrieve the monthly email statistics for a specific subuser.** +**This endpoint allows you to setup reverse DNS.** -While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats for your subusers. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. +When setting up reverse DNS, you should use the same subdomain that you used when you created a domain authentication. -When using the `sort_by_metric` to sort your stats by a specific metric, you can not sort by the following metrics: -`bounce_drops`, `deferred`, `invalid_emails`, `processed`, `spam_report_drops`, `spam_reports`, or `unsubscribe_drops`. +Reverse DNS consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. -For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-reverse-dns/). -### GET /subusers/{subuser_name}/stats/monthly +### POST /whitelabel/ips ```python -params = {'date': 'test_string', 'sort_by_direction': 'asc', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1} -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).stats.monthly.get(query_params=params) +data = { + "domain": "example.com", + "ip": "192.168.1.1", + "subdomain": "email" +} +response = sg.client.whitelabel.ips.post(request_body=data) print response.status_code print response.body print response.headers ``` - -# SUPPRESSION +## Retrieve all reverse DNS records -## Retrieve all blocks +**This endpoint allows you to retrieve all of the reverse DNS that have been created by this account.** -**This endpoint allows you to retrieve a list of all email addresses that are currently on your blocks list.** +You may include a search key by using the "ip" parameter. This enables you to perform a prefix search for a given IP segment (e.g. "192."). -[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. +Reverse DNS consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-reverse-dns/). -### GET /suppression/blocks +### GET /whitelabel/ips ```python -params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} -response = sg.client.suppression.blocks.get(query_params=params) +params = {'ip': 'test_string', 'limit': 1, 'offset': 1} +response = sg.client.whitelabel.ips.get(query_params=params) print response.status_code print response.body print response.headers ``` -## Delete blocks +## Retrieve an reverse DNS setting -**This endpoint allows you to delete all email addresses on your blocks list.** +**This endpoint allows you to retrieve an reverse DNS setting.** -There are two options for deleting blocked emails: +Reverse DNS consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. -1. You can delete all blocked emails by setting `delete_all` to true in the request body. -2. You can delete some blocked emails by specifying the email addresses in an array in the request body. - -[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. - -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-reverse-dns/). -### DELETE /suppression/blocks +### GET /whitelabel/ips/{id} ```python -data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] -} -response = sg.client.suppression.blocks.delete(request_body=data) +id = "test_url_param" +response = sg.client.whitelabel.ips._(id).get() print response.status_code print response.body print response.headers ``` -## Retrieve a specific block +## Delete an reverse DNS record -**This endpoint allows you to retrieve a specific email address from your blocks list.** +**This endpoint allows you to delete an reverse DNS record.** -[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. +Reverse DNS consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-reverse-dns/). -### GET /suppression/blocks/{email} +### DELETE /whitelabel/ips/{id} ```python -email = "test_url_param" -response = sg.client.suppression.blocks._(email).get() +id = "test_url_param" +response = sg.client.whitelabel.ips._(id).delete() print response.status_code print response.body print response.headers ``` -## Delete a specific block +## Validate an reverse DNS record -**This endpoint allows you to delete a specific email address from your blocks list.** +**This endpoint allows you to validate an reverse DNS record.** -[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. +Reverse DNS consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-reverse-dns/). -### DELETE /suppression/blocks/{email} +### POST /whitelabel/ips/{id}/validate ```python -email = "test_url_param" -response = sg.client.suppression.blocks._(email).delete() +id = "test_url_param" +response = sg.client.whitelabel.ips._(id).validate.post() print response.status_code print response.body print response.headers ``` -## Retrieve all bounces - -**This endpoint allows you to retrieve all of your bounces.** +## Create Link Branding -Bounces are messages that are returned to the server that sent it. +**This endpoint allows you to create a new branded link.** -For more information see: +Link branding allows all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information -* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-link-branding/). -### GET /suppression/bounces +### POST /whitelabel/links ```python -params = {'start_time': 1, 'end_time': 1} -response = sg.client.suppression.bounces.get(query_params=params) +data = { + "default": True, + "domain": "example.com", + "subdomain": "mail" +} +params = {'limit': 1, 'offset': 1} +response = sg.client.whitelabel.links.post(request_body=data, query_params=params) print response.status_code print response.body print response.headers ``` -## Delete bounces +## Retrieve all link brands -**This endpoint allows you to delete all of your bounces. You can also use this endpoint to remove a specific email address from your bounce list.** - -Bounces are messages that are returned to the server that sent it. - -For more information see: +**This endpoint allows you to retrieve all branded links.** -* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information -* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) -* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) +Link branding allows all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -Note: the `delete_all` and `emails` parameters should be used independently of each other as they have different purposes. +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-link-branding/). -### DELETE /suppression/bounces +### GET /whitelabel/links ```python -data = { - "delete_all": True, - "emails": [ - "example@example.com", - "example2@example.com" - ] -} -response = sg.client.suppression.bounces.delete(request_body=data) +params = {'limit': 1} +response = sg.client.whitelabel.links.get(query_params=params) print response.status_code print response.body print response.headers ``` -## Retrieve a Bounce +## Retrieve a Default Link Branding -**This endpoint allows you to retrieve a specific bounce for a given email address.** +**This endpoint allows you to retrieve the default link branding.** -Bounces are messages that are returned to the server that sent it. +Default link branding is the actual link whitelabel to be used when sending messages. If there are multiple branded links, the default is determined by the following order: +
    +
  • Validated link branding marked as "default"
  • +
  • Legacy link whitelabels (migrated from the whitelabel wizard)
  • +
  • Default SendGrid link whitelabel (i.e. 100.ct.sendgrid.net)
  • +
-For more information see: +Link branding allows all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information -* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) -* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-link-branding/). -### GET /suppression/bounces/{email} +### GET /whitelabel/links/default ```python -email = "test_url_param" -response = sg.client.suppression.bounces._(email).get() +params = {'domain': 'test_string'} +response = sg.client.whitelabel.links.default.get(query_params=params) print response.status_code print response.body print response.headers ``` -## Delete a bounce +## Retrieve Associated Link Branding -**This endpoint allows you to remove an email address from your bounce list.** +**This endpoint allows you to retrieve the associated link branding for a subuser.** -Bounces are messages that are returned to the server that sent it. This endpoint allows you to delete a single email address from your bounce list. +Link branding can be associated with subusers from the parent account. This functionality allows +subusers to send mail using their parent's link branding. To associate a link branding, the parent account +must first create a branded link and validate it. The parent may then associate that branded link with a subuser via the API or the Subuser Management page in the user interface. -For more information see: +Link branding allows all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information -* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) -* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-link-branding/). -### DELETE /suppression/bounces/{email} +### GET /whitelabel/links/subuser ```python -params = {'email_address': 'example@example.com'} -email = "test_url_param" -response = sg.client.suppression.bounces._(email).delete(query_params=params) +params = {'username': 'test_string'} +response = sg.client.whitelabel.links.subuser.get(query_params=params) print response.status_code print response.body print response.headers ``` -## Retrieve all invalid emails +## Disassociate a Link Branding -**This endpoint allows you to retrieve a list of all invalid email addresses.** +**This endpoint allows you to disassociate a link branding from a subuser.** -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. +Link branding can be associated with subusers from the parent account. This functionality allows +subusers to send mail using their parent's link branding. To associate a link branding, the parent account +must first create a branded link and validate it. The parent may then associate that branded link with a subuser via the API or the Subuser Management page in the user interface. -Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. +Link branding allows all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-link-branding/). -### GET /suppression/invalid_emails +### DELETE /whitelabel/links/subuser ```python -params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} -response = sg.client.suppression.invalid_emails.get(query_params=params) +params = {'username': 'test_string'} +response = sg.client.whitelabel.links.subuser.delete(query_params=params) print response.status_code print response.body print response.headers ``` -## Delete invalid emails +## Update a Link Branding -**This endpoint allows you to remove email addresses from your invalid email address list.** - -There are two options for deleting invalid email addresses: - -1) You can delete all invalid email addresses by setting `delete_all` to true in the request body. -2) You can delete some invalid email addresses by specifying certain addresses in an array in the request body. - -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. +**This endpoint allows you to update a specific link branding. You can use this endpoint to change a link branding's default status.** -Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. +Link branding allows all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-link-branding/). -### DELETE /suppression/invalid_emails +### PATCH /whitelabel/links/{id} ```python data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] + "default": True } -response = sg.client.suppression.invalid_emails.delete(request_body=data) +id = "test_url_param" +response = sg.client.whitelabel.links._(id).patch(request_body=data) print response.status_code print response.body print response.headers ``` -## Retrieve a specific invalid email - -**This endpoint allows you to retrieve a specific invalid email addresses.** +## Retrieve a Link Branding -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. +**This endpoint allows you to retrieve a specific link branding.** -Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. +Link branding allows all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-link-branding/). -### GET /suppression/invalid_emails/{email} +### GET /whitelabel/links/{id} ```python -email = "test_url_param" -response = sg.client.suppression.invalid_emails._(email).get() +id = "test_url_param" +response = sg.client.whitelabel.links._(id).get() print response.status_code print response.body print response.headers ``` -## Delete a specific invalid email - -**This endpoint allows you to remove a specific email address from the invalid email address list.** +## Delete a Link Branding -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. +**This endpoint allows you to delete a link branding.** -Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. +Link branding allows all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-link-branding/). -### DELETE /suppression/invalid_emails/{email} +### DELETE /whitelabel/links/{id} ```python -email = "test_url_param" -response = sg.client.suppression.invalid_emails._(email).delete() +id = "test_url_param" +response = sg.client.whitelabel.links._(id).delete() print response.status_code print response.body print response.headers ``` -## Retrieve a specific spam report +## Validate a Link Branding -**This endpoint allows you to retrieve a specific spam report.** +**This endpoint allows you to validate a link branding.** -[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to SendGrid. +Link branding allows all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-link-branding/). -### GET /suppression/spam_report/{email} +### POST /whitelabel/links/{id}/validate ```python -email = "test_url_param" -response = sg.client.suppression.spam_report._(email).get() +id = "test_url_param" +response = sg.client.whitelabel.links._(id).validate.post() print response.status_code print response.body print response.headers ``` -## Delete a specific spam report +## Associate a Link Branding -**This endpoint allows you to delete a specific spam report.** +**This endpoint allows you to associate a link branding with a subuser account.** -[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to SendGrid. +Link branding can be associated with subusers from the parent account. This functionality allows +subusers to send mail using their parent's link branding. To associate a link branding, the parent account +must first create a branded link and validate it. The parent may then associate that branded link with a subuser via the API or the Subuser Management page in the user interface. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). +Link branding allows all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -### DELETE /suppression/spam_report/{email} +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-link-branding/). + +### POST /whitelabel/links/{link_id}/subuser ```python -email = "test_url_param" -response = sg.client.suppression.spam_report._(email).delete() +data = { + "username": "jane@example.com" +} +link_id = "test_url_param" +response = sg.client.whitelabel.links._(link_id).subuser.post(request_body=data) print response.status_code print response.body print response.headers ``` -## Retrieve all spam reports -**This endpoint allows you to retrieve all spam reports.** -[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to SendGrid. + +# STATS -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). +## Retrieve global email statistics -### GET /suppression/spam_reports +**This endpoint allows you to retrieve all of your global email statistics between a given date range.** + +Parent accounts will see aggregated stats for their account and all subuser accounts. Subuser accounts will only see their own stats. + +### GET /stats ```python -params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} -response = sg.client.suppression.spam_reports.get(query_params=params) +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 ``` -## Delete spam reports - -**This endpoint allows you to delete your spam reports.** + +# SUBUSERS -There are two options for deleting spam reports: +## Create Subuser -1) You can delete all spam reports by setting "delete_all" to true in the request body. -2) You can delete some spam reports by specifying the email addresses in an array in the request body. +This endpoint allows you to retrieve a list of all of your subusers. You can choose to retrieve specific subusers as well as limit the results that come back from the API. -[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to SendGrid. +For more information about Subusers: -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). +* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) +* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) -### DELETE /suppression/spam_reports +### POST /subusers ```python data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] + "email": "John@example.com", + "ips": [ + "1.1.1.1", + "2.2.2.2" + ], + "password": "johns_password", + "username": "John@example.com" } -response = sg.client.suppression.spam_reports.delete(request_body=data) +response = sg.client.subusers.post(request_body=data) print response.status_code print response.body print response.headers ``` -## Retrieve all global suppressions +## List all Subusers -**This endpoint allows you to retrieve a list of all email address that are globally suppressed.** +This endpoint allows you to retrieve a list of all of your subusers. You can choose to retrieve specific subusers as well as limit the results that come back from the API. -A global suppression (or global unsubscribe) is an email address of a recipient who does not want to receive any of your messages. A globally suppressed recipient will be removed from any email you send. For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/global_unsubscribes.html). +For more information about Subusers: -### GET /suppression/unsubscribes +* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) +* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) + +### GET /subusers ```python -params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} -response = sg.client.suppression.unsubscribes.get(query_params=params) +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 ``` - -# TEMPLATES +## Retrieve Subuser Reputations -## Create a transactional template. - -**This endpoint allows you to create a transactional template.** - -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. +Subuser sender reputations give a good idea how well a sender is doing with regards to how recipients and recipient servers react to the mail that is being received. When a bounce, spam report, or other negative action happens on a sent email, it will effect your sender rating. -Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +This endpoint allows you to request the reputations for your subusers. -### POST /templates +### GET /subusers/reputations ```python -data = { - "name": "example_name" -} -response = sg.client.templates.post(request_body=data) +params = {'usernames': 'test_string'} +response = sg.client.subusers.reputations.get(query_params=params) print response.status_code print response.body print response.headers ``` -## Retrieve all transactional templates. +## Retrieve email statistics for your subusers. -**This endpoint allows you to retrieve all transactional templates.** +**This endpoint allows you to retrieve the email statistics for the given subusers.** -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. +You may retrieve statistics for up to 10 different subusers by including an additional _subusers_ parameter for each additional subuser. -Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. -### GET /templates +For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). + +### GET /subusers/stats ```python -response = sg.client.templates.get() +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 ``` -## Edit a transactional template. +## Retrieve monthly stats for all subusers -**This endpoint allows you to edit a transactional template.** +**This endpoint allows you to retrieve the monthly email statistics for all subusers over the given date range.** -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. +While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats for your subusers. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. -Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +When using the `sort_by_metric` to sort your stats by a specific metric, you can not sort by the following metrics: +`bounce_drops`, `deferred`, `invalid_emails`, `processed`, `spam_report_drops`, `spam_reports`, or `unsubscribe_drops`. +For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). -### PATCH /templates/{template_id} +### GET /subusers/stats/monthly ```python -data = { - "name": "new_example_name" -} -template_id = "test_url_param" -response = sg.client.templates._(template_id).patch(request_body=data) +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 ``` -## Retrieve a single transactional template. +## Retrieve the totals for each email statistic metric for all subusers. -**This endpoint allows you to retrieve a single transactional template.** +**This endpoint allows you to retrieve the total sums of each email statistic metric for all subusers over the given date range.** -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. -Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. +For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). -### GET /templates/{template_id} +### GET /subusers/stats/sums ```python -template_id = "test_url_param" -response = sg.client.templates._(template_id).get() +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 ``` -## Delete a template. - -**This endpoint allows you to delete a transactional template.** +## Enable/disable a subuser -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. +This endpoint allows you to enable or disable a subuser. -Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +For more information about Subusers: +* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) +* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) -### DELETE /templates/{template_id} +### PATCH /subusers/{subuser_name} ```python -template_id = "test_url_param" -response = sg.client.templates._(template_id).delete() +data = { + "disabled": False +} +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).patch(request_body=data) print response.status_code print response.body print response.headers ``` -## Create a new transactional template version. +## Delete a subuser -**This endpoint allows you to create a new version of a template.** +This endpoint allows you to delete a subuser. This is a permanent action, once you delete a subuser it cannot be retrieved. -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +For more information about Subusers: -For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) +* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) +### DELETE /subusers/{subuser_name} -### POST /templates/{template_id}/versions + +```python +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).delete() +print response.status_code +print response.body +print response.headers +``` +## Update IPs assigned to a subuser + +Each subuser should be assigned to an IP address, from which all of this subuser's mail will be sent. Often, this is the same IP as the parent account, but each subuser can have their own, or multiple, IP addresses as well. + +More information: + +* [How to request more IPs](https://sendgrid.com/docs/Classroom/Basics/Account/adding_an_additional_dedicated_ip_to_your_account.html) +* [IPs can be whitelabeled](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/ips.html) + +### PUT /subusers/{subuser_name}/ips ```python -data = { - "active": 1, - "html_content": "<%body%>", - "name": "example_version_name", - "plain_content": "<%body%>", - "subject": "<%subject%>", - "template_id": "ddb96bbc-9b92-425e-8979-99464621b543" -} -template_id = "test_url_param" -response = sg.client.templates._(template_id).versions.post(request_body=data) +data = [ + "127.0.0.1" +] +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).ips.put(request_body=data) print response.status_code print response.body print response.headers ``` -## Edit a transactional template version. +## Update Monitor Settings for a subuser -**This endpoint allows you to edit a version of one of your transactional templates.** +Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +### PUT /subusers/{subuser_name}/monitor -For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| template_id | string | The ID of the original template | -| version_id | string | The ID of the template version | +```python +data = { + "email": "example@example.com", + "frequency": 500 +} +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.put(request_body=data) +print response.status_code +print response.body +print response.headers +``` +## Create monitor settings -### PATCH /templates/{template_id}/versions/{version_id} +Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. + +### POST /subusers/{subuser_name}/monitor ```python data = { - "active": 1, - "html_content": "<%body%>", - "name": "updated_example_name", - "plain_content": "<%body%>", - "subject": "<%subject%>" + "email": "example@example.com", + "frequency": 50000 } -template_id = "test_url_param" -version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).patch(request_body=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 ``` -## Retrieve a specific transactional template version. +## Retrieve monitor settings for a subuser -**This endpoint allows you to retrieve a specific version of a template.** +Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +### GET /subusers/{subuser_name}/monitor -For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| template_id | string | The ID of the original template | -| version_id | string | The ID of the template version | +```python +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.get() +print response.status_code +print response.body +print response.headers +``` +## Delete monitor settings -### GET /templates/{template_id}/versions/{version_id} +Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. + +### DELETE /subusers/{subuser_name}/monitor ```python -template_id = "test_url_param" -version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).get() +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.delete() print response.status_code print response.body print response.headers ``` -## Delete a transactional template version. +## Retrieve the monthly email statistics for a single subuser -**This endpoint allows you to delete one of your transactional template versions.** +**This endpoint allows you to retrieve the monthly email statistics for a specific subuser.** -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats for your subusers. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. -For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +When using the `sort_by_metric` to sort your stats by a specific metric, you can not sort by the following metrics: +`bounce_drops`, `deferred`, `invalid_emails`, `processed`, `spam_report_drops`, `spam_reports`, or `unsubscribe_drops`. -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| template_id | string | The ID of the original template | -| version_id | string | The ID of the template version | +For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). -### DELETE /templates/{template_id}/versions/{version_id} +### GET /subusers/{subuser_name}/stats/monthly ```python -template_id = "test_url_param" -version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).delete() +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 ``` -## Activate a transactional template version. - -**This endpoint allows you to activate a version of one of your templates.** + +# SUPPRESSION -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +## Retrieve all blocks +**This endpoint allows you to retrieve a list of all email addresses that are currently on your blocks list.** -For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| template_id | string | The ID of the original template | -| version_id | string | The ID of the template version | +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). -### POST /templates/{template_id}/versions/{version_id}/activate +### GET /suppression/blocks ```python -template_id = "test_url_param" -version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).activate.post() +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 ``` - -# TRACKING SETTINGS +## Delete blocks -## Retrieve Tracking Settings +**This endpoint allows you to delete all email addresses on your blocks list.** -**This endpoint allows you to retrieve a list of all tracking settings that you can enable on your account.** +There are two options for deleting blocked emails: -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +1. You can delete all blocked emails by setting `delete_all` to true in the request body. +2. You can delete some blocked emails by specifying the email addresses in an array in the request body. -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. -### GET /tracking_settings +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). + +### DELETE /suppression/blocks ```python -params = {'limit': 1, 'offset': 1} -response = sg.client.tracking_settings.get(query_params=params) +data = { + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] +} +response = sg.client.suppression.blocks.delete(request_body=data) print response.status_code print response.body print response.headers ``` -## Update Click Tracking Settings +## Retrieve a specific block -**This endpoint allows you to change your current click tracking setting. You can enable, or disable, click tracking using this endpoint.** +**This endpoint allows you to retrieve a specific email address from your blocks list.** -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). -### PATCH /tracking_settings/click +### GET /suppression/blocks/{email} ```python -data = { - "enabled": True -} -response = sg.client.tracking_settings.click.patch(request_body=data) +email = "test_url_param" +response = sg.client.suppression.blocks._(email).get() print response.status_code print response.body print response.headers ``` -## Retrieve Click Track Settings +## Delete a specific block -**This endpoint allows you to retrieve your current click tracking setting.** +**This endpoint allows you to delete a specific email address from your blocks list.** -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). -### GET /tracking_settings/click +### DELETE /suppression/blocks/{email} ```python -response = sg.client.tracking_settings.click.get() +email = "test_url_param" +response = sg.client.suppression.blocks._(email).delete() print response.status_code print response.body print response.headers ``` -## Update Google Analytics Settings - -**This endpoint allows you to update your current setting for Google Analytics.** +## Retrieve all bounces -For more information about using Google Analytics, please refer to [Googles URL Builder](https://support.google.com/analytics/answer/1033867?hl=en) and their article on ["Best Practices for Campaign Building"](https://support.google.com/analytics/answer/1037445). +**This endpoint allows you to retrieve all of your bounces.** -We default the settings to Googles recommendations. For more information, see [Google Analytics Demystified](https://sendgrid.com/docs/Classroom/Track/Collecting_Data/google_analytics_demystified_ga_statistics_vs_sg_statistics.html). +Bounces are messages that are returned to the server that sent it. -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +For more information see: -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information +* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) -### PATCH /tracking_settings/google_analytics +### GET /suppression/bounces ```python -data = { - "enabled": True, - "utm_campaign": "website", - "utm_content": "", - "utm_medium": "email", - "utm_source": "sendgrid.com", - "utm_term": "" -} -response = sg.client.tracking_settings.google_analytics.patch(request_body=data) +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 ``` -## Retrieve Google Analytics Settings +## Delete bounces -**This endpoint allows you to retrieve your current setting for Google Analytics.** +**This endpoint allows you to delete all of your bounces. You can also use this endpoint to remove a specific email address from your bounce list.** -For more information about using Google Analytics, please refer to [Googles URL Builder](https://support.google.com/analytics/answer/1033867?hl=en) and their article on ["Best Practices for Campaign Building"](https://support.google.com/analytics/answer/1037445). +Bounces are messages that are returned to the server that sent it. -We default the settings to Googles recommendations. For more information, see [Google Analytics Demystified](https://sendgrid.com/docs/Classroom/Track/Collecting_Data/google_analytics_demystified_ga_statistics_vs_sg_statistics.html). +For more information see: -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information +* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) +* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +Note: the `delete_all` and `emails` parameters should be used independently of each other as they have different purposes. -### GET /tracking_settings/google_analytics +### DELETE /suppression/bounces ```python -response = sg.client.tracking_settings.google_analytics.get() +data = { + "delete_all": True, + "emails": [ + "example@example.com", + "example2@example.com" + ] +} +response = sg.client.suppression.bounces.delete(request_body=data) print response.status_code print response.body print response.headers ``` -## Update Open Tracking Settings +## Retrieve a Bounce -**This endpoint allows you to update your current settings for open tracking.** +**This endpoint allows you to retrieve a specific bounce for a given email address.** -Open Tracking adds an invisible image at the end of the email which can track email opens. If the email recipient has images enabled on their email client, a request to SendGrids server for the invisible image is executed and an open event is logged. These events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook. +Bounces are messages that are returned to the server that sent it. -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +For more information see: -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information +* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) +* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) -### PATCH /tracking_settings/open +### GET /suppression/bounces/{email} ```python -data = { - "enabled": True -} -response = sg.client.tracking_settings.open.patch(request_body=data) +email = "test_url_param" +response = sg.client.suppression.bounces._(email).get() print response.status_code print response.body print response.headers ``` -## Get Open Tracking Settings +## Delete a bounce -**This endpoint allows you to retrieve your current settings for open tracking.** +**This endpoint allows you to remove an email address from your bounce list.** -Open Tracking adds an invisible image at the end of the email which can track email opens. If the email recipient has images enabled on their email client, a request to SendGrids server for the invisible image is executed and an open event is logged. These events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook. +Bounces are messages that are returned to the server that sent it. This endpoint allows you to delete a single email address from your bounce list. -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +For more information see: -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information +* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) +* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) -### GET /tracking_settings/open +### DELETE /suppression/bounces/{email} ```python -response = sg.client.tracking_settings.open.get() +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 ``` -## Update Subscription Tracking Settings +## Retrieve all invalid emails -**This endpoint allows you to update your current settings for subscription tracking.** +**This endpoint allows you to retrieve a list of all invalid email addresses.** -Subscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails. +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). -### PATCH /tracking_settings/subscription +### GET /suppression/invalid_emails ```python -data = { - "enabled": True, - "html_content": "html content", - "landing": "landing page html", - "plain_content": "text content", - "replace": "replacement tag", - "url": "url" -} -response = sg.client.tracking_settings.subscription.patch(request_body=data) +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 ``` -## Retrieve Subscription Tracking Settings +## Delete invalid emails -**This endpoint allows you to retrieve your current settings for subscription tracking.** +**This endpoint allows you to remove email addresses from your invalid email address list.** -Subscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails. +There are two options for deleting invalid email addresses: -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +1) You can delete all invalid email addresses by setting `delete_all` to true in the request body. +2) You can delete some invalid email addresses by specifying certain addresses in an array in the request body. -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. -### GET /tracking_settings/subscription +Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. + +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). + +### DELETE /suppression/invalid_emails ```python -response = sg.client.tracking_settings.subscription.get() +data = { + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] +} +response = sg.client.suppression.invalid_emails.delete(request_body=data) print response.status_code print response.body print response.headers ``` - -# USER - -## Get a user's account information. - -**This endpoint allows you to retrieve your user account details.** +## Retrieve a specific invalid email -Your user's account information includes the user's account type and reputation. +**This endpoint allows you to retrieve a specific invalid email addresses.** -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. -For more information about your user profile: +Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). -### GET /user/account +### GET /suppression/invalid_emails/{email} ```python -response = sg.client.user.account.get() +email = "test_url_param" +response = sg.client.suppression.invalid_emails._(email).get() print response.status_code print response.body print response.headers ``` -## Retrieve your credit balance +## Delete a specific invalid email -**This endpoint allows you to retrieve the current credit balance for your account.** +**This endpoint allows you to remove a specific email address from the invalid email address list.** -Your monthly credit allotment limits the number of emails you may send before incurring overage charges. For more information about credits and billing, please visit our [Classroom](https://sendgrid.com/docs/Classroom/Basics/Billing/billing_info_and_faqs.html). +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. -### GET /user/credits +Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. + +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). + +### DELETE /suppression/invalid_emails/{email} ```python -response = sg.client.user.credits.get() +email = "test_url_param" +response = sg.client.suppression.invalid_emails._(email).delete() print response.status_code print response.body print response.headers ``` -## Update your account email address - -**This endpoint allows you to update the email address currently on file for your account.** +## Retrieve a specific spam report -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. +**This endpoint allows you to retrieve a specific spam report.** -For more information about your user profile: +[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to SendGrid. -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). -### PUT /user/email +### GET /suppression/spam_report/{email} ```python -data = { - "email": "example@example.com" -} -response = sg.client.user.email.put(request_body=data) +email = "test_url_param" +response = sg.client.suppression.spam_report._(email).get() print response.status_code print response.body print response.headers ``` -## Retrieve your account email address - -**This endpoint allows you to retrieve the email address currently on file for your account.** +## Delete a specific spam report -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. +**This endpoint allows you to delete a specific spam report.** -For more information about your user profile: +[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to SendGrid. -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). -### GET /user/email +### DELETE /suppression/spam_report/{email} ```python -response = sg.client.user.email.get() +email = "test_url_param" +response = sg.client.suppression.spam_report._(email).delete() print response.status_code print response.body print response.headers ``` -## Update your password - -**This endpoint allows you to update your password.** +## Retrieve all spam reports -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. +**This endpoint allows you to retrieve all spam reports.** -For more information about your user profile: +[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to SendGrid. -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). -### PUT /user/password +### GET /suppression/spam_reports ```python -data = { - "new_password": "new_password", - "old_password": "old_password" -} -response = sg.client.user.password.put(request_body=data) +params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} +response = sg.client.suppression.spam_reports.get(query_params=params) print response.status_code print response.body print response.headers ``` -## Update a user's profile +## Delete spam reports -**This endpoint allows you to update your current profile details.** +**This endpoint allows you to delete your spam reports.** -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. +There are two options for deleting spam reports: -For more information about your user profile: +1) You can delete all spam reports by setting "delete_all" to true in the request body. +2) You can delete some spam reports by specifying the email addresses in an array in the request body. -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to SendGrid. -It should be noted that any one or more of the parameters can be updated via the PATCH /user/profile endpoint. The only requirement is that you include at least one when you PATCH. +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). -### PATCH /user/profile +### DELETE /suppression/spam_reports ```python data = { - "city": "Orange", - "first_name": "Example", - "last_name": "User" + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] } -response = sg.client.user.profile.patch(request_body=data) +response = sg.client.suppression.spam_reports.delete(request_body=data) print response.status_code print response.body print response.headers ``` -## Get a user's profile - -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. +## Retrieve all global suppressions -For more information about your user profile: +**This endpoint allows you to retrieve a list of all email address that are globally suppressed.** -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +A global suppression (or global unsubscribe) is an email address of a recipient who does not want to receive any of your messages. A globally suppressed recipient will be removed from any email you send. For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/global_unsubscribes.html). -### GET /user/profile +### GET /suppression/unsubscribes ```python -response = sg.client.user.profile.get() +params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} +response = sg.client.suppression.unsubscribes.get(query_params=params) print response.status_code print response.body print response.headers ``` -## Cancel or pause a scheduled send + +# TEMPLATES -**This endpoint allows you to cancel or pause an email that has been scheduled to be sent.** +## Create a transactional template. -If the maximum number of cancellations/pauses are added, HTTP 400 will -be returned. +**This endpoint allows you to create a transactional template.** -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. -### POST /user/scheduled_sends +Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). + +### POST /templates ```python data = { - "batch_id": "YOUR_BATCH_ID", - "status": "pause" + "name": "example_name" } -response = sg.client.user.scheduled_sends.post(request_body=data) +response = sg.client.templates.post(request_body=data) print response.status_code print response.body print response.headers ``` -## Retrieve all scheduled sends +## Retrieve all transactional templates. -**This endpoint allows you to retrieve all cancel/paused scheduled send information.** +**This endpoint allows you to retrieve all transactional templates.** -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. -### GET /user/scheduled_sends +Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). + +### GET /templates ```python -response = sg.client.user.scheduled_sends.get() +response = sg.client.templates.get() print response.status_code print response.body print response.headers ``` -## Update user scheduled send information +## Edit a transactional template. -**This endpoint allows you to update the status of a scheduled send for the given `batch_id`.** +**This endpoint allows you to edit a transactional template.** -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. -### PATCH /user/scheduled_sends/{batch_id} +Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). + + +### PATCH /templates/{template_id} ```python data = { - "status": "pause" + "name": "new_example_name" } -batch_id = "test_url_param" -response = sg.client.user.scheduled_sends._(batch_id).patch(request_body=data) +template_id = "test_url_param" +response = sg.client.templates._(template_id).patch(request_body=data) print response.status_code print response.body print response.headers ``` -## Retrieve scheduled send - -**This endpoint allows you to retrieve the cancel/paused scheduled send information for a specific `batch_id`.** - -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. - -### GET /user/scheduled_sends/{batch_id} +## Retrieve a single transactional template. +**This endpoint allows you to retrieve a single transactional template.** -```python -batch_id = "test_url_param" -response = sg.client.user.scheduled_sends._(batch_id).get() -print response.status_code -print response.body -print response.headers -``` -## Delete a cancellation or pause of a scheduled send +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. -**This endpoint allows you to delete the cancellation/pause of a scheduled send.** +Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### DELETE /user/scheduled_sends/{batch_id} +### GET /templates/{template_id} ```python -batch_id = "test_url_param" -response = sg.client.user.scheduled_sends._(batch_id).delete() +template_id = "test_url_param" +response = sg.client.templates._(template_id).get() print response.status_code print response.body print response.headers ``` -## Update Enforced TLS settings +## Delete a template. -**This endpoint allows you to update your current Enforced TLS settings.** +**This endpoint allows you to delete a transactional template.** -The Enforced TLS settings specify whether or not the recipient is required to support TLS or have a valid certificate. See the [SMTP Ports User Guide](https://sendgrid.com/docs/Classroom/Basics/Email_Infrastructure/smtp_ports.html) for more information on opportunistic TLS. +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. -**Note:** If either setting is enabled and the recipient does not support TLS or have a valid certificate, we drop the message and send a block event with TLS required but not supported as the description. +Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -### PATCH /user/settings/enforced_tls + +### DELETE /templates/{template_id} ```python -data = { - "require_tls": True, - "require_valid_cert": False -} -response = sg.client.user.settings.enforced_tls.patch(request_body=data) +template_id = "test_url_param" +response = sg.client.templates._(template_id).delete() print response.status_code print response.body print response.headers ``` -## Retrieve current Enforced TLS settings. +## Create a new transactional template version. -**This endpoint allows you to retrieve your current Enforced TLS settings.** +**This endpoint allows you to create a new version of a template.** -The Enforced TLS settings specify whether or not the recipient is required to support TLS or have a valid certificate. See the [SMTP Ports User Guide](https://sendgrid.com/docs/Classroom/Basics/Email_Infrastructure/smtp_ports.html) for more information on opportunistic TLS. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. -**Note:** If either setting is enabled and the recipient does not support TLS or have a valid certificate, we drop the message and send a block event with TLS required but not supported as the description. +For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -### GET /user/settings/enforced_tls + +### POST /templates/{template_id}/versions ```python -response = sg.client.user.settings.enforced_tls.get() +data = { + "active": 1, + "html_content": "<%body%>", + "name": "example_version_name", + "plain_content": "<%body%>", + "subject": "<%subject%>", + "template_id": "ddb96bbc-9b92-425e-8979-99464621b543" +} +template_id = "test_url_param" +response = sg.client.templates._(template_id).versions.post(request_body=data) print response.status_code print response.body print response.headers ``` -## Update your username +## Edit a transactional template version. -**This endpoint allows you to update the username for your account.** +**This endpoint allows you to edit a version of one of your transactional templates.** -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. -For more information about your user profile: +For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| template_id | string | The ID of the original template | +| version_id | string | The ID of the template version | -### PUT /user/username +### PATCH /templates/{template_id}/versions/{version_id} ```python data = { - "username": "test_username" + "active": 1, + "html_content": "<%body%>", + "name": "updated_example_name", + "plain_content": "<%body%>", + "subject": "<%subject%>" } -response = sg.client.user.username.put(request_body=data) +template_id = "test_url_param" +version_id = "test_url_param" +response = sg.client.templates._(template_id).versions._(version_id).patch(request_body=data) print response.status_code print response.body print response.headers ``` -## Retrieve your username +## Retrieve a specific transactional template version. -**This endpoint allows you to retrieve your current account username.** +**This endpoint allows you to retrieve a specific version of a template.** -Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. -For more information about your user profile: +For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| template_id | string | The ID of the original template | +| version_id | string | The ID of the template version | -### GET /user/username +### GET /templates/{template_id}/versions/{version_id} ```python -response = sg.client.user.username.get() +template_id = "test_url_param" +version_id = "test_url_param" +response = sg.client.templates._(template_id).versions._(version_id).get() print response.status_code print response.body print response.headers ``` -## Update Event Notification Settings +## Delete a transactional template version. -**This endpoint allows you to update your current event webhook settings.** +**This endpoint allows you to delete one of your transactional template versions.** -If an event type is marked as `true`, then the event webhook will include information about that event. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. -SendGrids Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email. +For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| template_id | string | The ID of the original template | +| version_id | string | The ID of the template version | -### PATCH /user/webhooks/event/settings +### DELETE /templates/{template_id}/versions/{version_id} ```python -data = { - "bounce": True, - "click": True, - "deferred": True, - "delivered": True, - "dropped": True, - "enabled": True, - "group_resubscribe": True, - "group_unsubscribe": True, - "open": True, - "processed": True, - "spam_report": True, - "unsubscribe": True, - "url": "url" -} -response = sg.client.user.webhooks.event.settings.patch(request_body=data) +template_id = "test_url_param" +version_id = "test_url_param" +response = sg.client.templates._(template_id).versions._(version_id).delete() print response.status_code print response.body print response.headers ``` -## Retrieve Event Webhook settings +## Activate a transactional template version. -**This endpoint allows you to retrieve your current event webhook settings.** +**This endpoint allows you to activate a version of one of your templates.** -If an event type is marked as `true`, then the event webhook will include information about that event. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. -SendGrids Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email. -Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. +For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -### GET /user/webhooks/event/settings +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| template_id | string | The ID of the original template | +| version_id | string | The ID of the template version | + +### POST /templates/{template_id}/versions/{version_id}/activate ```python -response = sg.client.user.webhooks.event.settings.get() +template_id = "test_url_param" +version_id = "test_url_param" +response = sg.client.templates._(template_id).versions._(version_id).activate.post() print response.status_code print response.body print response.headers ``` -## Test Event Notification Settings + +# TRACKING SETTINGS -**This endpoint allows you to test your event webhook by sending a fake event notification post to the provided URL.** +## Retrieve Tracking Settings -SendGrids Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email. +**This endpoint allows you to retrieve a list of all tracking settings that you can enable on your account.** -Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -### POST /user/webhooks/event/test +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). + +### GET /tracking_settings ```python -data = { - "url": "url" -} -response = sg.client.user.webhooks.event.test.post(request_body=data) +params = {'limit': 1, 'offset': 1} +response = sg.client.tracking_settings.get(query_params=params) print response.status_code print response.body print response.headers ``` -## Create a parse setting +## Update Click Tracking Settings -**This endpoint allows you to create a new inbound parse setting.** +**This endpoint allows you to change your current click tracking setting. You can enable, or disable, click tracking using this endpoint.** -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -### POST /user/webhooks/parse/settings +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). + +### PATCH /tracking_settings/click ```python data = { - "hostname": "myhostname.com", - "send_raw": False, - "spam_check": True, - "url": "http://email.myhosthame.com" + "enabled": True } -response = sg.client.user.webhooks.parse.settings.post(request_body=data) +response = sg.client.tracking_settings.click.patch(request_body=data) print response.status_code print response.body print response.headers ``` -## Retrieve all parse settings +## Retrieve Click Track Settings -**This endpoint allows you to retrieve all of your current inbound parse settings.** +**This endpoint allows you to retrieve your current click tracking setting.** -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -### GET /user/webhooks/parse/settings +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). + +### GET /tracking_settings/click ```python -response = sg.client.user.webhooks.parse.settings.get() +response = sg.client.tracking_settings.click.get() print response.status_code print response.body print response.headers ``` -## Update a parse setting +## Update Google Analytics Settings -**This endpoint allows you to update a specific inbound parse setting.** +**This endpoint allows you to update your current setting for Google Analytics.** -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). +For more information about using Google Analytics, please refer to [Googles URL Builder](https://support.google.com/analytics/answer/1033867?hl=en) and their article on ["Best Practices for Campaign Building"](https://support.google.com/analytics/answer/1037445). -### PATCH /user/webhooks/parse/settings/{hostname} +We default the settings to Googles recommendations. For more information, see [Google Analytics Demystified](https://sendgrid.com/docs/Classroom/Track/Collecting_Data/google_analytics_demystified_ga_statistics_vs_sg_statistics.html). + +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. + +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). + +### PATCH /tracking_settings/google_analytics ```python data = { - "send_raw": True, - "spam_check": False, - "url": "http://newdomain.com/parse" + "enabled": True, + "utm_campaign": "website", + "utm_content": "", + "utm_medium": "email", + "utm_source": "sendgrid.com", + "utm_term": "" } -hostname = "test_url_param" -response = sg.client.user.webhooks.parse.settings._(hostname).patch(request_body=data) +response = sg.client.tracking_settings.google_analytics.patch(request_body=data) print response.status_code print response.body print response.headers ``` -## Retrieve a specific parse setting - -**This endpoint allows you to retrieve a specific inbound parse setting.** - -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). +## Retrieve Google Analytics Settings -### GET /user/webhooks/parse/settings/{hostname} +**This endpoint allows you to retrieve your current setting for Google Analytics.** +For more information about using Google Analytics, please refer to [Googles URL Builder](https://support.google.com/analytics/answer/1033867?hl=en) and their article on ["Best Practices for Campaign Building"](https://support.google.com/analytics/answer/1037445). -```python -hostname = "test_url_param" -response = sg.client.user.webhooks.parse.settings._(hostname).get() -print response.status_code -print response.body -print response.headers -``` -## Delete a parse setting +We default the settings to Googles recommendations. For more information, see [Google Analytics Demystified](https://sendgrid.com/docs/Classroom/Track/Collecting_Data/google_analytics_demystified_ga_statistics_vs_sg_statistics.html). -**This endpoint allows you to delete a specific inbound parse setting.** +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). -### DELETE /user/webhooks/parse/settings/{hostname} +### GET /tracking_settings/google_analytics ```python -hostname = "test_url_param" -response = sg.client.user.webhooks.parse.settings._(hostname).delete() +response = sg.client.tracking_settings.google_analytics.get() print response.status_code print response.body print response.headers ``` -## Retrieves Inbound Parse Webhook statistics. +## Update Open Tracking Settings -**This endpoint allows you to retrieve the statistics for your Parse Webhook usage.** +**This endpoint allows you to update your current settings for open tracking.** -SendGrid's Inbound Parse Webhook allows you to parse the contents and attachments of incoming emails. The Parse API can then POST the parsed emails to a URL that you specify. The Inbound Parse Webhook cannot parse messages greater than 20MB in size, including all attachments. +Open Tracking adds an invisible image at the end of the email which can track email opens. If the email recipient has images enabled on their email client, a request to SendGrids server for the invisible image is executed and an open event is logged. These events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook. -There are a number of pre-made integrations for the SendGrid Parse Webhook which make processing events easy. You can find these integrations in the [Library Index](https://sendgrid.com/docs/Integrate/libraries.html#-Webhook-Libraries). +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -### GET /user/webhooks/parse/stats +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). + +### PATCH /tracking_settings/open ```python -params = {'aggregated_by': 'day', 'limit': 'test_string', 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 'test_string'} -response = sg.client.user.webhooks.parse.stats.get(query_params=params) +data = { + "enabled": True +} +response = sg.client.tracking_settings.open.patch(request_body=data) print response.status_code print response.body print response.headers ``` - -# WHITELABEL - -## Create a domain whitelabel. +## Get Open Tracking Settings -**This endpoint allows you to create a whitelabel for one of your domains.** +**This endpoint allows you to retrieve your current settings for open tracking.** -If you are creating a domain whitelabel that you would like a subuser to use, you have two options: -1. Use the "username" parameter. This allows you to create a whitelabel on behalf of your subuser. This means the subuser is able to see and modify the created whitelabel. -2. Use the Association workflow (see Associate Domain section). This allows you to assign a whitelabel created by the parent to a subuser. This means the subuser will default to the assigned whitelabel, but will not be able to see or modify that whitelabel. However, if the subuser creates their own whitelabel it will overwrite the assigned whitelabel. +Open Tracking adds an invisible image at the end of the email which can track email opens. If the email recipient has images enabled on their email client, a request to SendGrids server for the invisible image is executed and an open event is logged. These events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook. -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). -### POST /whitelabel/domains +### GET /tracking_settings/open ```python -data = { - "automatic_security": False, - "custom_spf": True, - "default": True, - "domain": "example.com", - "ips": [ - "192.168.1.1", - "192.168.1.2" - ], - "subdomain": "news", - "username": "john@example.com" -} -response = sg.client.whitelabel.domains.post(request_body=data) +response = sg.client.tracking_settings.open.get() print response.status_code print response.body print response.headers ``` -## List all domain whitelabels. +## Update Subscription Tracking Settings -**This endpoint allows you to retrieve a list of all domain whitelabels you have created.** +**This endpoint allows you to update your current settings for subscription tracking.** -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Subscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). -### GET /whitelabel/domains +### PATCH /tracking_settings/subscription ```python -params = {'username': 'test_string', 'domain': 'test_string', 'exclude_subusers': 'true', 'limit': 1, 'offset': 1} -response = sg.client.whitelabel.domains.get(query_params=params) +data = { + "enabled": True, + "html_content": "html content", + "landing": "landing page html", + "plain_content": "text content", + "replace": "replacement tag", + "url": "url" +} +response = sg.client.tracking_settings.subscription.patch(request_body=data) print response.status_code print response.body print response.headers ``` -## Get the default domain whitelabel. +## Retrieve Subscription Tracking Settings -**This endpoint allows you to retrieve the default whitelabel for a domain.** +**This endpoint allows you to retrieve your current settings for subscription tracking.** -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Subscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| domain | string |The domain to find a default domain whitelabel for. | +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). -### GET /whitelabel/domains/default +### GET /tracking_settings/subscription ```python -response = sg.client.whitelabel.domains.default.get() +response = sg.client.tracking_settings.subscription.get() print response.status_code print response.body print response.headers ``` -## List the domain whitelabel associated with the given user. + +# USER -**This endpoint allows you to retrieve all of the whitelabels that have been assigned to a specific subuser.** +## Get a user's account information. -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +**This endpoint allows you to retrieve your user account details.** -Domain whitelabels can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's whitelabels. To associate a whitelabel with a subuser, the parent account must first create the whitelabel and validate it. The parent may then associate the whitelabel via the subuser management tools. +Your user's account information includes the user's account type and reputation. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| username | string | Username of the subuser to find associated whitelabels for. | +For more information about your user profile: -### GET /whitelabel/domains/subuser +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) + +### GET /user/account ```python -response = sg.client.whitelabel.domains.subuser.get() +response = sg.client.user.account.get() print response.status_code print response.body print response.headers ``` -## Disassociate a domain whitelabel from a given user. - -**This endpoint allows you to disassociate a specific whitelabel from a subuser.** - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. - -Domain whitelabels can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's whitelabels. To associate a whitelabel with a subuser, the parent account must first create the whitelabel and validate it. The parent may then associate the whitelabel via the subuser management tools. +## Retrieve your credit balance -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +**This endpoint allows you to retrieve the current credit balance for your account.** -## URI Parameters -| URI Parameter | Type | Required? | Description | -|---|---|---|---| -| username | string | required | Username for the subuser to find associated whitelabels for. | +Your monthly credit allotment limits the number of emails you may send before incurring overage charges. For more information about credits and billing, please visit our [Classroom](https://sendgrid.com/docs/Classroom/Basics/Billing/billing_info_and_faqs.html). -### DELETE /whitelabel/domains/subuser +### GET /user/credits ```python -response = sg.client.whitelabel.domains.subuser.delete() +response = sg.client.user.credits.get() print response.status_code print response.body print response.headers ``` -## Update a domain whitelabel. +## Update your account email address + +**This endpoint allows you to update the email address currently on file for your account.** -**This endpoint allows you to update the settings for a domain whitelabel.** +Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +For more information about your user profile: -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) -### PATCH /whitelabel/domains/{domain_id} +### PUT /user/email ```python data = { - "custom_spf": True, - "default": False + "email": "example@example.com" } -domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).patch(request_body=data) +response = sg.client.user.email.put(request_body=data) print response.status_code print response.body print response.headers ``` -## Retrieve a domain whitelabel. +## Retrieve your account email address -**This endpoint allows you to retrieve a specific domain whitelabel.** +**This endpoint allows you to retrieve the email address currently on file for your account.** -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +For more information about your user profile: +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) -### GET /whitelabel/domains/{domain_id} +### GET /user/email ```python -domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).get() +response = sg.client.user.email.get() print response.status_code print response.body print response.headers ``` -## Delete a domain whitelabel. +## Update your password + +**This endpoint allows you to update your password.** -**This endpoint allows you to delete a domain whitelabel.** +Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +For more information about your user profile: -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) -### DELETE /whitelabel/domains/{domain_id} +### PUT /user/password ```python -domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).delete() +data = { + "new_password": "new_password", + "old_password": "old_password" +} +response = sg.client.user.password.put(request_body=data) print response.status_code print response.body print response.headers ``` -## Associate a domain whitelabel with a given user. +## Update a user's profile -**This endpoint allows you to associate a specific domain whitelabel with a subuser.** +**This endpoint allows you to update your current profile details.** -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. -Domain whitelabels can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's whitelabels. To associate a whitelabel with a subuser, the parent account must first create the whitelabel and validate it. The parent may then associate the whitelabel via the subuser management tools. +For more information about your user profile: -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| domain_id | integer | ID of the domain whitelabel to associate with the subuser. | +It should be noted that any one or more of the parameters can be updated via the PATCH /user/profile endpoint. The only requirement is that you include at least one when you PATCH. -### POST /whitelabel/domains/{domain_id}/subuser +### PATCH /user/profile ```python data = { - "username": "jane@example.com" + "city": "Orange", + "first_name": "Example", + "last_name": "User" } -domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).subuser.post(request_body=data) +response = sg.client.user.profile.patch(request_body=data) print response.status_code print response.body print response.headers ``` -## Add an IP to a domain whitelabel. - -**This endpoint allows you to add an IP address to a domain whitelabel.** +## Get a user's profile -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +For more information about your user profile: -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| id | integer | ID of the domain to which you are adding an IP | +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) -### POST /whitelabel/domains/{id}/ips +### GET /user/profile ```python -data = { - "ip": "192.168.0.1" -} -id = "test_url_param" -response = sg.client.whitelabel.domains._(id).ips.post(request_body=data) +response = sg.client.user.profile.get() print response.status_code print response.body print response.headers ``` -## Remove an IP from a domain whitelabel. - -**This endpoint allows you to remove a domain's IP address from that domain's whitelabel.** +## Cancel or pause a scheduled send -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +**This endpoint allows you to cancel or pause an email that has been scheduled to be sent.** -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +If the maximum number of cancellations/pauses are added, HTTP 400 will +be returned. -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| id | integer | ID of the domain whitelabel to delete the IP from. | -| ip | string | IP to remove from the domain whitelabel. | +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### DELETE /whitelabel/domains/{id}/ips/{ip} +### POST /user/scheduled_sends ```python -id = "test_url_param" -ip = "test_url_param" -response = sg.client.whitelabel.domains._(id).ips._(ip).delete() +data = { + "batch_id": "YOUR_BATCH_ID", + "status": "pause" +} +response = sg.client.user.scheduled_sends.post(request_body=data) print response.status_code print response.body print response.headers ``` -## Validate a domain whitelabel. - -**This endpoint allows you to validate a domain whitelabel. If it fails, it will return an error message describing why the whitelabel could not be validated.** - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +## Retrieve all scheduled sends -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +**This endpoint allows you to retrieve all cancel/paused scheduled send information.** -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| id | integer |ID of the domain whitelabel to validate. | +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### POST /whitelabel/domains/{id}/validate +### GET /user/scheduled_sends ```python -id = "test_url_param" -response = sg.client.whitelabel.domains._(id).validate.post() +response = sg.client.user.scheduled_sends.get() print response.status_code print response.body print response.headers ``` -## Create an IP whitelabel - -**This endpoint allows you to create an IP whitelabel.** - -When creating an IP whitelable, you should use the same subdomain that you used when you created a domain whitelabel. +## Update user scheduled send information -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. +**This endpoint allows you to update the status of a scheduled send for the given `batch_id`.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### POST /whitelabel/ips +### PATCH /user/scheduled_sends/{batch_id} ```python data = { - "domain": "example.com", - "ip": "192.168.1.1", - "subdomain": "email" + "status": "pause" } -response = sg.client.whitelabel.ips.post(request_body=data) +batch_id = "test_url_param" +response = sg.client.user.scheduled_sends._(batch_id).patch(request_body=data) print response.status_code print response.body print response.headers ``` -## Retrieve all IP whitelabels - -**This endpoint allows you to retrieve all of the IP whitelabels that have been created by this account.** - -You may include a search key by using the "ip" parameter. This enables you to perform a prefix search for a given IP segment (e.g. "192."). +## Retrieve scheduled send -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. +**This endpoint allows you to retrieve the cancel/paused scheduled send information for a specific `batch_id`.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### GET /whitelabel/ips +### GET /user/scheduled_sends/{batch_id} ```python -params = {'ip': 'test_string', 'limit': 1, 'offset': 1} -response = sg.client.whitelabel.ips.get(query_params=params) +batch_id = "test_url_param" +response = sg.client.user.scheduled_sends._(batch_id).get() print response.status_code print response.body print response.headers ``` -## Retrieve an IP whitelabel - -**This endpoint allows you to retrieve an IP whitelabel.** +## Delete a cancellation or pause of a scheduled send -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. +**This endpoint allows you to delete the cancellation/pause of a scheduled send.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### GET /whitelabel/ips/{id} +### DELETE /user/scheduled_sends/{batch_id} ```python -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).get() +batch_id = "test_url_param" +response = sg.client.user.scheduled_sends._(batch_id).delete() print response.status_code print response.body print response.headers ``` -## Delete an IP whitelabel +## Update Enforced TLS settings -**This endpoint allows you to delete an IP whitelabel.** +**This endpoint allows you to update your current Enforced TLS settings.** -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. +The Enforced TLS settings specify whether or not the recipient is required to support TLS or have a valid certificate. See the [SMTP Ports User Guide](https://sendgrid.com/docs/Classroom/Basics/Email_Infrastructure/smtp_ports.html) for more information on opportunistic TLS. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). +**Note:** If either setting is enabled and the recipient does not support TLS or have a valid certificate, we drop the message and send a block event with TLS required but not supported as the description. -### DELETE /whitelabel/ips/{id} +### PATCH /user/settings/enforced_tls ```python -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).delete() +data = { + "require_tls": True, + "require_valid_cert": False +} +response = sg.client.user.settings.enforced_tls.patch(request_body=data) print response.status_code print response.body print response.headers ``` -## Validate an IP whitelabel +## Retrieve current Enforced TLS settings. -**This endpoint allows you to validate an IP whitelabel.** +**This endpoint allows you to retrieve your current Enforced TLS settings.** -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. +The Enforced TLS settings specify whether or not the recipient is required to support TLS or have a valid certificate. See the [SMTP Ports User Guide](https://sendgrid.com/docs/Classroom/Basics/Email_Infrastructure/smtp_ports.html) for more information on opportunistic TLS. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). +**Note:** If either setting is enabled and the recipient does not support TLS or have a valid certificate, we drop the message and send a block event with TLS required but not supported as the description. -### POST /whitelabel/ips/{id}/validate +### GET /user/settings/enforced_tls ```python -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).validate.post() +response = sg.client.user.settings.enforced_tls.get() print response.status_code print response.body print response.headers ``` -## Create a Link Whitelabel +## Update your username + +**This endpoint allows you to update the username for your account.** -**This endpoint allows you to create a new link whitelabel.** +Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +For more information about your user profile: -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) -### POST /whitelabel/links +### PUT /user/username ```python data = { - "default": True, - "domain": "example.com", - "subdomain": "mail" + "username": "test_username" } -params = {'limit': 1, 'offset': 1} -response = sg.client.whitelabel.links.post(request_body=data, query_params=params) +response = sg.client.user.username.put(request_body=data) print response.status_code print response.body print response.headers ``` -## Retrieve all link whitelabels +## Retrieve your username + +**This endpoint allows you to retrieve your current account username.** -**This endpoint allows you to retrieve all link whitelabels.** +Keeping your user profile up to date is important. This will help SendGrid to verify who you are as well as contact you should we need to. -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +For more information about your user profile: -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) -### GET /whitelabel/links +### GET /user/username ```python -params = {'limit': 1} -response = sg.client.whitelabel.links.get(query_params=params) +response = sg.client.user.username.get() print response.status_code print response.body print response.headers ``` -## Retrieve a Default Link Whitelabel +## Update Event Notification Settings -**This endpoint allows you to retrieve the default link whitelabel.** +**This endpoint allows you to update your current event webhook settings.** -Default link whitelabel is the actual link whitelabel to be used when sending messages. If there are multiple link whitelabels, the default is determined by the following order: -
    -
  • Validated link whitelabels marked as "default"
  • -
  • Legacy link whitelabels (migrated from the whitelabel wizard)
  • -
  • Default SendGrid link whitelabel (i.e. 100.ct.sendgrid.net)
  • -
+If an event type is marked as `true`, then the event webhook will include information about that event. -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +SendGrids Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. -### GET /whitelabel/links/default +### PATCH /user/webhooks/event/settings ```python -params = {'domain': 'test_string'} -response = sg.client.whitelabel.links.default.get(query_params=params) +data = { + "bounce": True, + "click": True, + "deferred": True, + "delivered": True, + "dropped": True, + "enabled": True, + "group_resubscribe": True, + "group_unsubscribe": True, + "open": True, + "processed": True, + "spam_report": True, + "unsubscribe": True, + "url": "url" +} +response = sg.client.user.webhooks.event.settings.patch(request_body=data) print response.status_code print response.body print response.headers ``` -## Retrieve Associated Link Whitelabel +## Retrieve Event Webhook settings -**This endpoint allows you to retrieve the associated link whitelabel for a subuser.** +**This endpoint allows you to retrieve your current event webhook settings.** -Link whitelables can be associated with subusers from the parent account. This functionality allows -subusers to send mail using their parent's link whitelabels. To associate a link whitelabel, the parent account -must first create a whitelabel and validate it. The parent may then associate that whitelabel with a subuser via the API or the Subuser Management page in the user interface. +If an event type is marked as `true`, then the event webhook will include information about that event. -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +SendGrids Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. -### GET /whitelabel/links/subuser +### GET /user/webhooks/event/settings ```python -params = {'username': 'test_string'} -response = sg.client.whitelabel.links.subuser.get(query_params=params) +response = sg.client.user.webhooks.event.settings.get() print response.status_code print response.body print response.headers ``` -## Disassociate a Link Whitelabel - -**This endpoint allows you to disassociate a link whitelabel from a subuser.** +## Test Event Notification Settings -Link whitelables can be associated with subusers from the parent account. This functionality allows -subusers to send mail using their parent's link whitelabels. To associate a link whitelabel, the parent account -must first create a whitelabel and validate it. The parent may then associate that whitelabel with a subuser via the API or the Subuser Management page in the user interface. +**This endpoint allows you to test your event webhook by sending a fake event notification post to the provided URL.** -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +SendGrids Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as SendGrid processes your email. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. -### DELETE /whitelabel/links/subuser +### POST /user/webhooks/event/test ```python -params = {'username': 'test_string'} -response = sg.client.whitelabel.links.subuser.delete(query_params=params) +data = { + "url": "url" +} +response = sg.client.user.webhooks.event.test.post(request_body=data) print response.status_code print response.body print response.headers ``` -## Update a Link Whitelabel - -**This endpoint allows you to update a specific link whitelabel. You can use this endpoint to change a link whitelabel's default status.** +## Create a parse setting -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +**This endpoint allows you to create a new inbound parse setting.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). -### PATCH /whitelabel/links/{id} +### POST /user/webhooks/parse/settings ```python data = { - "default": True + "hostname": "myhostname.com", + "send_raw": False, + "spam_check": True, + "url": "http://email.myhosthame.com" } -id = "test_url_param" -response = sg.client.whitelabel.links._(id).patch(request_body=data) +response = sg.client.user.webhooks.parse.settings.post(request_body=data) print response.status_code print response.body print response.headers ``` -## Retrieve a Link Whitelabel - -**This endpoint allows you to retrieve a specific link whitelabel.** +## Retrieve all parse settings -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +**This endpoint allows you to retrieve all of your current inbound parse settings.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). -### GET /whitelabel/links/{id} +### GET /user/webhooks/parse/settings ```python -id = "test_url_param" -response = sg.client.whitelabel.links._(id).get() +response = sg.client.user.webhooks.parse.settings.get() print response.status_code print response.body print response.headers ``` -## Delete a Link Whitelabel - -**This endpoint allows you to delete a link whitelabel.** +## Update a parse setting -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +**This endpoint allows you to update a specific inbound parse setting.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). -### DELETE /whitelabel/links/{id} +### PATCH /user/webhooks/parse/settings/{hostname} ```python -id = "test_url_param" -response = sg.client.whitelabel.links._(id).delete() +data = { + "send_raw": True, + "spam_check": False, + "url": "http://newdomain.com/parse" +} +hostname = "test_url_param" +response = sg.client.user.webhooks.parse.settings._(hostname).patch(request_body=data) print response.status_code print response.body print response.headers ``` -## Validate a Link Whitelabel - -**This endpoint allows you to validate a link whitelabel.** +## Retrieve a specific parse setting -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +**This endpoint allows you to retrieve a specific inbound parse setting.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). -### POST /whitelabel/links/{id}/validate +### GET /user/webhooks/parse/settings/{hostname} ```python -id = "test_url_param" -response = sg.client.whitelabel.links._(id).validate.post() +hostname = "test_url_param" +response = sg.client.user.webhooks.parse.settings._(hostname).get() print response.status_code print response.body print response.headers ``` -## Associate a Link Whitelabel +## Delete a parse setting -**This endpoint allows you to associate a link whitelabel with a subuser account.** +**This endpoint allows you to delete a specific inbound parse setting.** -Link whitelables can be associated with subusers from the parent account. This functionality allows -subusers to send mail using their parent's link whitelabels. To associate a link whitelabel, the parent account -must first create a whitelabel and validate it. The parent may then associate that whitelabel with a subuser via the API or the Subuser Management page in the user interface. +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +### DELETE /user/webhooks/parse/settings/{hostname} -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### POST /whitelabel/links/{link_id}/subuser +```python +hostname = "test_url_param" +response = sg.client.user.webhooks.parse.settings._(hostname).delete() +print response.status_code +print response.body +print response.headers +``` +## Retrieves Inbound Parse Webhook statistics. + +**This endpoint allows you to retrieve the statistics for your Parse Webhook usage.** + +SendGrid's Inbound Parse Webhook allows you to parse the contents and attachments of incoming emails. The Parse API can then POST the parsed emails to a URL that you specify. The Inbound Parse Webhook cannot parse messages greater than 20MB in size, including all attachments. + +There are a number of pre-made integrations for the SendGrid Parse Webhook which make processing events easy. You can find these integrations in the [Library Index](https://sendgrid.com/docs/Integrate/libraries.html#-Webhook-Libraries). + +### GET /user/webhooks/parse/stats ```python -data = { - "username": "jane@example.com" -} -link_id = "test_url_param" -response = sg.client.whitelabel.links._(link_id).subuser.post(request_body=data) +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 From f49af64b8bdb6e04b66bb0dbc7fa66bad80a35ee Mon Sep 17 00:00:00 2001 From: Chandler Weiner Date: Wed, 17 Oct 2018 16:29:12 -0400 Subject: [PATCH 148/462] Correct spelling and anchor links --- USAGE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/USAGE.md b/USAGE.md index b14656ba8..bdd4dccfc 100644 --- a/USAGE.md +++ b/USAGE.md @@ -30,7 +30,7 @@ sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) * [PARTNER SETTINGS](#partner-settings) * [SCOPES](#scopes) * [SENDERS](#senders) -* [SENDER Authorization](#sender-authorization) +* [SENDER AUTHENTICATION](#sender-authentication) * [STATS](#stats) * [SUBUSERS](#subusers) * [SUPPRESSION](#suppression) From ad7a96af0e11b06b4f83c9b3f627de6d6ac9ed44 Mon Sep 17 00:00:00 2001 From: Chandler Weiner Date: Wed, 17 Oct 2018 16:33:52 -0400 Subject: [PATCH 149/462] Change path and file to "senderauthentication" --- .../senderauthentication.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{whitelabel/whitelabel.py => senderauthentication/senderauthentication.py} (100%) diff --git a/examples/whitelabel/whitelabel.py b/examples/senderauthentication/senderauthentication.py similarity index 100% rename from examples/whitelabel/whitelabel.py rename to examples/senderauthentication/senderauthentication.py From 54f1d305ef85ab92b1d9505be83bc3a3fac117b1 Mon Sep 17 00:00:00 2001 From: Bhavin Jawade Date: Fri, 19 Oct 2018 00:27:22 +0530 Subject: [PATCH 150/462] corrected links in CodeofConduct. Hacktoberfest. Issue #671 --- CODE_OF_CONDUCT.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 39ed18bf7..6532e6d45 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -36,6 +36,6 @@ 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)
From 3363e01f99cd60d23029d25c7102abdafa5aeacc Mon Sep 17 00:00:00 2001 From: Peter Yasi Date: Thu, 18 Oct 2018 23:23:12 -0400 Subject: [PATCH 151/462] Add unit tests for spam check --- test/test_spam_check.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 test/test_spam_check.py diff --git a/test/test_spam_check.py b/test/test_spam_check.py new file mode 100644 index 000000000..240b5b114 --- /dev/null +++ b/test/test_spam_check.py @@ -0,0 +1,38 @@ +from sendgrid.helpers.mail.spam_check import SpamCheck + +try: + import unittest2 as unittest +except ImportError: + import unittest + + +class UnitTests(unittest.TestCase): + + def test_spam_all_values(self): + expected = {'enable': True, 'threshold': 5, 'post_to_url': 'https://www.test.com'} + spam_check = SpamCheck(enable=True, threshold=5, post_to_url='https://www.test.com') + self.assertEqual(spam_check.get(), expected) + + def test_spam_no_url(self): + expected = {'enable': True, 'threshold': 10} + spam_check = SpamCheck(enable=True, threshold=10) + self.assertEqual(spam_check.get(), expected) + + def test_spam_no_threshold(self): + expected = {'enable': True} + spam_check = SpamCheck(enable=True) + self.assertEqual(spam_check.get(), expected) + + def test_has_values_but_not_enabled(self): + expected = {'enable': False, 'threshold': 1, 'post_to_url': 'https://www.test.com'} + spam_check = SpamCheck(enable=False, threshold=1, post_to_url='https://www.test.com') + self.assertEqual(spam_check.get(), expected) + + def test_spam_change_properties(self): + 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) + From 0f7e83616db51172e3836bdcf4aec7392e00bfaf Mon Sep 17 00:00:00 2001 From: Peter Yasi Date: Thu, 18 Oct 2018 23:28:23 -0400 Subject: [PATCH 152/462] Test description --- test/test_spam_check.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_spam_check.py b/test/test_spam_check.py index 240b5b114..326c5d070 100644 --- a/test/test_spam_check.py +++ b/test/test_spam_check.py @@ -29,6 +29,8 @@ def test_has_values_but_not_enabled(self): 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 From c9325eac217fd0a803943278c64d33f5e8c67966 Mon Sep 17 00:00:00 2001 From: vkmrishad Date: Sat, 20 Oct 2018 15:42:44 +0530 Subject: [PATCH 153/462] PEP8 Fixes and String Formatting Enhancement --- examples/helpers/stats/stats_example.py | 2 ++ register.py | 2 +- sendgrid/helpers/inbound/config.py | 7 ++++--- sendgrid/helpers/inbound/send.py | 4 +++- sendgrid/helpers/mail/content.py | 1 + sendgrid/helpers/mail/exceptions.py | 2 +- sendgrid/helpers/mail/validators.py | 4 +--- setup.py | 1 + test/test_app.py | 2 +- test/test_config.py | 2 +- test/test_mail.py | 6 +++--- test/test_project.py | 2 ++ test/test_send.py | 15 ++++++++++----- test/test_sendgrid.py | 7 +++++-- test/test_unassigned.py | 4 +++- 15 files changed, 39 insertions(+), 22 deletions(-) diff --git a/examples/helpers/stats/stats_example.py b/examples/helpers/stats/stats_example.py index d48664c3f..f3196881a 100644 --- a/examples/helpers/stats/stats_example.py +++ b/examples/helpers/stats/stats_example.py @@ -45,6 +45,7 @@ def build_subuser_stats(): # 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' @@ -92,6 +93,7 @@ def get_subuser_stats_sums(): print(response.headers) pprint_json(response.body) + get_global_stats() get_category_stats() get_category_stats_sums() diff --git a/register.py b/register.py index 0a7ffe8d8..00ddca15c 100644 --- a/register.py +++ b/register.py @@ -17,4 +17,4 @@ ''' final_text = readme_rst.replace(replace, replacement) with open('./README.txt', 'w', encoding='utf-8') as f: - f.write(final_text) + f.write(final_text) 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..e3526eb7c 100644 --- a/sendgrid/helpers/inbound/send.py +++ b/sendgrid/helpers/inbound/send.py @@ -37,6 +37,7 @@ def url(self): """URL to send to.""" return self._url + def main(): config = Config() parser = argparse.ArgumentParser(description='Test data and optional host.') @@ -54,5 +55,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/content.py b/sendgrid/helpers/mail/content.py index cff8ac498..da4ed8027 100644 --- a/sendgrid/helpers/mail/content.py +++ b/sendgrid/helpers/mail/content.py @@ -1,5 +1,6 @@ from .validators import ValidateAPIKey + class Content(object): """Content to be included in your email. diff --git a/sendgrid/helpers/mail/exceptions.py b/sendgrid/helpers/mail/exceptions.py index ab4dd9c0c..5279c365a 100644 --- a/sendgrid/helpers/mail/exceptions.py +++ b/sendgrid/helpers/mail/exceptions.py @@ -2,6 +2,7 @@ # Various types of extensible SendGrid related exceptions ################################################################ + class SendGridException(Exception): """Wrapper/default SendGrid-related exception""" pass @@ -19,4 +20,3 @@ def __init__(self, message="SendGrid API Key detected"): self.expression = expression self.message = message - diff --git a/sendgrid/helpers/mail/validators.py b/sendgrid/helpers/mail/validators.py index b4a69f697..96e81795e 100644 --- a/sendgrid/helpers/mail/validators.py +++ b/sendgrid/helpers/mail/validators.py @@ -3,6 +3,7 @@ # Various types of Validators ################################################################ + class ValidateAPIKey(object): """Validates content to ensure SendGrid API key is not present""" @@ -27,7 +28,6 @@ def __init__(self, regex_strings=None, use_default=True): default_regex_string = '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 @@ -54,7 +54,6 @@ 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 @@ -68,4 +67,3 @@ def validate_message_text(self, message_string): for regex in self.regexes: if regex.match(message_string) is not None: raise APIKeyIncludedException() - diff --git a/setup.py b/setup.py index 014691b61..11aa3a07e 100644 --- a/setup.py +++ b/setup.py @@ -20,6 +20,7 @@ def getRequires(): deps.append('unittest2py3k') return deps + setup( name='sendgrid', version=str(__version__), diff --git a/test/test_app.py b/test/test_app.py index 1a8e4a698..13b0a9522 100644 --- a/test/test_app.py +++ b/test/test_app.py @@ -23,4 +23,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..d20af40e2 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -43,7 +43,7 @@ def test_initialization(self): 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_mail.py b/test/test_mail.py index 08d0feb8e..ffe5586b4 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -52,7 +52,7 @@ def test_sendgridAPIKey(self): personalization.add_to(Email("test@example.com")) mail.add_personalization(personalization) - #Try to include SendGrid API key + # Try to include SendGrid API key try: mail.add_content(Content("text/plain", "some SG.2123b1B.1212lBaC here")) mail.add_content( @@ -72,11 +72,11 @@ def test_sendgridAPIKey(self): '"subject": "Hello World from the SendGrid Python Library"}' ) - #Exception should be thrown + # Exception should be thrown except Exception as e: pass - #Exception not thrown + # Exception not thrown else: self.fail("Should have failed as SendGrid API key included") diff --git a/test/test_project.py b/test/test_project.py index 4fbc8147c..5e269ae43 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -5,6 +5,7 @@ except ImportError: import unittest + class ProjectTests(unittest.TestCase): # ./docker @@ -71,5 +72,6 @@ def test_usage(self): def test_use_cases(self): self.assertTrue(os.path.isfile('./use_cases/README.md')) + if __name__ == '__main__': unittest.main() diff --git a/test/test_send.py b/test/test_send.py index 16d496b85..7079c8b0e 100644 --- a/test/test_send.py +++ b/test/test_send.py @@ -30,13 +30,18 @@ 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_sendgrid.py b/test/test_sendgrid.py index c545cbb2d..4b386e713 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -144,8 +144,11 @@ def test_hello_world(self): 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'}) + 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} diff --git a/test/test_unassigned.py b/test/test_unassigned.py index d13451277..6054447d8 100644 --- a/test/test_unassigned.py +++ b/test/test_unassigned.py @@ -55,6 +55,7 @@ } ] ''' + def get_all_ip(): ret_val = json.loads(ret_json) return ret_val @@ -67,7 +68,6 @@ def make_data(): return data - def test_unassigned_ip_json(): data = make_data() @@ -79,6 +79,7 @@ def test_unassigned_ip_json(): for item in calculated: assert item["ip"] in data + def test_unassigned_ip_obj(): data = make_data() @@ -89,6 +90,7 @@ def test_unassigned_ip_obj(): for item in calculated: assert item["ip"] in data + def test_unassigned_baddata(): as_json = False calculated = unassigned(dict(), as_json=as_json) From 568e5b08a24dc32ca598c513da56b582e6f26a87 Mon Sep 17 00:00:00 2001 From: Mike Dorman Date: Sat, 20 Oct 2018 21:27:01 +0530 Subject: [PATCH 154/462] Update examples/helpers/README.md Co-Authored-By: tulikavijay --- examples/helpers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/helpers/README.md b/examples/helpers/README.md index 917aebb16..5d5e31a03 100644 --- a/examples/helpers/README.md +++ b/examples/helpers/README.md @@ -1,5 +1,5 @@ ## 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, +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! From c0b5081db05fbea72d4f3f4f0e0233cd402c43a0 Mon Sep 17 00:00:00 2001 From: Mike Dorman Date: Sat, 20 Oct 2018 21:27:21 +0530 Subject: [PATCH 155/462] Update examples/helpers/README.md Co-Authored-By: tulikavijay --- examples/helpers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/helpers/README.md b/examples/helpers/README.md index 5d5e31a03..70cec463d 100644 --- a/examples/helpers/README.md +++ b/examples/helpers/README.md @@ -1,6 +1,6 @@ ## 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 +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. From b0778e8631e3e2f6e4f7dc86184331eb7668ab1c Mon Sep 17 00:00:00 2001 From: Mike Dorman Date: Sat, 20 Oct 2018 21:27:28 +0530 Subject: [PATCH 156/462] Update examples/helpers/README.md Co-Authored-By: tulikavijay --- examples/helpers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/helpers/README.md b/examples/helpers/README.md index 70cec463d..0a9c0c700 100644 --- a/examples/helpers/README.md +++ b/examples/helpers/README.md @@ -3,7 +3,7 @@ You can use helper classes to customize the process of sending emails using Send 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. +> 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) From d923a4b514f8fbd180a2c4f943f4068bbfea0a9f Mon Sep 17 00:00:00 2001 From: Mike Dorman Date: Sat, 20 Oct 2018 21:27:33 +0530 Subject: [PATCH 157/462] Update examples/helpers/README.md Co-Authored-By: tulikavijay --- examples/helpers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/helpers/README.md b/examples/helpers/README.md index 0a9c0c700..c7f44ac4d 100644 --- a/examples/helpers/README.md +++ b/examples/helpers/README.md @@ -13,7 +13,7 @@ defines minimum requirement to send an email. subject = "Hello World from the SendGrid Python Library" to_email = Email("test@example.com") ``` -you can use `Email` class to define a mail id. +You can use `Email` class to define a mail id. ``` content = Content("text/plain", "some text here") From 759abbb165260ec670c32d7e5662da38d05bda77 Mon Sep 17 00:00:00 2001 From: Mike Dorman Date: Sat, 20 Oct 2018 21:27:40 +0530 Subject: [PATCH 158/462] Update examples/helpers/README.md Co-Authored-By: tulikavijay --- examples/helpers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/helpers/README.md b/examples/helpers/README.md index c7f44ac4d..5f57ca66e 100644 --- a/examples/helpers/README.md +++ b/examples/helpers/README.md @@ -18,7 +18,7 @@ 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. +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) From 073fd38d71b89fedeb64547865a96778915b5ab0 Mon Sep 17 00:00:00 2001 From: Mike Dorman Date: Sat, 20 Oct 2018 21:27:46 +0530 Subject: [PATCH 159/462] Update examples/helpers/README.md Co-Authored-By: tulikavijay --- examples/helpers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/helpers/README.md b/examples/helpers/README.md index 5f57ca66e..6774ea7f7 100644 --- a/examples/helpers/README.md +++ b/examples/helpers/README.md @@ -23,7 +23,7 @@ The `Content` class takes mainly two parameters: MIME type and the actual conten ``` 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. +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 From 682d7dfa07dda2076f247a4e459bb816ddc74f44 Mon Sep 17 00:00:00 2001 From: Mike Dorman Date: Sat, 20 Oct 2018 21:27:54 +0530 Subject: [PATCH 160/462] Update examples/helpers/README.md Co-Authored-By: tulikavijay --- examples/helpers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/helpers/README.md b/examples/helpers/README.md index 6774ea7f7..5fb1445be 100644 --- a/examples/helpers/README.md +++ b/examples/helpers/README.md @@ -24,7 +24,7 @@ The `Content` class takes mainly two parameters: MIME type and the actual conten 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) +For more information on parameters and usage, see [here](https://github.com/sendgrid/sendgrid-python/blob/master/sendgrid/helpers/mail/mail.py) ### Creating Personalizations From 6b27879d61c9ce3bf5b1932eb11affde582945ba Mon Sep 17 00:00:00 2001 From: Mike Dorman Date: Sat, 20 Oct 2018 21:28:03 +0530 Subject: [PATCH 161/462] Update examples/helpers/README.md Co-Authored-By: tulikavijay --- examples/helpers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/helpers/README.md b/examples/helpers/README.md index 5fb1445be..04e32f0b7 100644 --- a/examples/helpers/README.md +++ b/examples/helpers/README.md @@ -28,7 +28,7 @@ For more information on parameters and usage, see [here](https://github.com/send ### 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) +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() From f4ac98c582faa9f5ff29c7dffe7da4487a6eeccc Mon Sep 17 00:00:00 2001 From: Tulika Date: Sat, 20 Oct 2018 22:05:01 +0530 Subject: [PATCH 162/462] Add remaining sections --- examples/helpers/README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/examples/helpers/README.md b/examples/helpers/README.md index 04e32f0b7..95981b497 100644 --- a/examples/helpers/README.md +++ b/examples/helpers/README.md @@ -35,3 +35,35 @@ After creating a dictionary, you can go ahead and create a `Personalization` obj 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). From 1819e607cd2a2663b7eed467536bfe798918b108 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Tue, 9 Oct 2018 15:12:18 -0400 Subject: [PATCH 163/462] Add use case for generation of Plain Text Content from HTML Resolves #354 --- use_cases/README.md | 1 + use_cases/sending_html_content.md | 58 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 use_cases/sending_html_content.md diff --git a/use_cases/README.md b/use_cases/README.md index 188464d09..bee7d628a 100644 --- a/use_cases/README.md +++ b/use_cases/README.md @@ -14,6 +14,7 @@ This directory provides examples for specific use cases of this library. Please ### Working with Mail * [Asynchronous Mail Send](asynchronous_mail_send.md) * [Attachment](attachment.md) +* [Sending HTML-Only Content](sending_html_content.md) * [Transactional Templates](transational_templates.md) ### Library Features diff --git a/use_cases/sending_html_content.md b/use_cases/sending_html_content.md new file mode 100644 index 000000000..86029dd59 --- /dev/null +++ b/use_cases/sending_html_content.md @@ -0,0 +1,58 @@ +# 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 sendgrid +import os +from sendgrid.helpers.mail import Email, Content, 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 + + +

+ + +""" + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +from_email = Email("test@exmaple.com") +subject = "subject" +to_emila = Email("to_email@example.com") +html_content = Content("text/html", html_text) + +mail = Mail(from_email, subject, to_email, html_content) + +soup = BeautifulSoup(html_text) +plain_text = soup.get_text() +plain_content = Content("text/plain", plain_text) +mail.add_content(plain_content) + +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) +``` \ No newline at end of file From b3feabe6288e7c2e2ca7a5cd0ec66cf104722de4 Mon Sep 17 00:00:00 2001 From: jaykay12 Date: Wed, 10 Oct 2018 03:38:16 +0530 Subject: [PATCH 164/462] Created First-timers.md File --- FIRST_TIMERS.md | 75 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 FIRST_TIMERS.md 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 From a83c82a27d6037c54e4fab76532248015742b9bb Mon Sep 17 00:00:00 2001 From: David McKay Date: Thu, 25 Oct 2018 15:42:41 +0100 Subject: [PATCH 165/462] Cleanup Env Documentation There were some conflicting configuration details, namely .env_sample, and sendgrid.env. I've opted for .env_sample, as sendgrid.env actually seemed to be incorrect. --- .gitignore | 1 - CONTRIBUTING.md | 15 +++++++++---- docker/USAGE.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ sendgrid.env | 1 - use_cases/aws.md | 20 ++++++++--------- 5 files changed, 78 insertions(+), 16 deletions(-) delete mode 100644 sendgrid.env diff --git a/.gitignore b/.gitignore index 4ac2623b4..dd43e4a8d 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,5 @@ README.txt coverage.xml htmlcov temp*.py -sendgrid.env .vscode sendgrid/VERSION.txt diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c4aa2c4a5..344605834 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,7 +62,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 - #### @@ -87,9 +87,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: ##### @@ -184,8 +189,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 ``` diff --git a/docker/USAGE.md b/docker/USAGE.md index cd543c402..1a4473766 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 .env_sample to .env file. +3. Edit .env file for yours 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/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/use_cases/aws.md b/use_cases/aws.md index 9c30fd7ed..2d5ebdcb1 100644 --- a/use_cases/aws.md +++ b/use_cases/aws.md @@ -19,15 +19,15 @@ Before starting this tutorial, you will need to have access to an AWS account in ## 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 @@ -44,7 +44,7 @@ For the rest of the tutorial, we'll be working out of the git repository we clon ``` $ 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: ``` @@ -157,16 +157,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! From ef0c8e011a3e6134713f40d21bdbffe5ce7c1286 Mon Sep 17 00:00:00 2001 From: David McKay Date: Fri, 26 Oct 2018 13:57:34 +0100 Subject: [PATCH 166/462] Cleanup Dockerfile's This commit tries to remove as many redundant layers as possible, while still maintaining readability. --- docker-test/Dockerfile | 20 +++++++----------- docker-test/entrypoint.sh | 0 docker/Dockerfile | 44 +++++++++++++++++++-------------------- docker/entrypoint.sh | 0 4 files changed, 29 insertions(+), 35 deletions(-) mode change 100644 => 100755 docker-test/entrypoint.sh mode change 100644 => 100755 docker/entrypoint.sh diff --git a/docker-test/Dockerfile b/docker-test/Dockerfile index c17d790d6..1d763b00e 100644 --- a/docker-test/Dockerfile +++ b/docker-test/Dockerfile @@ -1,23 +1,19 @@ 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/* +RUN apk add --update --no-cache bash curl -# install Prism -WORKDIR /root +# Install Prism ADD https://raw.githubusercontent.com/stoplightio/prism/master/install.sh install.sh -RUN chmod +x ./install.sh && sync && \ - ./install.sh && \ - rm ./install.sh - -# set up default sendgrid env -WORKDIR /root +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"] + +ENTRYPOINT ["entrypoint.sh"] CMD ["--mock"] diff --git a/docker-test/entrypoint.sh b/docker-test/entrypoint.sh old mode 100644 new mode 100755 diff --git a/docker/Dockerfile b/docker/Dockerfile index 798b494e0..bbbf56277 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,8 +1,12 @@ FROM ubuntu:xenial + +WORKDIR /root + ENV PYTHON_VERSIONS='python2.7 python3.4 python3.5 python3.6' \ - OAI_SPEC_URL="https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/oai_stoplight.json" + OAI_SPEC_URL="https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/oai_stoplight.json" \ + DEBIAN_FRONTEND=noninteractive -# install testing versions of python, including old versions, from deadsnakes +# Install testing versions of python, including old versions, from deadsnakes RUN set -x \ && apt-get update \ && apt-get install -y --no-install-recommends software-properties-common \ @@ -14,35 +18,29 @@ RUN set -x \ && apt-get purge -y --auto-remove software-properties-common \ && rm -rf /var/lib/apt/lists/* -WORKDIR /root - -# install Prism +# Install Prism ADD https://raw.githubusercontent.com/stoplightio/prism/master/install.sh install.sh -RUN chmod +x ./install.sh && sync && \ - ./install.sh && \ - rm ./install.sh +RUN sync && bash install.sh -# install pip, tox +# Install pip, tox ADD https://bootstrap.pypa.io/get-pip.py get-pip.py -RUN python2.7 get-pip.py && \ - pip install tox && \ - rm get-pip.py +RUN python2.7 get-pip.py && pip install tox -#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 +# Install pyyaml, six, werkzeug +RUN python3.6 -m pip install pyyaml six werkzeug flask -# set up default sendgrid env +# 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 \ + && git clone https://github.com/sendgrid/python-http-client.git + WORKDIR /root -RUN ln -s /root/sources/sendgrid-python/sendgrid && \ - ln -s /root/sources/python-http-client/python_http_client + +RUN ln -s /root/sources/sendgrid-python/sendgrid \ + && ln -s /root/sources/python-http-client/python_http_client COPY entrypoint.sh entrypoint.sh -RUN chmod +x entrypoint.sh + ENTRYPOINT ["./entrypoint.sh"] CMD ["--mock"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh old mode 100644 new mode 100755 From d1f3545fe1d6413bef4c0d59f499ca28e26944f9 Mon Sep 17 00:00:00 2001 From: Corey McCandless Date: Sat, 27 Oct 2018 07:35:25 -0400 Subject: [PATCH 167/462] Use assertEqual instead of assertDictEqual assertEqual will call assertDictEqual --- test/test_mail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_mail.py b/test/test_mail.py index d2cb37af9..8ebe3eb2b 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -582,7 +582,7 @@ def test_from_emailmessage(self): self.assertEqual(mail.from_email.email, 'test@example.com') self.assertEqual(len(mail.personalizations), 1) self.assertEqual(len(mail.personalizations[0].tos), 1) - self.assertDictEqual(mail.personalizations[0].tos[0], {'email': 'test@sendgrid.com'}) + self.assertEqual(mail.personalizations[0].tos[0], {'email': 'test@sendgrid.com'}) self.assertEqual(len(mail.contents), 1) content = mail.contents[0] self.assertEqual(content.type, 'text/plain') From 2546fa3d7eb0e9dde80e4030c69923dcb34f05b4 Mon Sep 17 00:00:00 2001 From: PyroclasticMayhem Date: Sat, 27 Oct 2018 20:13:58 -0400 Subject: [PATCH 168/462] Correct attribution links formating --- CODE_OF_CONDUCT.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 39ed18bf7..723e645f5 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -36,6 +36,6 @@ 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) From 3f3eba45eff1447f51e49d82e6b15b92bbf6b696 Mon Sep 17 00:00:00 2001 From: Rishabh Chaudhary Date: Tue, 30 Oct 2018 00:28:50 +0530 Subject: [PATCH 169/462] Updation in prerequisites --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4132b62b5..114076241 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ We appreciate your continued support, thank you! ## 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) +- The SendGrid service, starting at the [free level](https://sendgrid.com/free?source=sendgrid-python) to send up to 40,000 emails for the first 30 days, then send 100 emails/day free forever or check out [our pricing](https://sendgrid.com/pricing?source=sendgrid-python). ## Setup Environment Variables ### Mac From 4762039541aca8bef1a8f0cca652211a0fcec350 Mon Sep 17 00:00:00 2001 From: Mohammed Rishad Date: Tue, 30 Oct 2018 10:25:42 +0530 Subject: [PATCH 170/462] Updated file with Master branch --- test/test_sendgrid.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index 4b386e713..37fe9f9f8 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -1,6 +1,7 @@ import sendgrid from sendgrid.helpers.mail import * from sendgrid.version import __version__ + try: import unittest2 as unittest except ImportError: @@ -25,7 +26,7 @@ def setUpClass(cls): 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: @@ -134,7 +135,7 @@ 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): @@ -144,11 +145,11 @@ def test_hello_world(self): 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' - }) + 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} From 5049c53f9e3db87e8b66ef5d21f89d84d303ec3e Mon Sep 17 00:00:00 2001 From: ChatPion Date: Tue, 30 Oct 2018 12:00:41 +0100 Subject: [PATCH 171/462] Update USAGE.md Change all "print a" to "print(a)" --- USAGE.md | 1387 +++++++++++++++++++++++++++--------------------------- 1 file changed, 694 insertions(+), 693 deletions(-) diff --git a/USAGE.md b/USAGE.md index 09b78609a..2a6fd62d7 100644 --- a/USAGE.md +++ b/USAGE.md @@ -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 @@ -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,9 +329,9 @@ 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 @@ -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,9 +399,9 @@ 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 @@ -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 @@ -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,9 +1412,9 @@ 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 @@ -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 @@ -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,9 +1681,9 @@ 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 @@ -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 @@ -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 @@ -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 @@ -2750,9 +2750,9 @@ 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 @@ -2765,9 +2765,9 @@ Sender Identities are required to be verified before use. If your domain has bee ```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 @@ -2800,9 +2800,9 @@ 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 @@ -2816,9 +2816,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).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 @@ -2832,9 +2832,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).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 @@ -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) ``` + From 1536f51d5d37ee4419bc3317e857ad9213692382 Mon Sep 17 00:00:00 2001 From: Rahul Purohit Date: Wed, 31 Oct 2018 00:41:04 +0530 Subject: [PATCH 172/462] updated README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 4132b62b5..77d6185dd 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,10 @@ Please browse the rest of this README for further detail. We appreciate your continued support, thank you! +# Announcements + + **NEW:** If you're a software engineer who is passionate about #DeveloperExperience and/or #OpenSource, [this is an incredible opportunity to join our #DX team](https://sendgrid.com/careers/role/1421152/?gh_jid=1421152) as a Developer Experience Engineer and work with [@thinkingserious](https://github.com/thinkingserious) and [@aroach](https://github.com/aroach)! Tell your friends :) + # Table of Contents * [Installation](#installation) From 28b7402228f57e60ab004de20106281b48dc507f Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 31 Oct 2018 00:02:42 +0200 Subject: [PATCH 173/462] Use raw-string notation for regex to avoid invalid escape sequence --- sendgrid/helpers/mail/validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/helpers/mail/validators.py b/sendgrid/helpers/mail/validators.py index 816ec71a4..4ca49a08a 100644 --- a/sendgrid/helpers/mail/validators.py +++ b/sendgrid/helpers/mail/validators.py @@ -25,7 +25,7 @@ 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): From 76ac470380a8357afab14970e12ff26a97264a95 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Mon, 12 Nov 2018 11:57:51 -0800 Subject: [PATCH 174/462] Rename transational_templates.md to transactional_templates.md --- .../{transational_templates.md => transactional_templates.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename use_cases/{transational_templates.md => transactional_templates.md} (99%) diff --git a/use_cases/transational_templates.md b/use_cases/transactional_templates.md similarity index 99% rename from use_cases/transational_templates.md rename to use_cases/transactional_templates.md index 491d528bd..48627ee58 100644 --- a/use_cases/transational_templates.md +++ b/use_cases/transactional_templates.md @@ -178,4 +178,4 @@ except urllib.HTTPError as e: print(response.status_code) print(response.body) print(response.headers) -``` \ No newline at end of file +``` From aaf4678de08a64875a7c0aea45cb692c62637a83 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 27 Nov 2018 11:30:54 -0800 Subject: [PATCH 175/462] Fixed local Docker testing --- docker-test/Dockerfile | 5 ++-- docker-test/entrypoint.sh | 4 ++-- docker-test/prism.sh | 50 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) create mode 100755 docker-test/prism.sh diff --git a/docker-test/Dockerfile b/docker-test/Dockerfile index 1d763b00e..140b5df68 100644 --- a/docker-test/Dockerfile +++ b/docker-test/Dockerfile @@ -8,12 +8,13 @@ ENV SENDGRID_API_KEY $SENDGRID_API_KEY RUN apk add --update --no-cache bash curl # Install Prism -ADD https://raw.githubusercontent.com/stoplightio/prism/master/install.sh install.sh +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"] +ENTRYPOINT ["./entrypoint.sh"] CMD ["--mock"] diff --git a/docker-test/entrypoint.sh b/docker-test/entrypoint.sh index f64d0cccb..d00023a44 100755 --- a/docker-test/entrypoint.sh +++ b/docker-test/entrypoint.sh @@ -5,10 +5,10 @@ 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 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 From ee058dfb5275bad06e2fcfb971043f54fbf5c646 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 27 Nov 2018 12:00:05 -0800 Subject: [PATCH 176/462] Add properies to mail.py --- sendgrid/helpers/mail/attachment.py | 2 + sendgrid/helpers/mail/mail.py | 123 ++++++++++++++++++++++++---- 2 files changed, 109 insertions(+), 16 deletions(-) diff --git a/sendgrid/helpers/mail/attachment.py b/sendgrid/helpers/mail/attachment.py index 045db6572..b162b7609 100644 --- a/sendgrid/helpers/mail/attachment.py +++ b/sendgrid/helpers/mail/attachment.py @@ -6,6 +6,8 @@ def __init__(self, content=None, type_=None, filename=None, disposition=None, co :param content: The Base64 encoded content of the attachment :type content: string, optional + :param type: The MIME type of the content you are attaching + :type type string, optional :param filename: The filename of the attachment :type filename: string, optional :param disposition: The content-disposition of the attachment, specifying display style. diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index c27fc72d7..91d7976b1 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -9,6 +9,17 @@ class Mail(object): """Creates the response body for v3/mail/send""" def __init__( self, from_email=None, subject=None, to_email=None, content=None): + """Create Mail object + + :param from_email: The email address of the sender + :type from_email: string, optional + :param subject: The subject of the email + :type subject: string, optional + :param to_email: The email address of the recipient + :type to_email: string, optional + :param content: The body of the email + :type content: string, optional + """ self._attachments = None self._categories = None self._contents = None @@ -16,27 +27,27 @@ def __init__( self._headers = None self._personalizations = None self._sections = None - self.asm = None - self.batch_id = None - self.from_email = None - self.ip_pool_name = None - self.mail_settings = None - self.reply_to = None - self.send_at = None - self.subject = None - self.template_id = None - self.tracking_settings = None - - # Minimum required to send a single email - if from_email: + self._asm = None + self._batch_id = None + self._from_email = None + self._ip_pool_name = None + self._mail_settings = None + self._reply_to = None + self._send_at = None + self._subject = None + self._template_id = None + self._tracking_settings = None + + # Minimum required data to send a single email + if from_email is not None: self.from_email = from_email - if subject: + if subject is not None: self.subject = subject - if to_email: + if to_email is not None: personalization = Personalization() personalization.add_to(to_email) self.add_personalization(personalization) - if content: + if content is not None: self.add_content(content) def __str__(self): @@ -117,6 +128,86 @@ def sections(self): def add_section(self, section): self._sections = self._ensure_append(section, self._sections) + @property + def asm(self): + return self._asm + + @asm.setter + def asm(self, value): + self._asm = value + + @property + def batch_id(self): + return self._batch_id + + @batch_id.setter + def batch_id(self, value): + self._batch_id = value + + @property + def from_email(self): + return self._from_email + + @from_email.setter + def from_email(self, value): + self._from_email = value + + @property + def ip_pool_name(self): + return self._ip_pool_name + + @ip_pool_name.setter + def ip_pool_name(self, value): + self._ip_pool_name = value + + @property + def mail_settings(self): + return self._mail_settings + + @mail_settings.setter + def mail_settings(self, value): + self._mail_settings = value + + @property + def reply_to(self): + return self._reply_to + + @reply_to.setter + def reply_to(self, value): + self._reply_to = value + + @property + def send_at(self): + return self._send_at + + @send_at.setter + def send_at(self, value): + self._send_at = value + + @property + def subject(self): + return self._subject + + @subject.setter + def subject(self, value): + self._subject = value + + @property + def template_id(self): + return self._template_id + + @template_id.setter + def template_id(self, value): + self._template_id = value + + @property + def tracking_settings(self): + return self._tracking_settings + + @tracking_settings.setter + def tracking_settings(self, value): + self._tracking_settings = value + def get(self): """ :return: request body dict From 965579462e351e77d24f10bd20765bb1c30a98bb Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 29 Nov 2018 11:23:13 -0800 Subject: [PATCH 177/462] Finished updating properties --- sendgrid/helpers/mail/open_tracking.py | 10 ++++- sendgrid/helpers/mail/plain_text_content.py | 37 +++++++++++++++---- sendgrid/helpers/mail/sandbox_mode.py | 5 ++- sendgrid/helpers/mail/section.py | 9 ++++- sendgrid/helpers/mail/spam_check.py | 13 +++++-- sendgrid/helpers/mail/subject.py | 4 +- .../helpers/mail/subscription_tracking.py | 17 +++++++-- sendgrid/helpers/mail/substitution.py | 9 ++++- sendgrid/helpers/mail/validators.py | 2 +- 9 files changed, 82 insertions(+), 24 deletions(-) diff --git a/sendgrid/helpers/mail/open_tracking.py b/sendgrid/helpers/mail/open_tracking.py index 633f2d9ed..636f6c968 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): diff --git a/sendgrid/helpers/mail/plain_text_content.py b/sendgrid/helpers/mail/plain_text_content.py index 1a3c00191..1380bc84d 100644 --- a/sendgrid/helpers/mail/plain_text_content.py +++ b/sendgrid/helpers/mail/plain_text_content.py @@ -6,15 +6,28 @@ class PlainTextContent(Content): """Plain text content to be included in your email. """ - def __init__(self, value): + def __init__(self, value, validator=None): """Create a PlainTextContent with the specified MIME type and value. :param value: The actual text content. - :type value: string, optional """ - self._validator = ValidateAPIKey() - self.type = "text/plain" - self.value = value + self._value = None + self._validator = None + + if value is not None: + self.value = value + if validator is not None: + self.validator = validator + else: + self.validator = ValidateAPIKey() + + @property + def type(self): + """The actual text content. + + :rtype: string + """ + return "text/plain" @property def value(self): @@ -26,9 +39,17 @@ def value(self): @value.setter def value(self, value): - self._validator.validate_message_dict(value) + self.validator.validate_message_dict(value) self._value = value + @property + def validator(self): + return self._validator + + @validator.setter + def validator(self, value): + self._validator = value + def get(self): """ Get a JSON-ready representation of this PlainTextContent. @@ -37,6 +58,6 @@ def get(self): :rtype: dict """ content = {} - content["type"] = "text/plain" - content["value"] = self._value + content["type"] = self.type + content["value"] = self.value return content diff --git a/sendgrid/helpers/mail/sandbox_mode.py b/sendgrid/helpers/mail/sandbox_mode.py index ce9d66935..72ca6be4a 100644 --- a/sendgrid/helpers/mail/sandbox_mode.py +++ b/sendgrid/helpers/mail/sandbox_mode.py @@ -10,7 +10,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): diff --git a/sendgrid/helpers/mail/section.py b/sendgrid/helpers/mail/section.py index ac20e85a6..664e38c60 100644 --- a/sendgrid/helpers/mail/section.py +++ b/sendgrid/helpers/mail/section.py @@ -3,8 +3,13 @@ class Section(object): def __init__(self, key=None, value=None): """Create a section with the given key and value.""" - self.key = key - self.value = value + 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): diff --git a/sendgrid/helpers/mail/spam_check.py b/sendgrid/helpers/mail/spam_check.py index 61eaa0cfc..d601628f3 100644 --- a/sendgrid/helpers/mail/spam_check.py +++ b/sendgrid/helpers/mail/spam_check.py @@ -11,9 +11,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): diff --git a/sendgrid/helpers/mail/subject.py b/sendgrid/helpers/mail/subject.py index 2a9841c16..da1022651 100644 --- a/sendgrid/helpers/mail/subject.py +++ b/sendgrid/helpers/mail/subject.py @@ -7,6 +7,8 @@ def __init__(self, subject): :param subject: The subject for an email :type subject: string """ + self._subject = None + self.subject = subject @property @@ -35,4 +37,4 @@ def get(self): :returns: This Subject, ready for use in a request body. :rtype: string """ - return self._subject + return self.subject diff --git a/sendgrid/helpers/mail/subscription_tracking.py b/sendgrid/helpers/mail/subscription_tracking.py index 204e427f0..2b8e3ab08 100644 --- a/sendgrid/helpers/mail/subscription_tracking.py +++ b/sendgrid/helpers/mail/subscription_tracking.py @@ -16,10 +16,19 @@ def __init__(self, enable=None, text=None, html=None, substitution_tag=None): :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): diff --git a/sendgrid/helpers/mail/substitution.py b/sendgrid/helpers/mail/substitution.py index a441ebe64..769191e08 100644 --- a/sendgrid/helpers/mail/substitution.py +++ b/sendgrid/helpers/mail/substitution.py @@ -11,8 +11,13 @@ def __init__(self, key=None, value=None): :param value: Value to substitute into email :type value: string, optional """ - self.key = key - self.value = value + 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): diff --git a/sendgrid/helpers/mail/validators.py b/sendgrid/helpers/mail/validators.py index 4ca49a08a..dcf431baa 100644 --- a/sendgrid/helpers/mail/validators.py +++ b/sendgrid/helpers/mail/validators.py @@ -1,6 +1,6 @@ from .exceptions import APIKeyIncludedException ################################################################ -# Various types of Validators +# Email content validators ################################################################ From 00260892fc02e300427975cb98a720ad462ce7db Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 29 Nov 2018 14:19:10 -0800 Subject: [PATCH 178/462] Send a Single Email to a Single Recipient --- TROUBLESHOOTING.md | 2 +- live_test.py | 3 +- sendgrid/helpers/mail/html_content.py | 12 +++++-- sendgrid/helpers/mail/mail.py | 38 +++++++++++++-------- sendgrid/helpers/mail/plain_text_content.py | 19 +++-------- 5 files changed, 41 insertions(+), 33 deletions(-) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 29c0c8e89..3535670d8 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -105,7 +105,7 @@ 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)) ``` diff --git a/live_test.py b/live_test.py index 97fa15816..cc4e1d124 100644 --- a/live_test.py +++ b/live_test.py @@ -1,5 +1,6 @@ # 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 @@ -11,6 +12,7 @@ try: sendgrid_client = SendGridAPIClient(apikey=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) @@ -21,4 +23,3 @@ # ToDo ## The Mail constructor should also support passing in tuples and strings -## The send function parameter should be better named, maybe email_message or simply message diff --git a/sendgrid/helpers/mail/html_content.py b/sendgrid/helpers/mail/html_content.py index 810280abe..051c807a6 100644 --- a/sendgrid/helpers/mail/html_content.py +++ b/sendgrid/helpers/mail/html_content.py @@ -17,6 +17,14 @@ def __init__(self, value = None): if value is not None: self.value = value + @property + def type(self): + """The actual text content. + + :rtype: string + """ + return "text/html" + @property def value(self): """The actual HTML content. @@ -38,6 +46,6 @@ def get(self): :rtype: dict """ content = {} - content["type"] = "text/html" - content["value"] = self._value + content["type"] = self.type + content["value"] = self.value return content diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 91d7976b1..c049c4275 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -8,17 +8,25 @@ class Mail(object): """Creates the response body for v3/mail/send""" def __init__( - self, from_email=None, subject=None, to_email=None, content=None): + self, + from_email=None, + subject=None, + to_emails=None, + plain_text_content=None, + html_content=None + ): """Create Mail object :param from_email: The email address of the sender - :type from_email: string, optional + :type from_email: From, optional :param subject: The subject of the email - :type subject: string, optional - :param to_email: The email address of the recipient - :type to_email: string, optional - :param content: The body of the email - :type content: string, optional + :type subject: Subject, optional + :param to_emails: The email address of the recipient + :type to_emails: string, 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 @@ -43,12 +51,14 @@ def __init__( self.from_email = from_email if subject is not None: self.subject = subject - if to_email is not None: + if to_emails is not None: personalization = Personalization() - personalization.add_to(to_email) + personalization.add_to(to_emails) self.add_personalization(personalization) - if content is not None: - self.add_content(content) + if plain_text_content is not None: + self.add_content(plain_text_content) + if html_content is not None: + self.add_content(html_content) def __str__(self): return str(self.get()) @@ -97,7 +107,7 @@ def contents(self): def add_content(self, content): # Text content should be before HTML content - if content._type == "text/plain": + if content.type == "text/plain": self._contents = self._ensure_insert(content, self._contents) else: self._contents = self._ensure_append(content, self._contents) @@ -214,7 +224,7 @@ def get(self): """ mail = { 'from': self._get_or_none(self.from_email), - 'subject': self.subject, + '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 []], @@ -245,7 +255,7 @@ def from_EmailMessage(cls, message): mail = cls( from_email=Email(message.get('From')), subject=message.get('Subject'), - to_email=Email(message.get('To')), + to_emails=Email(message.get('To')), ) try: body = message.get_content() diff --git a/sendgrid/helpers/mail/plain_text_content.py b/sendgrid/helpers/mail/plain_text_content.py index 1380bc84d..51070fe53 100644 --- a/sendgrid/helpers/mail/plain_text_content.py +++ b/sendgrid/helpers/mail/plain_text_content.py @@ -6,20 +6,17 @@ class PlainTextContent(Content): """Plain text content to be included in your email. """ - def __init__(self, value, validator=None): + def __init__(self, value): """Create a PlainTextContent with the specified MIME type and value. :param value: The actual text content. """ self._value = None - self._validator = None + self._validator = ValidateAPIKey() if value is not None: self.value = value - if validator is not None: - self.validator = validator - else: - self.validator = ValidateAPIKey() + @property def type(self): @@ -39,17 +36,9 @@ def value(self): @value.setter def value(self, value): - self.validator.validate_message_dict(value) + self._validator.validate_message_dict(value) self._value = value - @property - def validator(self): - return self._validator - - @validator.setter - def validator(self, value): - self._validator = value - def get(self): """ Get a JSON-ready representation of this PlainTextContent. From 284ebaa34544a900c221722bbda596a265a8aa8e Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 4 Dec 2018 10:32:54 -0800 Subject: [PATCH 179/462] Added Send a Single Email to Multiple Recipients Use Case --- docker-test/entrypoint.sh | 2 +- live_test.py | 90 +++++++++++++++++++++--- sendgrid/helpers/mail/email.py | 23 +++++- sendgrid/helpers/mail/mail.py | 45 ++++++++++-- sendgrid/helpers/mail/personalization.py | 2 +- test/test_mail.py | 2 +- 6 files changed, 145 insertions(+), 19 deletions(-) diff --git a/docker-test/entrypoint.sh b/docker-test/entrypoint.sh index d00023a44..e8ae30ddb 100755 --- a/docker-test/entrypoint.sh +++ b/docker-test/entrypoint.sh @@ -13,5 +13,5 @@ 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/live_test.py b/live_test.py index cc4e1d124..18246295a 100644 --- a/live_test.py +++ b/live_test.py @@ -1,22 +1,92 @@ -# Send a Single Email to a Single Recipient +## 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: +# sendgrid_client = SendGridAPIClient(apikey=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 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: +# sendgrid_client = SendGridAPIClient(apikey=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 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 +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('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')) + 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: sendgrid_client = SendGridAPIClient(apikey=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) + # response = sendgrid_client.send(message=message) + # print(response.status_code) + # print(response.body) + # print(response.headers) except SendGridException as e: print(e.message) diff --git a/sendgrid/helpers/mail/email.py b/sendgrid/helpers/mail/email.py index b27b7ef6e..e8f978ea2 100644 --- a/sendgrid/helpers/mail/email.py +++ b/sendgrid/helpers/mail/email.py @@ -21,7 +21,11 @@ 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): """Create an Email with the given address and name. Either fill the separate name and email fields, or pass all information @@ -33,6 +37,7 @@ def __init__(self, email=None, name=None): """ self._name = None self._email = None + self._substitutions = None if email and not name: # allows passing emails as "dude Fella " @@ -79,6 +84,22 @@ def email(self): def email(self, value): self._email = value + @property + def substitutions(self): + return self._substitutions + + @substitutions.setter + def substitutions(self, value): + self._substitutions = value + + @property + def subject(self): + return self._subject + + @subject.setter + def subject(self, value): + self._subject = value + def get(self): """ Get a JSON-ready representation of this Email. diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index c049c4275..30726a459 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -3,6 +3,7 @@ from .header import Header from .email import Email from .content import Content +from .subject import Subject class Mail(object): @@ -13,7 +14,9 @@ def __init__( subject=None, to_emails=None, plain_text_content=None, - html_content=None + html_content=None, + global_substitutions=None, + is_multiple=False ): """Create Mail object @@ -52,9 +55,38 @@ def __init__( if subject is not None: self.subject = subject if to_emails is not None: - personalization = Personalization() - personalization.add_to(to_emails) - self.add_personalization(personalization) + if is_multiple == True: + if isinstance(to_emails, list): + for email in to_emails: + personalization = Personalization() + personalization.add_to(email) + self.add_personalization(personalization) + else: + personalization = Personalization() + personalization.add_to(to_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: + personalization = Personalization() + if isinstance(to_emails, list): + for email in to_emails: + personalization.add_to(email) + else: + personalization.add_to(to_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) + self.add_personalization(personalization) if plain_text_content is not None: self.add_content(plain_text_content) if html_content is not None: @@ -200,7 +232,10 @@ def subject(self): @subject.setter def subject(self, value): - self._subject = value + if isinstance(value, Subject): + self._subject = value + else: + self._subject = Subject(value) @property def template_id(self): diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py index 49a779cf9..9cf4889ab 100644 --- a/sendgrid/helpers/mail/personalization.py +++ b/sendgrid/helpers/mail/personalization.py @@ -26,7 +26,7 @@ def tos(self): def tos(self, value): self._tos = value - def add_to(self, email): + def add_to(self, email, substitutions=None): """Add a single recipient to this Personalization. :type email: Email diff --git a/test/test_mail.py b/test/test_mail.py index 8ebe3eb2b..6d32d8af4 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -578,7 +578,7 @@ def test_from_emailmessage(self): message['From'] = 'test@example.com' message['To'] = 'test@sendgrid.com' mail = Mail.from_EmailMessage(message) - self.assertEqual(mail.subject, 'URGENT TITLE') + self.assertEqual(mail.subject.get(), 'URGENT TITLE') self.assertEqual(mail.from_email.email, 'test@example.com') self.assertEqual(len(mail.personalizations), 1) self.assertEqual(len(mail.personalizations[0].tos), 1) From 39acf5f2f602e34cf70fe6e1dbeeb04511e1ff26 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Mon, 10 Dec 2018 13:11:32 -0800 Subject: [PATCH 180/462] Send Multiple Emails to Multiple Recipients --- live_test.py | 8 ++++---- sendgrid/helpers/mail/email.py | 3 +++ sendgrid/helpers/mail/personalization.py | 6 +++++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/live_test.py b/live_test.py index 18246295a..3a8ffa798 100644 --- a/live_test.py +++ b/live_test.py @@ -83,10 +83,10 @@ try: sendgrid_client = SendGridAPIClient(apikey=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) + response = sendgrid_client.send(message=message) + print(response.status_code) + print(response.body) + print(response.headers) except SendGridException as e: print(e.message) diff --git a/sendgrid/helpers/mail/email.py b/sendgrid/helpers/mail/email.py index e8f978ea2..7d9877650 100644 --- a/sendgrid/helpers/mail/email.py +++ b/sendgrid/helpers/mail/email.py @@ -49,6 +49,9 @@ def __init__(self, if name is not None: self.name = name + + if substitutions is not None: + self.substitutions = substitutions @property def name(self): diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py index 9cf4889ab..cf2741faa 100644 --- a/sendgrid/helpers/mail/personalization.py +++ b/sendgrid/helpers/mail/personalization.py @@ -26,11 +26,15 @@ def tos(self): def tos(self, value): self._tos = value - def add_to(self, email, substitutions=None): + def add_to(self, email): """Add a single recipient to this Personalization. :type email: Email """ + if email.substitutions: + print(email.substitutions) + for substition in email.substitutions: + self.add_substitution(substition) self._tos.append(email.get()) @property From 43fc638d4dc9f060cd61b60c446de9a560bdfe02 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Sat, 15 Dec 2018 15:10:16 -0800 Subject: [PATCH 181/462] WIP - final use case update, kitchen sink --- live_test.py | 104 +++++++++++++------ sendgrid/helpers/mail/__init__.py | 2 + sendgrid/helpers/mail/bcc_email.py | 5 + sendgrid/helpers/mail/cc_email.py | 5 + sendgrid/helpers/mail/email.py | 12 ++- sendgrid/helpers/mail/mail.py | 121 ++++++++++++++++------- sendgrid/helpers/mail/personalization.py | 13 +++ 7 files changed, 194 insertions(+), 68 deletions(-) create mode 100644 sendgrid/helpers/mail/bcc_email.py create mode 100644 sendgrid/helpers/mail/cc_email.py diff --git a/live_test.py b/live_test.py index 3a8ffa798..f439e7d64 100644 --- a/live_test.py +++ b/live_test.py @@ -46,47 +46,93 @@ # except SendGridException as e: # print(e.message) -# Send Multiple Emails to Multiple Recipients +# # 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: +# sendgrid_client = SendGridAPIClient(apikey=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) + +# Kitchen Sink - an example with all settings used import os import json from sendgrid import SendGridAPIClient -from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException, Substitution +from sendgrid.helpers.mail import Mail, From, To, Cc, Bcc, Subject, PlainTextContent, HtmlContent, SendGridException, Substitution, Header 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'), - }) +message = Mail() + +message.to = To('elmer+test1@sendgrid.com', 'Example User1') +message.to = [ + To('elmer+test2@sendgrid.com', 'Example User2'), + To('elmer+test3@sendgrid.com', 'Example User3') ] -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) + +message.cc = Cc('test4@example.com', 'Example User4') +message.cc = [ + Cc('test5@example.com', 'Example User5'), + Cc('test6@example.com', 'Example User6') +] + +message.bcc = Bcc('test7@example.com', 'Example User7') +message.bcc = [ + Bcc('test8@example.com', 'Example User8'), + Bcc('test9@example.com', 'Example User9') +] + +# message.header = Header('X-Test1', 'Test1') +# message.header = Header('X-Test2', 'Test2') +# message.header = [ +# Header('X-Test3', 'Test3'), +# Header('X-Test4', 'Test4') +# ] try: sendgrid_client = SendGridAPIClient(apikey=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) + # response = sendgrid_client.send(message=message) + # print(response.status_code) + # print(response.body) + # print(response.headers) except SendGridException as e: print(e.message) diff --git a/sendgrid/helpers/mail/__init__.py b/sendgrid/helpers/mail/__init__.py index 7a26aa411..38f2ceb18 100644 --- a/sendgrid/helpers/mail/__init__.py +++ b/sendgrid/helpers/mail/__init__.py @@ -26,4 +26,6 @@ from .substitution import Substitution from .tracking_settings import TrackingSettings from .to_email import To +from .cc_email import Cc +from .bcc_email import Bcc from .validators import ValidateAPIKey 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/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/email.py b/sendgrid/helpers/mail/email.py index 7d9877650..ce22980fe 100644 --- a/sendgrid/helpers/mail/email.py +++ b/sendgrid/helpers/mail/email.py @@ -25,7 +25,8 @@ def __init__(self, email=None, name=None, substitutions=None, - subject=None): + subject=None, + p=None): """Create an Email with the given address and name. Either fill the separate name and email fields, or pass all information @@ -38,6 +39,7 @@ def __init__(self, self._name = None self._email = None self._substitutions = None + self._personalization = None if email and not name: # allows passing emails as "dude Fella " @@ -103,6 +105,14 @@ def subject(self): def subject(self, value): self._subject = value + @property + def p(self): + return self._personalization + + @p.setter + def p(self, value): + self._personalization = value + def get(self): """ Get a JSON-ready representation of this Email. diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 30726a459..02a5fa22a 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -5,7 +5,6 @@ from .content import Content from .subject import Subject - class Mail(object): """Creates the response body for v3/mail/send""" def __init__( @@ -36,7 +35,7 @@ def __init__( self._contents = None self._custom_args = None self._headers = None - self._personalizations = None + self._personalizations = [] self._sections = None self._asm = None self._batch_id = None @@ -55,38 +54,7 @@ def __init__( if subject is not None: self.subject = subject if to_emails is not None: - if is_multiple == True: - if isinstance(to_emails, list): - for email in to_emails: - personalization = Personalization() - personalization.add_to(email) - self.add_personalization(personalization) - else: - personalization = Personalization() - personalization.add_to(to_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: - personalization = Personalization() - if isinstance(to_emails, list): - for email in to_emails: - personalization.add_to(email) - else: - personalization.add_to(to_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) - self.add_personalization(personalization) + self._set_emails(to_emails, global_substitutions, is_multiple) if plain_text_content is not None: self.add_content(plain_text_content) if html_content is not None: @@ -95,9 +63,9 @@ def __init__( def __str__(self): return str(self.get()) - def _ensure_append(self, new_items, append_to): + def _ensure_append(self, new_items, append_to, index=0): append_to = append_to or [] - append_to.append(new_items) + append_to.insert(index, new_items) return append_to def _ensure_insert(self, new_items, insert_to): @@ -112,6 +80,75 @@ def _flatten_dicts(self, dicts): def _get_or_none(self, from_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): + # Send Multiple Emails to Multiple Recipients + if is_multiple == True: + if isinstance(emails, list): + for email in emails: + if p == 0 and self._personalizations[p] == None: + personalization = Personalization() + self.add_personalization(personalization, index=p) + else: + self._personalizations[p].add_email(email) + 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) + + @property + def to(self): + pass + + @to.setter + def to(self, to_emails, global_substitutions=None, is_multiple=False, p=0): + self._set_emails(to_emails, None, is_multiple=is_multiple, p=p) + + @property + def cc(self): + pass + + @cc.setter + def cc(self, bcc_emails, global_substitutions=None, is_multiple=False, p=0): + self._set_emails(bcc_emails, None, is_multiple=is_multiple, p=p) + + @property + def bcc(self): + pass + + @bcc.setter + def bcc(self, bcc_emails, global_substitutions=None, is_multiple=False, p=0): + self._set_emails(bcc_emails, None, is_multiple=is_multiple, p=p) + @property def attachments(self): return self._attachments @@ -148,6 +185,14 @@ def add_content(self, content): def headers(self): return self._headers + @property + def header(self): + pass + + @header.setter + def header(self, header): + self.add_header(header) + def add_header(self, header): if isinstance(header, dict): (k, v) = list(header.items())[0] @@ -159,9 +204,9 @@ def add_header(self, header): def personalizations(self): return self._personalizations - def add_personalization(self, personalizations): + def add_personalization(self, personalizations, index=0): self._personalizations = self._ensure_append( - personalizations, self._personalizations) + personalizations, self._personalizations, index) @property def sections(self): diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py index cf2741faa..5290460dd 100644 --- a/sendgrid/helpers/mail/personalization.py +++ b/sendgrid/helpers/mail/personalization.py @@ -14,6 +14,19 @@ def __init__(self): self._custom_args = [] self._send_at = 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): """A list of recipients for this Personalization. From 72654f7d2fc9066d3fd9eae3a8ea448ec561f87d Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Sat, 23 Mar 2019 12:27:44 -0700 Subject: [PATCH 182/462] kitchen sink example --- live_test.py | 203 +++++++++++++-- proposals/mail-helper-refactor.md | 15 +- sendgrid/helpers/mail/__init__.py | 35 ++- sendgrid/helpers/mail/asm.py | 8 +- sendgrid/helpers/mail/attachment.py | 82 +++---- sendgrid/helpers/mail/batch_id.py | 43 ++++ sendgrid/helpers/mail/bcc_settings.py | 9 +- sendgrid/helpers/mail/bcc_settings_email.py | 34 +++ sendgrid/helpers/mail/content.py | 1 + sendgrid/helpers/mail/content_id.py | 38 +++ sendgrid/helpers/mail/custom_arg.py | 16 +- sendgrid/helpers/mail/disposition.py | 46 ++++ sendgrid/helpers/mail/email.py | 21 +- sendgrid/helpers/mail/file_content.py | 34 +++ sendgrid/helpers/mail/file_name.py | 34 +++ sendgrid/helpers/mail/file_type.py | 34 +++ sendgrid/helpers/mail/footer_html.py | 34 +++ sendgrid/helpers/mail/footer_settings.py | 4 +- sendgrid/helpers/mail/footer_text.py | 34 +++ sendgrid/helpers/mail/ganalytics.py | 5 +- sendgrid/helpers/mail/group_id.py | 34 +++ sendgrid/helpers/mail/groups_to_display.py | 36 +++ sendgrid/helpers/mail/header.py | 15 +- sendgrid/helpers/mail/ip_pool_name.py | 34 +++ sendgrid/helpers/mail/mail.py | 231 ++++++++++++++++-- sendgrid/helpers/mail/mime_type.py | 5 + sendgrid/helpers/mail/open_tracking.py | 2 +- .../mail/open_tracking_substitution_tag.py | 38 +++ sendgrid/helpers/mail/reply_to.py | 5 + sendgrid/helpers/mail/send_at.py | 63 +++++ sendgrid/helpers/mail/spam_check.py | 4 +- sendgrid/helpers/mail/spam_threshold.py | 42 ++++ sendgrid/helpers/mail/spam_url.py | 37 +++ sendgrid/helpers/mail/subject.py | 15 +- sendgrid/helpers/mail/subscription_html.py | 37 +++ .../mail/subscription_substitution_tag.py | 40 +++ sendgrid/helpers/mail/subscription_text.py | 37 +++ .../helpers/mail/subscription_tracking.py | 6 +- sendgrid/helpers/mail/substitution.py | 17 +- sendgrid/helpers/mail/utm_campaign.py | 35 +++ sendgrid/helpers/mail/utm_content.py | 35 +++ sendgrid/helpers/mail/utm_medium.py | 35 +++ sendgrid/helpers/mail/utm_source.py | 36 +++ sendgrid/helpers/mail/utm_term.py | 35 +++ 44 files changed, 1487 insertions(+), 117 deletions(-) create mode 100644 sendgrid/helpers/mail/batch_id.py create mode 100644 sendgrid/helpers/mail/bcc_settings_email.py create mode 100644 sendgrid/helpers/mail/content_id.py create mode 100644 sendgrid/helpers/mail/disposition.py create mode 100644 sendgrid/helpers/mail/file_content.py create mode 100644 sendgrid/helpers/mail/file_name.py create mode 100644 sendgrid/helpers/mail/file_type.py create mode 100644 sendgrid/helpers/mail/footer_html.py create mode 100644 sendgrid/helpers/mail/footer_text.py create mode 100644 sendgrid/helpers/mail/group_id.py create mode 100644 sendgrid/helpers/mail/groups_to_display.py create mode 100644 sendgrid/helpers/mail/ip_pool_name.py create mode 100644 sendgrid/helpers/mail/mime_type.py create mode 100644 sendgrid/helpers/mail/open_tracking_substitution_tag.py create mode 100644 sendgrid/helpers/mail/reply_to.py create mode 100644 sendgrid/helpers/mail/send_at.py create mode 100644 sendgrid/helpers/mail/spam_threshold.py create mode 100644 sendgrid/helpers/mail/spam_url.py create mode 100644 sendgrid/helpers/mail/subscription_html.py create mode 100644 sendgrid/helpers/mail/subscription_substitution_tag.py create mode 100644 sendgrid/helpers/mail/subscription_text.py create mode 100644 sendgrid/helpers/mail/utm_campaign.py create mode 100644 sendgrid/helpers/mail/utm_content.py create mode 100644 sendgrid/helpers/mail/utm_medium.py create mode 100644 sendgrid/helpers/mail/utm_source.py create mode 100644 sendgrid/helpers/mail/utm_term.py diff --git a/live_test.py b/live_test.py index f439e7d64..0bb2fe1d5 100644 --- a/live_test.py +++ b/live_test.py @@ -95,36 +95,207 @@ import os import json from sendgrid import SendGridAPIClient -from sendgrid.helpers.mail import Mail, From, To, Cc, Bcc, Subject, PlainTextContent, HtmlContent, SendGridException, Substitution, Header +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() -message.to = To('elmer+test1@sendgrid.com', 'Example User1') +# Define Personalizations + +message.to = To('elmer+test1@sendgrid.com', 'Example User1', p=0) message.to = [ - To('elmer+test2@sendgrid.com', 'Example User2'), - To('elmer+test3@sendgrid.com', 'Example User3') + 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') +message.cc = Cc('test4@example.com', 'Example User4', p=0) message.cc = [ - Cc('test5@example.com', 'Example User5'), - Cc('test6@example.com', 'Example User6') + Cc('test5@example.com', 'Example User5', p=0), + Cc('test6@example.com', 'Example User6', p=0) ] -message.bcc = Bcc('test7@example.com', 'Example User7') +message.bcc = Bcc('test7@example.com', 'Example User7', p=0) message.bcc = [ - Bcc('test8@example.com', 'Example User8'), - Bcc('test9@example.com', 'Example User9') + Bcc('test8@example.com', 'Example User8', p=0), + Bcc('test9@example.com', 'Example User9', p=0) ] -# message.header = Header('X-Test1', 'Test1') -# message.header = Header('X-Test2', 'Test2') -# message.header = [ -# Header('X-Test3', 'Test3'), -# Header('X-Test4', 'Test4') -# ] +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(apikey=os.environ.get('SENDGRID_API_KEY')) diff --git a/proposals/mail-helper-refactor.md b/proposals/mail-helper-refactor.md index 68c3f403a..98991ad03 100644 --- a/proposals/mail-helper-refactor.md +++ b/proposals/mail-helper-refactor.md @@ -114,19 +114,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') ] @@ -138,13 +138,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 = [ @@ -223,7 +216,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') diff --git a/sendgrid/helpers/mail/__init__.py b/sendgrid/helpers/mail/__init__.py index 38f2ceb18..f4e6accff 100644 --- a/sendgrid/helpers/mail/__init__.py +++ b/sendgrid/helpers/mail/__init__.py @@ -1,31 +1,58 @@ -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 .email import Email from .exceptions import SendGridException, APIKeyIncludedException +from .file_content import FileContent +from .file_name import FileName +from .file_type import FileType from .footer_settings import FooterSettings +from .footer_text import FooterText +from .footer_html import FooterHtml from .from_email import From from .ganalytics import Ganalytics +from .group_id import GroupId +from .groups_to_display import GroupsToDisplay from .header import Header from .html_content import HtmlContent +from .ip_pool_name import IpPoolName from .mail_settings import MailSettings from .mail import Mail +from .mime_type import MimeType from .open_tracking import OpenTracking +from .open_tracking_substitution_tag import OpenTrackingSubstitutionTag from .personalization import Personalization from .plain_text_content import PlainTextContent +from .reply_to import ReplyTo from .sandbox_mode import SandBoxMode from .section import Section +from .send_at import SendAt from .spam_check import SpamCheck +from .spam_threshold import SpamThreshold +from .spam_url import SpamUrl from .subject import Subject from .subscription_tracking import SubscriptionTracking +from .subscription_text import SubscriptionText +from .subscription_html import SubscriptionHtml +from .subscription_substitution_tag import SubscriptionSubstitutionTag from .substitution import Substitution +from .template_id import TemplateId from .tracking_settings import TrackingSettings from .to_email import To -from .cc_email import Cc -from .bcc_email import Bcc +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..7ce76856e 100644 --- a/sendgrid/helpers/mail/asm.py +++ b/sendgrid/helpers/mail/asm.py @@ -1,4 +1,4 @@ -class ASM(object): +class Asm(object): """An object specifying unsubscribe behavior.""" def __init__(self, group_id=None, groups_to_display=None): @@ -41,7 +41,7 @@ def groups_to_display(self): @groups_to_display.setter def groups_to_display(self, value): - if value is not None and len(value) > 25: + if value is not None and len(value.get()) > 25: raise ValueError("New groups_to_display exceeds max length of 25.") self._groups_to_display = value @@ -54,8 +54,8 @@ def get(self): """ 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 b162b7609..62bee8498 100644 --- a/sendgrid/helpers/mail/attachment.py +++ b/sendgrid/helpers/mail/attachment.py @@ -1,15 +1,15 @@ class Attachment(object): """An attachment to be included with an email.""" - def __init__(self, content=None, type_=None, filename=None, disposition=None, content_id=None): + def __init__(self, file_content=None, file_type=None, file_name=None, disposition=None, content_id=None): """Create an Attachment - :param content: The Base64 encoded content of the attachment - :type content: string, optional - :param type: The MIME type of the content you are attaching - :type type string, optional - :param filename: The filename of the attachment - :type filename: string, optional + :param file_content: The Base64 encoded content of the attachment + :type file_content: string, optional + :param file_type: The MIME type of the content you are attaching + :type file_type string, optional + :param file_name: The filename of the attachment + :type file_name: 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 @@ -19,24 +19,24 @@ def __init__(self, content=None, type_=None, filename=None, disposition=None, co If unspecified, "attachment" is used. Must be one of the two choices. :type 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 + 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 = None - self._type = None - self._filename = None + self._file_content = None + self._file_type = None + self._file_name = None self._disposition = None self._content_id = None - if content is not None: - self.content = content + if file_content is not None: + self.file_content = file_content - if type_ is not None: - self.type = type_ + if file_type is not None: + self.file_type = file_type - if filename is not None: - self.filename = filename + if file_name is not None: + self.file_name = file_name if disposition is not None: self.disposition = disposition @@ -45,40 +45,40 @@ def __init__(self, content=None, type_=None, filename=None, disposition=None, co self.content_id = content_id @property - def content(self): + def file_content(self): """The Base64 encoded content of the attachment. :rtype: string """ - return self._content + return self._file_content - @content.setter - def content(self, value): - self._content = value + @file_content.setter + def file_content(self, value): + self._file_content = value @property - def type(self): + def file_type(self): """The MIME type of the content you are attaching. :rtype: string """ - return self._type + return self._file_type - @type.setter - def type(self, value): - self._type = value + @file_type.setter + def file_type(self, value): + self._file_type = value @property - def filename(self): - """The filename of the attachment. + def file_name(self): + """The file name of the attachment. :rtype: string """ - return self._filename + return self._file_name - @filename.setter - def filename(self, value): - self._filename = value + @file_name.setter + def file_name(self, value): + self._file_name = value @property def disposition(self): @@ -122,18 +122,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..dec83a63b --- /dev/null +++ b/sendgrid/helpers/mail/batch_id.py @@ -0,0 +1,43 @@ +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): + 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_settings.py b/sendgrid/helpers/mail/bcc_settings.py index c2456e989..79610122c 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,7 +11,7 @@ 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 = None self._email = None @@ -38,7 +38,7 @@ def enable(self, value): def email(self): """The email address that you would like to receive the BCC. - :rtype: Email + :rtype: string """ return self._email @@ -58,6 +58,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..00cc756b5 --- /dev/null +++ b/sendgrid/helpers/mail/bcc_settings_email.py @@ -0,0 +1,34 @@ +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): + 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 \ No newline at end of file diff --git a/sendgrid/helpers/mail/content.py b/sendgrid/helpers/mail/content.py index da4ed8027..48150b614 100644 --- a/sendgrid/helpers/mail/content.py +++ b/sendgrid/helpers/mail/content.py @@ -59,6 +59,7 @@ def get(self): :returns: This Content, ready for use in a request body. :rtype: dict """ + #TODO: text/plain must always come first content = {} if self.type is not None: content["type"] = self.type diff --git a/sendgrid/helpers/mail/content_id.py b/sendgrid/helpers/mail/content_id.py new file mode 100644 index 000000000..3e72b391b --- /dev/null +++ b/sendgrid/helpers/mail/content_id.py @@ -0,0 +1,38 @@ +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): + 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 aae6cdd68..cedbfd748 100644 --- a/sendgrid/helpers/mail/custom_arg.py +++ b/sendgrid/helpers/mail/custom_arg.py @@ -7,22 +7,26 @@ class CustomArg(object): Personalization. May not exceed 10,000 bytes. """ - def __init__(self, key=None, value=None): + 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 name: p is the Personalization object or Personalization object index + :type name: Personalization or 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): @@ -45,6 +49,14 @@ def value(self): def value(self, value): self._value = value + @property + def personalization(self): + return self._personalization + + @personalization.setter + def personalization(self, value): + 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..adadb1719 --- /dev/null +++ b/sendgrid/helpers/mail/disposition.py @@ -0,0 +1,46 @@ +class Disposition(object): + """The MIME type of the content you are attaching to an Attachment content.""" + + 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): + 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/email.py b/sendgrid/helpers/mail/email.py index ce22980fe..961277127 100644 --- a/sendgrid/helpers/mail/email.py +++ b/sendgrid/helpers/mail/email.py @@ -26,19 +26,22 @@ def __init__(self, name=None, substitutions=None, subject=None, - p=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 name: p is the Personalization object or Personalization object index + :type name: Personalization or integer, optional """ self._name = None self._email = None self._substitutions = None + self._subject = None self._personalization = None if email and not name: @@ -54,6 +57,12 @@ def __init__(self, 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): @@ -106,11 +115,11 @@ def subject(self, value): self._subject = value @property - def p(self): + def personalization(self): return self._personalization - @p.setter - def p(self, value): + @personalization.setter + def personalization(self, value): self._personalization = value def get(self): diff --git a/sendgrid/helpers/mail/file_content.py b/sendgrid/helpers/mail/file_content.py new file mode 100644 index 000000000..79414646f --- /dev/null +++ b/sendgrid/helpers/mail/file_content.py @@ -0,0 +1,34 @@ +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): + 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..29ea0d5f6 --- /dev/null +++ b/sendgrid/helpers/mail/file_name.py @@ -0,0 +1,34 @@ +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): + 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..7204332f4 --- /dev/null +++ b/sendgrid/helpers/mail/file_type.py @@ -0,0 +1,34 @@ +class FileType(object): + """The MIME type of the content you are attaching to an Attachment content.""" + + 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, value): + self._file_type = value + + 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..3a60331ce --- /dev/null +++ b/sendgrid/helpers/mail/footer_html.py @@ -0,0 +1,34 @@ +class FooterHtml(object): + """The FooterHtml of an Attachment.""" + + 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, value): + self._footer_html = value + + 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 \ No newline at end of file diff --git a/sendgrid/helpers/mail/footer_settings.py b/sendgrid/helpers/mail/footer_settings.py index 980da575e..39b7c56b2 100644 --- a/sendgrid/helpers/mail/footer_settings.py +++ b/sendgrid/helpers/mail/footer_settings.py @@ -72,8 +72,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..3082942f9 --- /dev/null +++ b/sendgrid/helpers/mail/footer_text.py @@ -0,0 +1,34 @@ +class FooterText(object): + """The FooterText of 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): + 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 \ No newline at end of file diff --git a/sendgrid/helpers/mail/ganalytics.py b/sendgrid/helpers/mail/ganalytics.py index 61b881dd0..302b2ded7 100644 --- a/sendgrid/helpers/mail/ganalytics.py +++ b/sendgrid/helpers/mail/ganalytics.py @@ -136,6 +136,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..56941e60b --- /dev/null +++ b/sendgrid/helpers/mail/group_id.py @@ -0,0 +1,34 @@ +class GroupId(object): + """The unsubscribe group 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): + 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 \ No newline at end of file diff --git a/sendgrid/helpers/mail/groups_to_display.py b/sendgrid/helpers/mail/groups_to_display.py new file mode 100644 index 000000000..e8df7830d --- /dev/null +++ b/sendgrid/helpers/mail/groups_to_display.py @@ -0,0 +1,36 @@ +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 of integers + """ + return self._groups_to_display + + @groups_to_display.setter + def groups_to_display(self, value): + 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 \ No newline at end of file diff --git a/sendgrid/helpers/mail/header.py b/sendgrid/helpers/mail/header.py index 7c031465d..dd45edc67 100644 --- a/sendgrid/helpers/mail/header.py +++ b/sendgrid/helpers/mail/header.py @@ -7,21 +7,26 @@ 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 or 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): @@ -46,6 +51,14 @@ def value(self): @value.setter def value(self, value): self._value = value + + @property + def personalization(self): + return self._personalization + + @personalization.setter + def personalization(self, value): + self._personalization = value def get(self): """ diff --git a/sendgrid/helpers/mail/ip_pool_name.py b/sendgrid/helpers/mail/ip_pool_name.py new file mode 100644 index 000000000..4763bdd8a --- /dev/null +++ b/sendgrid/helpers/mail/ip_pool_name.py @@ -0,0 +1,34 @@ +class IpPoolName(object): + """The IpPoolName of an Attachment.""" + + 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): + 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 \ No newline at end of file diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 02a5fa22a..36f473879 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -4,6 +4,8 @@ from .email import Email from .content import Content from .subject import Subject +from .custom_arg import CustomArg +from .send_at import SendAt class Mail(object): """Creates the response body for v3/mail/send""" @@ -123,7 +125,7 @@ def _set_emails(self, emails, global_substitutions=None, is_multiple=False, p=0) personalization.add_substitution(global_substitutions) if not has_internal_personalization: - self.add_personalization(personalization) + self.add_personalization(personalization, index=p) @property def to(self): @@ -131,6 +133,15 @@ def to(self): @to.setter def to(self, to_emails, global_substitutions=None, is_multiple=False, p=0): + if isinstance(to_emails, list): + for email in to_emails: + self.add_to(email, global_substitutions, is_multiple, p) + else: + self.add_to(to_emails, global_substitutions, is_multiple, p) + + def add_to(self, to_emails, global_substitutions=None, is_multiple=False, p=0): + if isinstance(to_emails, Email): + p = to_emails.personalization self._set_emails(to_emails, None, is_multiple=is_multiple, p=p) @property @@ -138,8 +149,17 @@ def cc(self): pass @cc.setter - def cc(self, bcc_emails, global_substitutions=None, is_multiple=False, p=0): - self._set_emails(bcc_emails, None, is_multiple=is_multiple, p=p) + def cc(self, cc_emails, global_substitutions=None, is_multiple=False, p=0): + if isinstance(cc_emails, list): + for email in cc_emails: + self.add_cc(email, global_substitutions, is_multiple, p) + else: + self.add_cc(cc_emails, global_substitutions, is_multiple, p) + + def add_cc(self, cc_emails, global_substitutions=None, is_multiple=False, p=0): + if isinstance(cc_emails, Email): + p = cc_emails.personalization + self._set_emails(cc_emails, None, is_multiple=is_multiple, p=p) @property def bcc(self): @@ -147,11 +167,33 @@ def bcc(self): @bcc.setter def bcc(self, bcc_emails, global_substitutions=None, is_multiple=False, p=0): + if isinstance(bcc_emails, list): + for email in bcc_emails: + self.add_bcc(email, global_substitutions, is_multiple, p) + else: + self.add_bcc(bcc_emails, global_substitutions, is_multiple, p) + + def add_bcc(self, bcc_emails, global_substitutions=None, is_multiple=False, p=0): + if isinstance(bcc_emails, Email): + p = bcc_emails.personalization self._set_emails(bcc_emails, None, is_multiple=is_multiple, p=p) @property def attachments(self): return self._attachments + + @property + def attachment(self): + pass + + @attachment.setter + def attachment(self, attachment): + #TODO: refactor duplicate code + if isinstance(attachment, list): + for a in attachment: + self.add_attachment(a) + else: + self.add_attachment(attachment) def add_attachment(self, attachment): self._attachments = self._ensure_append(attachment, self._attachments) @@ -160,6 +202,19 @@ def add_attachment(self, attachment): def categories(self): return self._categories + @property + def category(self): + pass + + @category.setter + def category(self, category): + #TODO: refactor duplicate code + if isinstance(category, list): + for c in category: + self.add_category(c) + else: + self.add_category(category) + def add_category(self, category): self._categories = self._ensure_append(category, self._categories) @@ -167,13 +222,60 @@ def add_category(self, category): def custom_args(self): return self._custom_args + @property + def custom_arg(self): + return self._custom_args + + @custom_arg.setter + def custom_arg(self, custom_arg): + if isinstance(custom_arg, list): + for c in custom_arg: + self.add_custom_arg(c) + else: + self.add_custom_arg(custom_arg) + def add_custom_arg(self, custom_arg): - self._custom_args = self._ensure_append(custom_arg, self._custom_args) + if custom_arg.personalization is not None: + #TODO: refactor duplicate code + 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 contents(self): return self._contents + @property + def content(self): + pass + + @content.setter + def content(self, content): + #TODO: refactor duplicate code + if isinstance(content, list): + for c in content: + self.add_content(c) + else: + self.add_content(content) + def add_content(self, content): # Text content should be before HTML content if content.type == "text/plain": @@ -191,14 +293,36 @@ def header(self): @header.setter def header(self, header): - self.add_header(header) + if isinstance(header, list): + for h in header: + self.add_header(h) + else: + self.add_header(header) def add_header(self, header): - 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) + if header.personalization is not None: + #TODO: refactor duplicate code + 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 personalizations(self): @@ -209,12 +333,60 @@ def add_personalization(self, personalizations, index=0): personalizations, self._personalizations, index) @property + #TODO: Order the properties according to the documentation def sections(self): return self._sections + @property + def section(self): + pass + + @section.setter + def section(self, section): + if isinstance(section, list): + for h in section: + self.add_section(h) + else: + self.add_section(section) + def add_section(self, section): self._sections = self._ensure_append(section, self._sections) + @property + def substitution(self): + pass + + @substitution.setter + def substitution(self, substitution): + if isinstance(substitution, list): + for s in substitution: + self.add_substitution(s) + else: + self.add_substitution(substitution) + + def add_substitution(self, substitution): + if substitution.personalization: + #TODO: refactor duplicate code + 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: + #TODO: refactor duplicate code + 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) + @property def asm(self): return self._asm @@ -269,7 +441,22 @@ def send_at(self): @send_at.setter def send_at(self, value): - self._send_at = value + 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 subject(self): @@ -278,7 +465,19 @@ def subject(self): @subject.setter def subject(self, value): if isinstance(value, Subject): - self._subject = value + 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) @@ -308,15 +507,15 @@ def get(self): '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.template_id, + '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.send_at, - 'batch_id': self.batch_id, + '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.ip_pool_name, + '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), diff --git a/sendgrid/helpers/mail/mime_type.py b/sendgrid/helpers/mail/mime_type.py new file mode 100644 index 000000000..52dffb77a --- /dev/null +++ b/sendgrid/helpers/mail/mime_type.py @@ -0,0 +1,5 @@ +class MimeType(object): + """The MIME type of the content of your email. + """ + text = "text/plain" + html = "text/html" \ No newline at end of file diff --git a/sendgrid/helpers/mail/open_tracking.py b/sendgrid/helpers/mail/open_tracking.py index 636f6c968..194b22f61 100644 --- a/sendgrid/helpers/mail/open_tracking.py +++ b/sendgrid/helpers/mail/open_tracking.py @@ -60,5 +60,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..f2cf32a0f --- /dev/null +++ b/sendgrid/helpers/mail/open_tracking_substitution_tag.py @@ -0,0 +1,38 @@ +class OpenTrackingSubstitutionTag(object): + #TODO: Make sure these are all consistent + """The OpenTrackingSubstitutionTag of an SubscriptionTracking.""" + + 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): + 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 \ No newline at end of file diff --git a/sendgrid/helpers/mail/reply_to.py b/sendgrid/helpers/mail/reply_to.py new file mode 100644 index 000000000..faf90f674 --- /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.""" \ No newline at end of file diff --git a/sendgrid/helpers/mail/send_at.py b/sendgrid/helpers/mail/send_at.py new file mode 100644 index 000000000..0190f6300 --- /dev/null +++ b/sendgrid/helpers/mail/send_at.py @@ -0,0 +1,63 @@ +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 or 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): + self._send_at = value + + @property + def personalization(self): + return self._personalization + + @personalization.setter + def personalization(self, value): + 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 d601628f3..d2894136e 100644 --- a/sendgrid/helpers/mail/spam_check.py +++ b/sendgrid/helpers/mail/spam_check.py @@ -73,8 +73,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..0e99b7d57 --- /dev/null +++ b/sendgrid/helpers/mail/spam_threshold.py @@ -0,0 +1,42 @@ +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): + 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 \ No newline at end of file diff --git a/sendgrid/helpers/mail/spam_url.py b/sendgrid/helpers/mail/spam_url.py new file mode 100644 index 000000000..676a7cce2 --- /dev/null +++ b/sendgrid/helpers/mail/spam_url.py @@ -0,0 +1,37 @@ +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): + 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 \ No newline at end of file diff --git a/sendgrid/helpers/mail/subject.py b/sendgrid/helpers/mail/subject.py index da1022651..a39cfe34d 100644 --- a/sendgrid/helpers/mail/subject.py +++ b/sendgrid/helpers/mail/subject.py @@ -1,15 +1,20 @@ class Subject(object): """A subject for an email message.""" - def __init__(self, subject): + 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 or integer, optional """ self._subject = None + self._personalization = None self.subject = subject + if p is not None: + self.personalization = p @property def subject(self): @@ -23,6 +28,14 @@ def subject(self): def subject(self, value): self._subject = value + @property + def personalization(self): + return self._personalization + + @personalization.setter + def personalization(self, value): + self._personalization = value + def __str__(self): """Get a JSON representation of this Mail request. diff --git a/sendgrid/helpers/mail/subscription_html.py b/sendgrid/helpers/mail/subscription_html.py new file mode 100644 index 000000000..9e19678fa --- /dev/null +++ b/sendgrid/helpers/mail/subscription_html.py @@ -0,0 +1,37 @@ +class SubscriptionHtml(object): + """The SubscriptionHtml 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): + 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 \ No newline at end of file diff --git a/sendgrid/helpers/mail/subscription_substitution_tag.py b/sendgrid/helpers/mail/subscription_substitution_tag.py new file mode 100644 index 000000000..5e5ab93eb --- /dev/null +++ b/sendgrid/helpers/mail/subscription_substitution_tag.py @@ -0,0 +1,40 @@ +class SubscriptionSubstitutionTag(object): + """The SubscriptionSubstitutionTag 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): + 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 \ No newline at end of file diff --git a/sendgrid/helpers/mail/subscription_text.py b/sendgrid/helpers/mail/subscription_text.py new file mode 100644 index 000000000..2b36829c2 --- /dev/null +++ b/sendgrid/helpers/mail/subscription_text.py @@ -0,0 +1,37 @@ +class SubscriptionText(object): + """The SubscriptionText 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): + 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 \ No newline at end of file diff --git a/sendgrid/helpers/mail/subscription_tracking.py b/sendgrid/helpers/mail/subscription_tracking.py index 2b8e3ab08..22e207a07 100644 --- a/sendgrid/helpers/mail/subscription_tracking.py +++ b/sendgrid/helpers/mail/subscription_tracking.py @@ -92,11 +92,11 @@ 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 769191e08..98ad4b3f6 100644 --- a/sendgrid/helpers/mail/substitution.py +++ b/sendgrid/helpers/mail/substitution.py @@ -3,21 +3,28 @@ 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 or integer + :param name: p is the Personalization object or Personalization object index + :type name: Personalization or 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): @@ -35,6 +42,14 @@ def value(self): def value(self, value): self._value = value + @property + def personalization(self): + return self._personalization + + @personalization.setter + def personalization(self, value): + self._personalization = value + def get(self): """ Get a JSON-ready representation of this Substitution. diff --git a/sendgrid/helpers/mail/utm_campaign.py b/sendgrid/helpers/mail/utm_campaign.py new file mode 100644 index 000000000..045e1dc78 --- /dev/null +++ b/sendgrid/helpers/mail/utm_campaign.py @@ -0,0 +1,35 @@ +class UtmCampaign(object): + """The UtmCampaign of an Ganalytics.""" + + 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): + 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 \ No newline at end of file diff --git a/sendgrid/helpers/mail/utm_content.py b/sendgrid/helpers/mail/utm_content.py new file mode 100644 index 000000000..e16bc1fd7 --- /dev/null +++ b/sendgrid/helpers/mail/utm_content.py @@ -0,0 +1,35 @@ +class UtmContent(object): + """The UtmContent of an Ganalytics.""" + + 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): + 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 \ No newline at end of file diff --git a/sendgrid/helpers/mail/utm_medium.py b/sendgrid/helpers/mail/utm_medium.py new file mode 100644 index 000000000..328c81eb9 --- /dev/null +++ b/sendgrid/helpers/mail/utm_medium.py @@ -0,0 +1,35 @@ +class UtmMedium(object): + """The UtmMedium of an Ganalytics.""" + + 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): + 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 \ No newline at end of file diff --git a/sendgrid/helpers/mail/utm_source.py b/sendgrid/helpers/mail/utm_source.py new file mode 100644 index 000000000..55b6cd769 --- /dev/null +++ b/sendgrid/helpers/mail/utm_source.py @@ -0,0 +1,36 @@ +class UtmSource(object): + """The UtmSource of an Ganalytics.""" + + 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): + 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 \ No newline at end of file diff --git a/sendgrid/helpers/mail/utm_term.py b/sendgrid/helpers/mail/utm_term.py new file mode 100644 index 000000000..04fadb044 --- /dev/null +++ b/sendgrid/helpers/mail/utm_term.py @@ -0,0 +1,35 @@ +class UtmTerm(object): + """The UtmTerm of an Ganalytics.""" + + 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): + 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 \ No newline at end of file From 74ec42ce86244402cd835b994fb72a5c5f7c2a45 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Sat, 23 Mar 2019 13:51:56 -0700 Subject: [PATCH 183/462] kitchen sink example --- .gitignore | 2 +- sendgrid/helpers/mail/template_id.py | 34 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 sendgrid/helpers/mail/template_id.py diff --git a/.gitignore b/.gitignore index dd43e4a8d..7bd21033d 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,6 @@ README.txt .coverage coverage.xml htmlcov -temp*.py +temp.py .vscode sendgrid/VERSION.txt diff --git a/sendgrid/helpers/mail/template_id.py b/sendgrid/helpers/mail/template_id.py new file mode 100644 index 000000000..50b8cc9ff --- /dev/null +++ b/sendgrid/helpers/mail/template_id.py @@ -0,0 +1,34 @@ +class TemplateId(object): + """The TemplateId of an Attachment.""" + + 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): + 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 From 557378b4a8b0ddd8f26440718db3b010c24776f5 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Sat, 23 Mar 2019 17:03:35 -0700 Subject: [PATCH 184/462] Testing everything but the kitchen sink --- LICENSE.txt | 2 +- live_test.py | 450 ++++---- sendgrid/helpers/mail/__init__.py | 4 +- sendgrid/helpers/mail/content.py | 4 +- sendgrid/helpers/mail/exceptions.py | 2 +- sendgrid/helpers/mail/html_content.py | 4 +- sendgrid/helpers/mail/mail.py | 16 +- sendgrid/helpers/mail/personalization.py | 1 - sendgrid/helpers/mail/plain_text_content.py | 4 +- sendgrid/helpers/mail/validators.py | 10 +- test/test_mail.py | 1114 +++++++++++-------- test/test_sendgrid.py | 4 +- 12 files changed, 882 insertions(+), 733 deletions(-) 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/live_test.py b/live_test.py index 0bb2fe1d5..22d759312 100644 --- a/live_test.py +++ b/live_test.py @@ -1,4 +1,4 @@ -## Send a Single Email to a Single Recipient +# ## Send a Single Email to a Single Recipient # import os # import json # from sendgrid import SendGridAPIClient @@ -11,8 +11,8 @@ # html_content=HtmlContent('and easy to do anywhere, even with Python')) # try: -# sendgrid_client = SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) # print(json.dumps(message.get(), sort_keys=True, indent=4)) +# sendgrid_client = SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) # response = sendgrid_client.send(message=message) # print(response.status_code) # print(response.body) @@ -37,52 +37,8 @@ # html_content=HtmlContent('and easy to do anywhere, even with Python')) # try: -# sendgrid_client = SendGridAPIClient(apikey=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 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: # sendgrid_client = SendGridAPIClient(apikey=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) @@ -90,222 +46,266 @@ # except SendGridException as e: # print(e.message) -# Kitchen Sink - an example with all settings used +# Send Multiple Emails to Multiple Recipients 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) +from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException, Substitution import time import datetime -message = Mail() +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) -# Define Personalizations +try: + print(json.dumps(message.get(), sort_keys=True, indent=4)) + # sendgrid_client = SendGridAPIClient(apikey=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) -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) -] +# # Kitchen Sink - an example with all settings used -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) -] +# 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.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 = Mail() -message.subject = Subject('Sending with SendGrid is Fun 0', p=0) +# # Define Personalizations -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.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.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.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.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.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.send_at = SendAt(1461775051, p=0) +# message.subject = Subject('Sending with SendGrid is Fun 0', 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.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.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.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.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.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.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.send_at = SendAt(1461775051, p=0) -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.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.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.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.send_at = SendAt(1461775052, 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.subject = Subject('Sending with SendGrid is Fun 1', 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) +# ] -# The values below this comment are global to entire message +# 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.from_email = From('dx@sendgrid.com', 'DX') +# 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.reply_to = ReplyTo('dx_reply@sendgrid.com', 'DX Reply') +# message.send_at = SendAt(1461775052, p=1) -message.subject = Subject('Sending with SendGrid is Fun 2') +# message.subject = Subject('Sending with SendGrid is Fun 1', p=1) -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!!') -] +# # The values below this comment are global to entire message -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.from_email = From('dx@sendgrid.com', 'DX') -message.template_id = TemplateId('13b8f94f-bcae-4ec6-b752-70d6cb59f932') +# message.reply_to = ReplyTo('dx_reply@sendgrid.com', 'DX Reply') -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.subject = Subject('Sending with SendGrid is Fun 2') -message.header = Header('X-Test9', 'Test9') -message.header = Header('X-Test10', 'Test10') -message.header = [ - Header('X-Test11', 'Test11'), - Header('X-Test12', 'Test12') -] +# 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.category = Category('Category 1') -message.category = Category('Category 2') -message.category = [ - Category('Category 1'), - Category('Category 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.custom_arg = CustomArg('marketing5', 'false') -message.custom_arg = CustomArg('transactional5', 'true') -message.custom_arg = [ - CustomArg('marketing6', 'true'), - CustomArg('transactional6', 'false') -] +# message.template_id = TemplateId('13b8f94f-bcae-4ec6-b752-70d6cb59f932') -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 +# 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') +# ] -try: - sendgrid_client = SendGridAPIClient(apikey=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) +# 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(apikey=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) # ToDo diff --git a/sendgrid/helpers/mail/__init__.py b/sendgrid/helpers/mail/__init__.py index f4e6accff..ccf88702b 100644 --- a/sendgrid/helpers/mail/__init__.py +++ b/sendgrid/helpers/mail/__init__.py @@ -13,7 +13,7 @@ from .custom_arg import CustomArg from .disposition import Disposition 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 @@ -55,4 +55,4 @@ from .utm_term import UtmTerm from .utm_content import UtmContent from .utm_campaign import UtmCampaign -from .validators import ValidateAPIKey +from .validators import ValidateApiKey diff --git a/sendgrid/helpers/mail/content.py b/sendgrid/helpers/mail/content.py index 48150b614..99a41a2f2 100644 --- a/sendgrid/helpers/mail/content.py +++ b/sendgrid/helpers/mail/content.py @@ -1,4 +1,4 @@ -from .validators import ValidateAPIKey +from .validators import ValidateApiKey class Content(object): @@ -17,7 +17,7 @@ def __init__(self, type_=None, value=None): """ self._type = None self._value = None - self._validator = ValidateAPIKey() + self._validator = ValidateApiKey() if type_ is not None: self.type = type_ diff --git a/sendgrid/helpers/mail/exceptions.py b/sendgrid/helpers/mail/exceptions.py index 1f48d4f64..7f44abd84 100644 --- a/sendgrid/helpers/mail/exceptions.py +++ b/sendgrid/helpers/mail/exceptions.py @@ -8,7 +8,7 @@ class SendGridException(Exception): pass -class APIKeyIncludedException(SendGridException): +class ApiKeyIncludedException(SendGridException): """Exception raised for when SendGrid API Key included in message text""" def __init__(self, diff --git a/sendgrid/helpers/mail/html_content.py b/sendgrid/helpers/mail/html_content.py index 051c807a6..91aedb64c 100644 --- a/sendgrid/helpers/mail/html_content.py +++ b/sendgrid/helpers/mail/html_content.py @@ -1,5 +1,5 @@ from .content import Content -from .validators import ValidateAPIKey +from .validators import ValidateApiKey class HtmlContent(Content): @@ -12,7 +12,7 @@ def __init__(self, value = None): :type value: string, optional """ self._value = None - self._validator = ValidateAPIKey() + self._validator = ValidateApiKey() if value is not None: self.value = value diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 36f473879..5b1ebebed 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -1,4 +1,5 @@ """v3/mail/send response body builder""" +from collections import OrderedDict from .personalization import Personalization from .header import Header from .email import Email @@ -6,6 +7,7 @@ from .subject import Subject from .custom_arg import CustomArg from .send_at import SendAt +from .mime_type import MimeType class Mail(object): """Creates the response body for v3/mail/send""" @@ -87,11 +89,9 @@ def _set_emails(self, emails, global_substitutions=None, is_multiple=False, p=0) if is_multiple == True: if isinstance(emails, list): for email in emails: - if p == 0 and self._personalizations[p] == None: - personalization = Personalization() - self.add_personalization(personalization, index=p) - else: - self._personalizations[p].add_email(email) + personalization = Personalization() + personalization.add_email(email) + self.add_personalization(personalization) else: personalization = Personalization() personalization.add_email(emails) @@ -281,7 +281,11 @@ def add_content(self, content): if content.type == "text/plain": self._contents = self._ensure_insert(content, self._contents) else: - self._contents = self._ensure_append(content, self._contents) + if self._contents: + index = len(self._contents) + else: + index = 0 + self._contents = self._ensure_append(content, self._contents, index=index) @property def headers(self): diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py index 5290460dd..11fef6dbc 100644 --- a/sendgrid/helpers/mail/personalization.py +++ b/sendgrid/helpers/mail/personalization.py @@ -45,7 +45,6 @@ def add_to(self, email): :type email: Email """ if email.substitutions: - print(email.substitutions) for substition in email.substitutions: self.add_substitution(substition) self._tos.append(email.get()) diff --git a/sendgrid/helpers/mail/plain_text_content.py b/sendgrid/helpers/mail/plain_text_content.py index 51070fe53..a6051a5c9 100644 --- a/sendgrid/helpers/mail/plain_text_content.py +++ b/sendgrid/helpers/mail/plain_text_content.py @@ -1,5 +1,5 @@ from .content import Content -from .validators import ValidateAPIKey +from .validators import ValidateApiKey class PlainTextContent(Content): @@ -12,7 +12,7 @@ def __init__(self, value): :param value: The actual text content. """ self._value = None - self._validator = ValidateAPIKey() + self._validator = ValidateApiKey() if value is not None: self.value = value diff --git a/sendgrid/helpers/mail/validators.py b/sendgrid/helpers/mail/validators.py index dcf431baa..293d626d6 100644 --- a/sendgrid/helpers/mail/validators.py +++ b/sendgrid/helpers/mail/validators.py @@ -1,10 +1,10 @@ -from .exceptions import APIKeyIncludedException +from .exceptions import ApiKeyIncludedException ################################################################ # Email content validators ################################################################ -class ValidateAPIKey(object): +class ValidateApiKey(object): """Validates content to ensure SendGrid API key is not present""" regexes = None @@ -35,7 +35,7 @@ def validate_message_dict(self, request_body): request_body (:obj:`dict`): message parameter that is an argument to: mail.send.post() Raises: - APIKeyIncludedException: If any content in request_body matches regex + ApiKeyIncludedException: If any content in request_body matches regex """ # Handle string in edge-case @@ -60,10 +60,10 @@ def validate_message_text(self, message_string): Args: message_string (str): message that will be sent Raises: - APIKeyIncludedException: If message_string matches a regex string + 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/test/test_mail.py b/test/test_mail.py index 6d32d8af4..405f53fb0 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -10,10 +10,10 @@ EmailMessage = message.Message from sendgrid.helpers.mail import ( - ASM, - APIKeyIncludedException, + Asm, + ApiKeyIncludedException, Attachment, - BCCSettings, + BccSettings, BypassListManagement, Category, ClickTracking, @@ -34,13 +34,13 @@ SubscriptionTracking, Substitution, TrackingSettings, - ValidateAPIKey + ValidateApiKey ) class UnitTests(unittest.TestCase): - def test_sendgridAPIKey(self): + def test_sendgrid_api_key(self): """Tests if including SendGrid API will throw an Exception""" # Minimum required to send an email @@ -83,507 +83,653 @@ def test_sendgridAPIKey(self): 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")) + # 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( - 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"}' + message.get(), + { + "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" + }, ) - self.assertIsInstance(str(mail), str) - - def test_helloEmailAdditionalContent(self): + 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 - - """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")) + 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( - 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"}' + message.get(), + { + "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" + }, ) - self.assertIsInstance(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) + 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')) - 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) + message.get(), + { + "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_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" + + def test_multiple_emails_to_multiple_recipients(self): + from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException, Substitution + + 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'), }, - "personalizations": [ - { - "substitutions": { - "%city%": u"Αθήνα" - }, - "to": [ - { - "email": "test@example.com" - } - ] - } - ], - "subject": "Testing unicode substitutions with the SendGrid Python Library", - } - + 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( - json.dumps(mail.get(), sort_keys=True), - json.dumps(expected_result, sort_keys=True) + message.get(), + { + "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" + } + ] + }, + { + "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): + return + # 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): + return + # """ 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))) + return + # self.assertRaises(ValueError, Asm, 1, list(range(26))) def test_disable_tracking(self): - tracking_settings = TrackingSettings() - tracking_settings.click_tracking = ClickTracking(False, False) + return + # tracking_settings = TrackingSettings() + # tracking_settings.click_tracking = ClickTracking(False, False) - self.assertEqual( - tracking_settings.get(), - {'click_tracking': {'enable': False, 'enable_text': 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}] + return + # personalization = Personalization() + # personalization.substitutions = [{'a': 0}] def test_from_emailmessage(self): - message = EmailMessage() - body = 'message that is not urgent' - try: - message.set_content(body) - except AttributeError: - # Python2 - message.set_payload(body) - message.set_default_type('text/plain') - message['Subject'] = 'URGENT TITLE' - message['From'] = 'test@example.com' - message['To'] = 'test@sendgrid.com' - mail = Mail.from_EmailMessage(message) - self.assertEqual(mail.subject.get(), 'URGENT TITLE') - self.assertEqual(mail.from_email.email, 'test@example.com') - self.assertEqual(len(mail.personalizations), 1) - self.assertEqual(len(mail.personalizations[0].tos), 1) - self.assertEqual(mail.personalizations[0].tos[0], {'email': 'test@sendgrid.com'}) - self.assertEqual(len(mail.contents), 1) - content = mail.contents[0] - self.assertEqual(content.type, 'text/plain') - self.assertEqual(content.value, 'message that is not urgent') + return + # message = EmailMessage() + # body = 'message that is not urgent' + # try: + # message.set_content(body) + # except AttributeError: + # # Python2 + # message.set_payload(body) + # message.set_default_type('text/plain') + # message['Subject'] = 'URGENT TITLE' + # message['From'] = 'test@example.com' + # message['To'] = 'test@sendgrid.com' + # mail = Mail.from_EmailMessage(message) + # self.assertEqual(mail.subject.get(), 'URGENT TITLE') + # self.assertEqual(mail.from_email.email, 'test@example.com') + # self.assertEqual(len(mail.personalizations), 1) + # self.assertEqual(len(mail.personalizations[0].tos), 1) + # self.assertEqual(mail.personalizations[0].tos[0], {'email': 'test@sendgrid.com'}) + # self.assertEqual(len(mail.contents), 1) + # content = mail.contents[0] + # self.assertEqual(content.type, 'text/plain') + # self.assertEqual(content.value, 'message that is not urgent') diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index 78976edb0..25c46ff8e 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -131,8 +131,8 @@ def test_reset_request_headers(self): 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") + 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") From e6cf967b55356b1c0a4aa672acbc7bd7f1cdf934 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Sat, 23 Mar 2019 17:30:03 -0700 Subject: [PATCH 185/462] Add kitchen sink test --- live_test.py | 446 ++++++++++++------------- test/test_mail.py | 809 +++++++++++++++++++++++++++------------------- 2 files changed, 692 insertions(+), 563 deletions(-) diff --git a/live_test.py b/live_test.py index 22d759312..837b718f8 100644 --- a/live_test.py +++ b/live_test.py @@ -46,266 +46,266 @@ # 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(apikey=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 +# # Send Multiple Emails to Multiple Recipients # 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) +# from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException, Substitution # import time # import datetime -# message = Mail() +# 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) -# # Define Personalizations +# try: +# print(json.dumps(message.get(), sort_keys=True, indent=4)) +# sendgrid_client = SendGridAPIClient(apikey=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) -# 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) -# ] +# Kitchen Sink - an example with all settings used -# 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) -# ] +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.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 = Mail() -# message.subject = Subject('Sending with SendGrid is Fun 0', p=0) +# Define Personalizations -# 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.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.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.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.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.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.send_at = SendAt(1461775051, p=0) +message.subject = Subject('Sending with SendGrid is Fun 0', 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.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.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.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.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.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.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.send_at = SendAt(1461775051, p=0) -# 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.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.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.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.send_at = SendAt(1461775052, 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.subject = Subject('Sending with SendGrid is Fun 1', 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) +] -# # The values below this comment are global to entire message +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.from_email = From('dx@sendgrid.com', 'DX') +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.reply_to = ReplyTo('dx_reply@sendgrid.com', 'DX Reply') +message.send_at = SendAt(1461775052, p=1) -# message.subject = Subject('Sending with SendGrid is Fun 2') +message.subject = Subject('Sending with SendGrid is Fun 1', p=1) -# 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!!') -# ] +# The values below this comment are global to entire message -# 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.from_email = From('dx@sendgrid.com', 'DX') -# message.template_id = TemplateId('13b8f94f-bcae-4ec6-b752-70d6cb59f932') +message.reply_to = ReplyTo('dx_reply@sendgrid.com', 'DX Reply') -# 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.subject = Subject('Sending with SendGrid is Fun 2') -# message.header = Header('X-Test9', 'Test9') -# message.header = Header('X-Test10', 'Test10') -# message.header = [ -# Header('X-Test11', 'Test11'), -# Header('X-Test12', 'Test12') -# ] +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.category = Category('Category 1') -# message.category = Category('Category 2') -# message.category = [ -# Category('Category 1'), -# Category('Category 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.custom_arg = CustomArg('marketing5', 'false') -# message.custom_arg = CustomArg('transactional5', 'true') -# message.custom_arg = [ -# CustomArg('marketing6', 'true'), -# CustomArg('transactional6', 'false') -# ] +message.template_id = TemplateId('13b8f94f-bcae-4ec6-b752-70d6cb59f932') -# 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 +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') +] -# try: -# sendgrid_client = SendGridAPIClient(apikey=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) +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(apikey=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) # ToDo diff --git a/test/test_mail.py b/test/test_mail.py index 405f53fb0..174e222e5 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -95,7 +95,7 @@ def test_single_email_to_a_single_recipient(self): self.assertEqual( message.get(), - { + json.loads(r'''{ "content": [ { "type": "text/plain", @@ -121,7 +121,7 @@ def test_single_email_to_a_single_recipient(self): } ], "subject": "Sending with SendGrid is Fun" - }, + }''') ) def test_single_email_to_a_single_recipient_content_reversed(self): @@ -137,7 +137,7 @@ def test_single_email_to_a_single_recipient_content_reversed(self): self.assertEqual( message.get(), - { + json.loads(r'''{ "content": [ { "type": "text/plain", @@ -163,7 +163,7 @@ def test_single_email_to_a_single_recipient_content_reversed(self): } ], "subject": "Sending with SendGrid is Fun" - }, + }''') ) def test_send_a_single_email_to_multiple_recipients(self): @@ -181,7 +181,7 @@ def test_send_a_single_email_to_multiple_recipients(self): self.assertEqual( message.get(), - { + json.loads(r'''{ "content": [ { "type": "text/plain", @@ -211,11 +211,12 @@ def test_send_a_single_email_to_multiple_recipients(self): } ], "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, SendGridException, Substitution + self.maxDiff = None to_emails = [ To(email='test+to0@example.com', @@ -243,7 +244,7 @@ def test_multiple_emails_to_multiple_recipients(self): self.assertEqual( message.get(), - { + json.loads(r'''{ "content": [ { "type": "text/plain", @@ -287,351 +288,479 @@ def test_multiple_emails_to_multiple_recipients(self): } ], "subject": "Hi -name-" - } + }''') ) def test_kitchen_sink(self): - return - # self.max_diff = None + 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 + self.maxDiff = None - # """All settings set""" - # mail = Mail() + message = Mail() - # mail.from_email = Email("test@example.com", "Example User") + # Define Personalizations - # mail.subject = "Hello World from the SendGrid Python Library" + 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) + ] - # 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) + 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) + ] - # 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) + 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) + ] - # mail.add_content(Content("text/plain", "some text here")) - # mail.add_content( - # Content( - # "text/html", - # "some text here")) + message.subject = Subject('Sending with SendGrid is Fun 0', p=0) - # 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 + 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) + ] - # 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") + 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) + ] - # 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) - # ) + 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 + 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@sendgrid.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@sendgrid.com", + "name": "Example User1" + }, + { + "email": "test2@sendgrid.com", + "name": "Example User2" + }, + { + "email": "test3@sendgrid.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@sendgrid.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" + } + } + }''') + ) def test_unicode_values_in_substitutions_helper(self): return From c97ec64afb8f4d0a4436f3c242ec98e27e48af13 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Sat, 23 Mar 2019 18:41:14 -0700 Subject: [PATCH 186/462] Prep for review --- live_test.py | 436 +++++++++--------- sendgrid/helpers/mail/content.py | 1 - sendgrid/helpers/mail/mail.py | 355 +++++++------- .../mail/open_tracking_substitution_tag.py | 1 - 4 files changed, 388 insertions(+), 405 deletions(-) diff --git a/live_test.py b/live_test.py index 837b718f8..340bc2d1d 100644 --- a/live_test.py +++ b/live_test.py @@ -90,223 +90,219 @@ # 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(apikey=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) - -# ToDo - -## The Mail constructor should also support passing in tuples and strings +# # 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(apikey=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) diff --git a/sendgrid/helpers/mail/content.py b/sendgrid/helpers/mail/content.py index 99a41a2f2..33554dbee 100644 --- a/sendgrid/helpers/mail/content.py +++ b/sendgrid/helpers/mail/content.py @@ -59,7 +59,6 @@ def get(self): :returns: This Content, ready for use in a request body. :rtype: dict """ - #TODO: text/plain must always come first content = {} if self.type is not None: content["type"] = self.type diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 5b1ebebed..8306a999b 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -1,13 +1,13 @@ """v3/mail/send response body builder""" from collections import OrderedDict -from .personalization import Personalization -from .header import Header -from .email import Email from .content import Content -from .subject import Subject from .custom_arg import CustomArg -from .send_at import SendAt +from .email import Email +from .header import Header from .mime_type import MimeType +from .personalization import Personalization +from .send_at import SendAt +from .subject import Subject class Mail(object): """Creates the response body for v3/mail/send""" @@ -127,6 +127,14 @@ def _set_emails(self, emails, global_substitutions=None, is_multiple=False, p=0) if not has_internal_personalization: self.add_personalization(personalization, index=p) + @property + def personalizations(self): + return self._personalizations + + def add_personalization(self, personalizations, index=0): + self._personalizations = self._ensure_append( + personalizations, self._personalizations, index) + @property def to(self): pass @@ -179,44 +187,99 @@ def add_bcc(self, bcc_emails, global_substitutions=None, is_multiple=False, p=0) self._set_emails(bcc_emails, None, is_multiple=is_multiple, p=p) @property - def attachments(self): - return self._attachments - - @property - def attachment(self): - pass + def subject(self): + return self._subject - @attachment.setter - def attachment(self, attachment): - #TODO: refactor duplicate code - if isinstance(attachment, list): - for a in attachment: - self.add_attachment(a) + @subject.setter + def subject(self, value): + 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.add_attachment(attachment) + self._subject = Subject(value) - def add_attachment(self, attachment): - self._attachments = self._ensure_append(attachment, self._attachments) + @property + def headers(self): + return self._headers @property - def categories(self): - return self._categories + def header(self): + pass + + @header.setter + def header(self, header): + if isinstance(header, list): + for h in header: + self.add_header(h) + else: + self.add_header(header) + + def add_header(self, header): + 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 category(self): + def substitution(self): pass - - @category.setter - def category(self, category): - #TODO: refactor duplicate code - if isinstance(category, list): - for c in category: - self.add_category(c) + + @substitution.setter + def substitution(self, substitution): + if isinstance(substitution, list): + for s in substitution: + self.add_substitution(s) else: - self.add_category(category) + self.add_substitution(substitution) - def add_category(self, category): - self._categories = self._ensure_append(category, self._categories) + def add_substitution(self, 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) @property def custom_args(self): @@ -236,7 +299,6 @@ def custom_arg(self, custom_arg): def add_custom_arg(self, custom_arg): if custom_arg.personalization is not None: - #TODO: refactor duplicate code try: personalization = self._personalizations[custom_arg.personalization] has_internal_personalization = True @@ -258,6 +320,44 @@ def add_custom_arg(self, custom_arg): else: self._custom_args = self._ensure_append(custom_arg, self._custom_args) + @property + def send_at(self): + return self._send_at + + @send_at.setter + def send_at(self, value): + 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 from_email(self): + return self._from_email + + @from_email.setter + def from_email(self, value): + self._from_email = value + + @property + def reply_to(self): + return self._reply_to + + @reply_to.setter + def reply_to(self, value): + self._reply_to = value @property def contents(self): @@ -269,7 +369,6 @@ def content(self): @content.setter def content(self, content): - #TODO: refactor duplicate code if isinstance(content, list): for c in content: self.add_content(c) @@ -277,7 +376,6 @@ def content(self, content): self.add_content(content) def add_content(self, content): - # Text content should be before HTML content if content.type == "text/plain": self._contents = self._ensure_insert(content, self._contents) else: @@ -288,56 +386,33 @@ def add_content(self, content): self._contents = self._ensure_append(content, self._contents, index=index) @property - def headers(self): - return self._headers - + def attachments(self): + return self._attachments + @property - def header(self): + def attachment(self): pass - - @header.setter - def header(self, header): - if isinstance(header, list): - for h in header: - self.add_header(h) + + @attachment.setter + def attachment(self, attachment): + if isinstance(attachment, list): + for a in attachment: + self.add_attachment(a) else: - self.add_header(header) - - def add_header(self, header): - if header.personalization is not None: - #TODO: refactor duplicate code - 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) + self.add_attachment(attachment) + def add_attachment(self, attachment): + self._attachments = self._ensure_append(attachment, self._attachments) @property - def personalizations(self): - return self._personalizations - - def add_personalization(self, personalizations, index=0): - self._personalizations = self._ensure_append( - personalizations, self._personalizations, index) + def template_id(self): + return self._template_id + + @template_id.setter + def template_id(self, value): + self._template_id = value @property - #TODO: Order the properties according to the documentation def sections(self): return self._sections @@ -357,47 +432,23 @@ def add_section(self, section): self._sections = self._ensure_append(section, self._sections) @property - def substitution(self): - pass - - @substitution.setter - def substitution(self, substitution): - if isinstance(substitution, list): - for s in substitution: - self.add_substitution(s) - else: - self.add_substitution(substitution) - - def add_substitution(self, substitution): - if substitution.personalization: - #TODO: refactor duplicate code - 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: - #TODO: refactor duplicate code - 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) + def categories(self): + return self._categories @property - def asm(self): - return self._asm + def category(self): + pass - @asm.setter - def asm(self, value): - self._asm = value + @category.setter + def category(self, category): + if isinstance(category, list): + for c in category: + self.add_category(c) + else: + self.add_category(category) + + def add_category(self, category): + self._categories = self._ensure_append(category, self._categories) @property def batch_id(self): @@ -408,12 +459,12 @@ def batch_id(self, value): self._batch_id = value @property - def from_email(self): - return self._from_email + def asm(self): + return self._asm - @from_email.setter - def from_email(self, value): - self._from_email = value + @asm.setter + def asm(self, value): + self._asm = value @property def ip_pool_name(self): @@ -431,68 +482,6 @@ def mail_settings(self): def mail_settings(self, value): self._mail_settings = value - @property - def reply_to(self): - return self._reply_to - - @reply_to.setter - def reply_to(self, value): - self._reply_to = value - - @property - def send_at(self): - return self._send_at - - @send_at.setter - def send_at(self, value): - 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 subject(self): - return self._subject - - @subject.setter - def subject(self, value): - 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): - return self._template_id - - @template_id.setter - def template_id(self, value): - self._template_id = value - @property def tracking_settings(self): return self._tracking_settings diff --git a/sendgrid/helpers/mail/open_tracking_substitution_tag.py b/sendgrid/helpers/mail/open_tracking_substitution_tag.py index f2cf32a0f..83c8fedc9 100644 --- a/sendgrid/helpers/mail/open_tracking_substitution_tag.py +++ b/sendgrid/helpers/mail/open_tracking_substitution_tag.py @@ -1,5 +1,4 @@ class OpenTrackingSubstitutionTag(object): - #TODO: Make sure these are all consistent """The OpenTrackingSubstitutionTag of an SubscriptionTracking.""" def __init__(self, open_tracking_substitution_tag=None): From 3877a8f07a263ab71b74182822334a83b0510313 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 26 Mar 2019 11:41:33 -0700 Subject: [PATCH 187/462] Rolling up diverging changes from master branch --- .github/PULL_REQUEST_TEMPLATE | 2 +- .gitignore | 2 +- CHANGELOG.md | 8 +- CODE_OF_CONDUCT.md | 12 +-- CONTRIBUTING.md | 31 ++++-- README.rst | 4 +- TROUBLESHOOTING.md | 14 +-- USAGE.md | 34 +++--- docker-test/README.md | 2 +- docker/Dockerfile | 52 +++++---- docker/Makefile | 20 ++++ docker/USAGE.md | 4 +- docker/docker-compose.yml | 35 ++++++ docker/sendgrid.env | 8 ++ examples/helpers/README.md | 69 ++++++++++++ examples/helpers/mail/mail_example.py | 3 +- examples/helpers/stats/stats_example.py | 101 ++++++++++++++++++ examples/mail/mail.py | 2 +- live_test.py | 49 +++++++++ proposals/mail-helper-refactor.md | 10 +- requirements.txt | 2 +- VERSION.txt => sendgrid/VERSION.txt | 0 sendgrid/__init__.py | 4 +- sendgrid/helpers/__init__.py | 20 +--- sendgrid/helpers/endpoints/ip/__init__.py | 0 sendgrid/helpers/endpoints/ip/unassigned.py | 52 +++++++++ sendgrid/helpers/inbound/README.md | 18 ++-- sendgrid/helpers/inbound/config.py | 7 +- sendgrid/helpers/mail/__init__.py | 1 + .../helpers/mail/dynamic_template_data.py | 56 ++++++++++ sendgrid/helpers/mail/mail.py | 20 ++++ sendgrid/helpers/mail/personalization.py | 16 ++- sendgrid/helpers/mail/spam_check.py | 13 ++- sendgrid/sendgrid.py | 45 ++------ setup.py | 4 +- test/test_config.py | 2 +- test/test_mail.py | 95 ++++++++++++++++ test/test_project.py | 19 ++-- test/test_sendgrid.py | 25 ++--- test/test_spam_check.py | 39 +++++++ use_cases/README.md | 4 +- use_cases/aws.md | 10 +- use_cases/domain_authentication.md | 5 + use_cases/domain_whitelabel.md | 5 - use_cases/flask_heroku.md | 9 ++ use_cases/slack_event_api_integration.md | 46 ++++++++ 46 files changed, 787 insertions(+), 192 deletions(-) create mode 100644 docker/Makefile create mode 100644 docker/docker-compose.yml create mode 100644 docker/sendgrid.env create mode 100644 examples/helpers/README.md create mode 100644 examples/helpers/stats/stats_example.py rename VERSION.txt => sendgrid/VERSION.txt (100%) create mode 100644 sendgrid/helpers/endpoints/ip/__init__.py create mode 100644 sendgrid/helpers/endpoints/ip/unassigned.py create mode 100644 sendgrid/helpers/mail/dynamic_template_data.py create mode 100644 test/test_spam_check.py create mode 100644 use_cases/domain_authentication.md delete mode 100644 use_cases/domain_whitelabel.md create mode 100644 use_cases/flask_heroku.md create mode 100644 use_cases/slack_event_api_integration.md diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE index 7ad590b42..b3b7a4446 100644 --- a/.github/PULL_REQUEST_TEMPLATE +++ b/.github/PULL_REQUEST_TEMPLATE @@ -21,4 +21,4 @@ Closes #2 - - -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 [SendGrid](mailto:dx@sendgrid.com), or file a GitHub Issue in this repository. diff --git a/.gitignore b/.gitignore index 7bd21033d..06437f587 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,4 @@ coverage.xml htmlcov temp.py .vscode -sendgrid/VERSION.txt +live_test.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 52f1479e4..1a206dfdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,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! @@ -51,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! @@ -65,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 723e645f5..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 344605834..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 @@ -112,7 +125,7 @@ Working examples that demonstrate usage. **/tests** -Currently we have unit and profiling tests. +Currently, we have unit and profiling tests. **/sendgrid** @@ -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/README.rst b/README.rst index 3cce78559..b92d7c4d5 100644 --- a/README.rst +++ b/README.rst @@ -97,7 +97,7 @@ Hello Email ----------- The following is the minimum needed code to send an email with the `/mail/send Helper`_ -(`here `__ is a full example): +(`here `__ is a full example): With Mail Helper Class ~~~~~~~~~~~~~~~~~~~~~~ @@ -120,7 +120,7 @@ With Mail Helper Class print(response.headers) The ``Mail`` constructor creates a `personalization object`_ for you. -`Here `__ is an example of how to add it. +`Here `__ is an example of how to add it. Without Mail Helper Class ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 3535670d8..296d28944 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 @@ -40,7 +40,7 @@ import urllib2 try: response = sg.client.mail.send.post(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: @@ -50,7 +50,7 @@ import urllib try: response = sg.client.mail.send.post(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 @@ -111,4 +111,4 @@ You can do this right before you call `response = sg.client.mail.send.post(reque # 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 2a6fd62d7..c19cf18da 100644 --- a/USAGE.md +++ b/USAGE.md @@ -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. @@ -338,7 +338,7 @@ print(response.headers) **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). @@ -407,7 +407,7 @@ print(response.headers) **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). @@ -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: @@ -1418,7 +1418,7 @@ 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. @@ -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 @@ -1687,7 +1687,7 @@ 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. @@ -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). @@ -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. @@ -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 @@ -2758,7 +2758,7 @@ print(response.headers) **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 @@ -2773,7 +2773,7 @@ print(response.headers) **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. @@ -2808,7 +2808,7 @@ print(response.headers) **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} @@ -2824,7 +2824,7 @@ print(response.headers) **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} @@ -2840,7 +2840,7 @@ print(response.headers) **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 diff --git a/docker-test/README.md b/docker-test/README.md index d44ebf759..487a17ee6 100644 --- a/docker-test/README.md +++ b/docker-test/README.md @@ -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/Dockerfile b/docker/Dockerfile index bbbf56277..cf2d36b6b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,12 +1,11 @@ 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" -WORKDIR /root - -ENV PYTHON_VERSIONS='python2.7 python3.4 python3.5 python3.6' \ - OAI_SPEC_URL="https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/oai_stoplight.json" \ - DEBIAN_FRONTEND=noninteractive +ARG SENDGRID-PYTHON_VERSION +ARG BRANCH_HTTP_CLIENT -# Install testing versions of python, including old versions, from deadsnakes +# install testing versions of python, including old versions, from deadsnakes RUN set -x \ && apt-get update \ && apt-get install -y --no-install-recommends software-properties-common \ @@ -18,29 +17,36 @@ RUN set -x \ && apt-get purge -y --auto-remove software-properties-common \ && rm -rf /var/lib/apt/lists/* -# Install Prism +WORKDIR /root + +# install Prism ADD https://raw.githubusercontent.com/stoplightio/prism/master/install.sh install.sh -RUN sync && bash install.sh +RUN chmod +x ./install.sh && sync && \ + ./install.sh && \ + rm ./install.sh -# Install pip, tox +# install pip, tox ADD https://bootstrap.pypa.io/get-pip.py get-pip.py -RUN python2.7 get-pip.py && pip install tox - -# Install pyyaml, six, werkzeug -RUN python3.6 -m pip install pyyaml six werkzeug flask - -# Set up default SendGrid env +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 + +# 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 +RUN ln -s /root/sources/sendgrid-python/sendgrid && \ + ln -s /root/sources/python-http-client/python_http_client COPY entrypoint.sh entrypoint.sh - +RUN chmod +x entrypoint.sh ENTRYPOINT ["./entrypoint.sh"] CMD ["--mock"] diff --git a/docker/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/USAGE.md b/docker/USAGE.md index 1a4473766..0d3f90b90 100644 --- a/docker/USAGE.md +++ b/docker/USAGE.md @@ -76,8 +76,8 @@ Note that the paths you specify in `-v` must be absolute. # Quickstart 1. Install docker-compose on your machine. -2. Must copy .env_sample to .env file. -3. Edit .env file for yours versions and paths. +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 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/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..3a4a350a8 100644 --- a/examples/helpers/mail/mail_example.py +++ b/examples/helpers/mail/mail_example.py @@ -80,7 +80,8 @@ def get_mock_personalization_dict(): def build_attachment1(): - """Build attachment mock.""" + """Build attachment mock. Make sure your content is base64 encoded before passing into attachment.content. + Another example: https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/attachment.md""" attachment = Attachment() attachment.content = ("TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNl" "Y3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12") 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 index 340bc2d1d..650698562 100644 --- a/live_test.py +++ b/live_test.py @@ -306,3 +306,52 @@ # 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(apikey=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 98991ad03..01c92dc9c 100644 --- a/proposals/mail-helper-refactor.md +++ b/proposals/mail-helper-refactor.md @@ -1,3 +1,5 @@ +# This is the original proposal for v4.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. @@ -185,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') @@ -224,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/requirements.txt b/requirements.txt index a6646a692..6ec05dd87 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Flask==1.0.2 -PyYAML==3.13 +PyYAML>=4.2b1 python-http-client==3.1.0 six==1.11.0 pytest==3.8.2 diff --git a/VERSION.txt b/sendgrid/VERSION.txt similarity index 100% rename from VERSION.txt rename to sendgrid/VERSION.txt diff --git a/sendgrid/__init__.py b/sendgrid/__init__.py index 050e798e9..3aecea426 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 @@ -16,11 +16,9 @@ """ import os -# v3 API from .sendgrid import SendGridAPIClient # noqa from .helpers.mail import Email # 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/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..51ddc8b2a --- /dev/null +++ b/sendgrid/helpers/endpoints/ip/unassigned.py @@ -0,0 +1,52 @@ +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(apikey=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 = unassinged(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 \ No newline at end of file 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/mail/__init__.py b/sendgrid/helpers/mail/__init__.py index ccf88702b..15cc1cc7e 100644 --- a/sendgrid/helpers/mail/__init__.py +++ b/sendgrid/helpers/mail/__init__.py @@ -12,6 +12,7 @@ from .content_id import ContentId from .custom_arg import CustomArg from .disposition import Disposition +from .dynamic_template_data import DynamicTemplateData from .email import Email from .exceptions import SendGridException, ApiKeyIncludedException from .file_content import FileContent diff --git a/sendgrid/helpers/mail/dynamic_template_data.py b/sendgrid/helpers/mail/dynamic_template_data.py new file mode 100644 index 000000000..9eef6e342 --- /dev/null +++ b/sendgrid/helpers/mail/dynamic_template_data.py @@ -0,0 +1,56 @@ +class DynamicTemplateData(object): + """In order 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 dynamic transactional template. + Should be JSON-serializeable structure. + + :param dynamic_template_data: Data for dynamic transactional template. + :type dynamic_template_data: A JSON-serializeable structure + :param name: p is the Personalization object or Personalization object index + :type name: Personalization or 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 dynamic transactional template. + + :rtype: A JSON-serializeable structure + """ + return self._dynamic_template_data + + @dynamic_template_data.setter + def dynamic_template_data(self, value): + self._dynamic_template_data = value + + @property + def personalization(self): + return self._personalization + + @personalization.setter + def personalization(self, value): + 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 dynamic transactional template. + :rtype: A JSON-serializeable structure. + """ + return self.dynamic_template_data diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 8306a999b..a66728f2e 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -8,6 +8,7 @@ from .personalization import Personalization from .send_at import SendAt from .subject import Subject +from .dynamic_template_data import DynamicTemplateData class Mail(object): """Creates the response body for v3/mail/send""" @@ -343,6 +344,25 @@ def send_at(self, value): else: self._send_at = SendAt(value) + @property + def dynamic_template_data(self): + pass + + @dynamic_template_data.setter + def dynamic_template_data(self, value): + 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 + + if not has_internal_personalization: + self.add_personalization(personalization, index=value.personalization) + @property def from_email(self): return self._from_email diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py index 11fef6dbc..288f94eee 100644 --- a/sendgrid/helpers/mail/personalization.py +++ b/sendgrid/helpers/mail/personalization.py @@ -13,6 +13,7 @@ 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) @@ -174,6 +175,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. @@ -188,7 +202,7 @@ def get(self): if value: personalization[key[:-1]] = value - for key in ['subject', 'send_at']: + for key in ['subject', 'send_at', 'dynamic_template_data']: value = getattr(self, key) if value: personalization[key] = value diff --git a/sendgrid/helpers/mail/spam_check.py b/sendgrid/helpers/mail/spam_check.py index d2894136e..fa1616ad7 100644 --- a/sendgrid/helpers/mail/spam_check.py +++ b/sendgrid/helpers/mail/spam_check.py @@ -1,3 +1,6 @@ +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.""" @@ -46,7 +49,10 @@ def threshold(self): @threshold.setter def threshold(self, value): - self._threshold = value + if isinstance(value, SpamThreshold): + self._threshold = value + else: + self._threshold = SpamThreshold(value) @property def post_to_url(self): @@ -59,7 +65,10 @@ def post_to_url(self): @post_to_url.setter def post_to_url(self, value): - self._post_to_url = value + if isinstance(value, SpamUrl): + self._post_to_url = value + else: + self._post_to_url = SpamUrl(value) def get(self): """ diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index b1665cf61..d89c87ec7 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -23,7 +23,7 @@ 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(api_key=os.environ.get('SENDGRID_API_KEY')) ... mail = Mail(from_email, subject, to_email, content) response = sg.client.mail.send.post(request_body=mail.get()) @@ -34,42 +34,30 @@ 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 + Note that the underlying client is 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 + :param api_key: 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 + :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: basestring + :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 """ from . import __version__ - 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') + self.api_key = api_key or os.environ.get('SENDGRID_API_KEY') self.impersonate_subuser = impersonate_subuser self.host = host - self.useragent = 'sendgrid/{};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, @@ -78,7 +66,7 @@ def __init__( @property def _default_headers(self): headers = { - "Authorization": 'Bearer {}'.format(self.apikey), + "Authorization": 'Bearer {}'.format(self.api_key), "User-agent": self.useragent, "Accept": 'application/json' } @@ -90,19 +78,6 @@ def _default_headers(self): 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 - - @api_key.setter - def api_key(self, value): - self.apikey = value - def send(self, message): response = self.client.mail.send.post(request_body=message.get()) return response diff --git a/setup.py b/setup.py index 7dde1f656..426222575 100644 --- a/setup.py +++ b/setup.py @@ -11,8 +11,8 @@ def getRequires(): 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, 'VERSION.txt'), encoding='utf-8').read().strip() -copy_file(os.path.join(dir_path, 'VERSION.txt'), +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) diff --git a/test/test_config.py b/test/test_config.py index 715eb685d..81c196fb2 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -41,7 +41,7 @@ def test_initialization(self): 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_mail.py b/test/test_mail.py index 174e222e5..b0154b08d 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -19,6 +19,7 @@ ClickTracking, Content, CustomArg, + DynamicTemplateData, Email, FooterSettings, Ganalytics, @@ -762,6 +763,100 @@ def test_kitchen_sink(self): }''') ) + # Send a Single Email to a Single Recipient + 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_unicode_values_in_substitutions_helper(self): return # """ Test that the Substitutions helper accepts unicode values """ diff --git a/test/test_project.py b/test/test_project.py index 340388af6..d488840f6 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -3,18 +3,13 @@ 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): @@ -68,9 +63,9 @@ def test_troubleshooting(self): def test_usage(self): self.assertTrue(os.path.isfile('./USAGE.md')) - # ./VERSION.txt - def test_usage(self): - self.assertTrue(os.path.isfile('./VERSION.txt')) + # ./sendgrid/VERSION.txt + def test_version(self): + self.assertTrue(os.path.isfile('./sendgrid/VERSION.txt')) # ./use-cases/README.md def test_use_cases(self): diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index 25c46ff8e..24666ca5b 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -65,26 +65,19 @@ def setUpClass(cls): # time.sleep(15) # print("Prism Started") - def test_apikey_init(self): - self.assertEqual(self.sg.apikey, os.environ.get('SENDGRID_API_KEY')) + 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.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") + 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' diff --git a/test/test_spam_check.py b/test/test_spam_check.py new file mode 100644 index 000000000..cf0aff767 --- /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/use_cases/README.md b/use_cases/README.md index bee7d628a..e9af0ba7e 100644 --- a/use_cases/README.md +++ b/use_cases/README.md @@ -8,7 +8,8 @@ This directory provides examples for specific use cases of this library. Please * [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 @@ -16,6 +17,7 @@ This directory provides examples for specific use cases of this library. Please * [Attachment](attachment.md) * [Sending HTML-Only Content](sending_html_content.md) * [Transactional Templates](transational_templates.md) +* [Integrate with Slack Events API](slack_event_api_integration.md) ### Library Features * [Error Handling](error_handling.md) \ No newline at end of file diff --git a/use_cases/aws.md b/use_cases/aws.md index 2d5ebdcb1..6cd6f0a79 100644 --- a/use_cases/aws.md +++ b/use_cases/aws.md @@ -9,11 +9,11 @@ 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.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. +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 @@ -23,7 +23,7 @@ Log in to your AWS account and go to the AWS CodeStar service. Click "Start a pr 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. @@ -36,11 +36,11 @@ 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 ``` 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/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/slack_event_api_integration.md b/use_cases/slack_event_api_integration.md new file mode 100644 index 000000000..eac8d195a --- /dev/null +++ b/use_cases/slack_event_api_integration.md @@ -0,0 +1,46 @@ +# 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 +import sendgrid +from sendgrid.helpers.mail import * + +sg = sendgrid.SendGridAPIClient(apikey=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 = Email("slack_integration@example.com") + to_email = Email("test@example.com") + subject = "Psst... Someone needs help!" + content = Content("text/plain", message) + mail = Mail(from_email, subject, to_email, content) + response = sg.client.mail.send.post(request_body=mail.get()) + return response.status_code + +# Start the slack event listener server on port 3000 +slack_events_adapter.start(port=3000) +``` From a945dcf55203ec621bb65fc2e43e6575cef34ddc Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Sun, 31 Mar 2019 23:35:14 -0700 Subject: [PATCH 188/462] Updated documentation --- .github/PULL_REQUEST_TEMPLATE | 7 +- .gitignore | 2 + README.rst | 90 +-- TROUBLESHOOTING.md | 4 +- USAGE.md | 4 +- docker-test/README.md | 2 +- docker/README.md | 1 + examples/helpers/mail/mail_example.py | 409 +++++----- live_test.py | 714 +++++++++--------- proposals/mail-helper-refactor.md | 2 +- sendgrid/__init__.py | 5 +- sendgrid/helpers/endpoints/__init__.py | 0 sendgrid/helpers/mail/README.md | 11 +- sendgrid/helpers/mail/asm.py | 40 +- sendgrid/helpers/mail/attachment.py | 112 ++- sendgrid/helpers/mail/batch_id.py | 5 + sendgrid/helpers/mail/bcc_settings.py | 10 + sendgrid/helpers/mail/bcc_settings_email.py | 7 +- .../helpers/mail/bypass_list_management.py | 5 + sendgrid/helpers/mail/category.py | 5 + sendgrid/helpers/mail/click_tracking.py | 17 +- sendgrid/helpers/mail/content.py | 65 +- sendgrid/helpers/mail/content_id.py | 9 + sendgrid/helpers/mail/custom_arg.py | 28 +- sendgrid/helpers/mail/disposition.py | 20 +- .../helpers/mail/dynamic_template_data.py | 26 +- sendgrid/helpers/mail/email.py | 113 ++- sendgrid/helpers/mail/exceptions.py | 10 + sendgrid/helpers/mail/file_content.py | 5 + sendgrid/helpers/mail/file_name.py | 5 + sendgrid/helpers/mail/file_type.py | 11 +- sendgrid/helpers/mail/footer_html.py | 11 +- sendgrid/helpers/mail/footer_settings.py | 15 + sendgrid/helpers/mail/footer_text.py | 7 +- sendgrid/helpers/mail/ganalytics.py | 36 +- sendgrid/helpers/mail/group_id.py | 7 +- sendgrid/helpers/mail/groups_to_display.py | 20 +- sendgrid/helpers/mail/header.py | 21 +- sendgrid/helpers/mail/html_content.py | 40 +- sendgrid/helpers/mail/ip_pool_name.py | 7 +- sendgrid/helpers/mail/mail.py | 471 ++++++++++-- sendgrid/helpers/mail/mail_settings.py | 25 + sendgrid/helpers/mail/mime_type.py | 2 +- sendgrid/helpers/mail/open_tracking.py | 15 + .../mail/open_tracking_substitution_tag.py | 11 +- sendgrid/helpers/mail/personalization.py | 18 +- sendgrid/helpers/mail/plain_text_content.py | 39 +- sendgrid/helpers/mail/sandbox_mode.py | 6 +- sendgrid/helpers/mail/section.py | 26 +- sendgrid/helpers/mail/send_at.py | 16 +- sendgrid/helpers/mail/spam_check.py | 25 +- sendgrid/helpers/mail/spam_threshold.py | 13 +- sendgrid/helpers/mail/spam_url.py | 7 + sendgrid/helpers/mail/subject.py | 16 +- sendgrid/helpers/mail/subscription_html.py | 9 +- .../mail/subscription_substitution_tag.py | 13 +- sendgrid/helpers/mail/subscription_text.py | 9 +- .../helpers/mail/subscription_tracking.py | 33 + sendgrid/helpers/mail/substitution.py | 29 +- sendgrid/helpers/mail/template_id.py | 7 +- sendgrid/helpers/mail/tracking_settings.py | 54 +- sendgrid/helpers/mail/utm_campaign.py | 7 +- sendgrid/helpers/mail/utm_content.py | 7 +- sendgrid/helpers/mail/utm_medium.py | 7 +- sendgrid/helpers/mail/utm_source.py | 9 +- sendgrid/helpers/mail/utm_term.py | 9 +- sendgrid/helpers/mail/validators.py | 33 +- sendgrid/helpers/stats/README.md | 10 + sendgrid/helpers/stats/__init__.py | 1 + sendgrid/helpers/stats/stats.py | 222 ++++++ sendgrid/sendgrid.py | 12 +- test/{test_send.py => test_inbound_send.py} | 0 test/test_mail.py | 299 ++++---- test/test_sendgrid.py | 68 -- use_cases/README.md | 28 +- use_cases/asynchronous_mail_send.md | 38 +- use_cases/attachment.md | 54 +- use_cases/aws.md | 17 +- use_cases/django.md | 23 +- use_cases/error_handling.md | 29 +- use_cases/kitchen_sink.md | 226 ++++++ use_cases/legacy_templates.md | 116 +++ ...nd_a_single_email_to_a_single_recipient.md | 19 + ...d_a_single_email_to_multiple_recipients.md | 24 + ..._multiple_emails_to_multiple_recipients.md | 39 + use_cases/sending_html_content.md | 29 +- use_cases/slack_event_api_integration.md | 20 +- use_cases/transational_templates.md | 154 ++-- 88 files changed, 2988 insertions(+), 1264 deletions(-) create mode 100644 sendgrid/helpers/endpoints/__init__.py create mode 100644 sendgrid/helpers/stats/README.md create mode 100644 sendgrid/helpers/stats/__init__.py create mode 100644 sendgrid/helpers/stats/stats.py rename test/{test_send.py => test_inbound_send.py} (100%) create mode 100644 use_cases/kitchen_sink.md create mode 100644 use_cases/legacy_templates.md create mode 100644 use_cases/send_a_single_email_to_a_single_recipient.md create mode 100644 use_cases/send_a_single_email_to_multiple_recipients.md create mode 100644 use_cases/send_multiple_emails_to_multiple_recipients.md diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE index b3b7a4446..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 06437f587..e703eeda6 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ htmlcov temp.py .vscode live_test.py +__pycache__ +example.pdf diff --git a/README.rst b/README.rst index b92d7c4d5..ed78ea382 100644 --- a/README.rst +++ b/README.rst @@ -104,20 +104,23 @@ With Mail Helper Class .. code:: 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) + 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. @@ -130,45 +133,48 @@ The following is the minimum needed code to send an email without the /mail/send .. code:: python - import sendgrid import os + from sendgrid import SendGridAPIClient - sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - data = { - "personalizations": [ - { - "to": [ + message = { + 'personalizations': [ + { + 'to': [ + { + 'email': 'test@example.com' + } + ], + 'subject': 'Sending with SendGrid is Fun' + } + ], + 'from': { + 'email': 'test@example.com' + }, + 'content': [ { - "email": "test@example.com" + 'type': 'text/plain', + 'value': 'and easy to do anywhere, even with Python' } - ], - "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) + 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 sendgrid import os + from sendgrid import SendGridAPIClient - sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) response = sg.client.suppression.bounces.get() print(response.status_code) print(response.body) @@ -179,11 +185,11 @@ General v3 Web API Usage (Without `Fluent Interface`_) .. code:: python - import sendgrid import os + from sendgrid import SendGridAPIClient - sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - response = sg.client._("suppression/bounces").get() + sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sg.client._('suppression/bounces').get() print(response.status_code) print(response.body) print(response.headers) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 296d28944..abb2ff14a 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -38,7 +38,7 @@ 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()) ``` @@ -48,7 +48,7 @@ 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()) ``` diff --git a/USAGE.md b/USAGE.md index c19cf18da..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 diff --git a/docker-test/README.md b/docker-test/README.md index 487a17ee6..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 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/examples/helpers/mail/mail_example.py b/examples/helpers/mail/mail_example.py index 3a4a350a8..9b7f71e4a 100644 --- a/examples/helpers/mail/mail_example.py +++ b/examples/helpers/mail/mail_example.py @@ -9,212 +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. Make sure your content is base64 encoded before passing into attachment.content. - Another example: https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/attachment.md""" - 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/live_test.py b/live_test.py index 650698562..12c52eeed 100644 --- a/live_test.py +++ b/live_test.py @@ -1,357 +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(apikey=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(apikey=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(apikey=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(apikey=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(apikey=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 +## 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 01c92dc9c..13fefdbd3 100644 --- a/proposals/mail-helper-refactor.md +++ b/proposals/mail-helper-refactor.md @@ -1,4 +1,4 @@ -# This is the original proposal for v4.0.0 +# This is the original proposal for v6.0.0 # Send a Single Email to a Single Recipient diff --git a/sendgrid/__init__.py b/sendgrid/__init__.py index 3aecea426..a40ac5ecc 100644 --- a/sendgrid/__init__.py +++ b/sendgrid/__init__.py @@ -17,7 +17,10 @@ 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')): 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/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/asm.py b/sendgrid/helpers/mail/asm.py index 7ce76856e..086d178d1 100644 --- a/sendgrid/helpers/mail/asm.py +++ b/sendgrid/helpers/mail/asm.py @@ -1,13 +1,16 @@ +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,34 +25,49 @@ 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.get()) > 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 = {} diff --git a/sendgrid/helpers/mail/attachment.py b/sendgrid/helpers/mail/attachment.py index 62bee8498..fa5ca45cf 100644 --- a/sendgrid/helpers/mail/attachment.py +++ b/sendgrid/helpers/mail/attachment.py @@ -1,15 +1,21 @@ +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, file_content=None, file_type=None, file_name=None, disposition=None, content_id=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: string, optional - :param file_type: The MIME type of the content you are attaching - :type file_type string, optional + :type file_content: FileContent, string :param file_name: The filename of the attachment - :type file_name: string, optional + :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 @@ -17,11 +23,11 @@ def __init__(self, file_content=None, file_type=None, file_name=None, dispositio - "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 + :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: string, optional + :type content_id: ContentId, string, optional """ self._file_content = None self._file_type = None @@ -48,37 +54,61 @@ def __init__(self, file_content=None, file_type=None, file_name=None, dispositio def file_content(self): """The Base64 encoded content of the attachment. - :rtype: string + :rtype: FileContent """ return self._file_content @file_content.setter def file_content(self, value): - self._file_content = value - - @property - def file_type(self): - """The MIME type of the content you are attaching. + """The Base64 encoded content of the attachment - :rtype: string + :param value: The Base64 encoded content of the attachment + :type value: FileContent, string """ - return self._file_type - - @file_type.setter - def file_type(self, value): - self._file_type = value + if isinstance(value, FileContent): + self._file_content = value + else: + self._file_content = FileContent(value) @property def file_name(self): """The file name of the attachment. - :rtype: string + :rtype: FileName """ return self._file_name @file_name.setter def file_name(self, value): - self._file_name = 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 file_type(self): + """The MIME type of the content you are attaching. + + :rtype: FileType + """ + return self._file_type + + @file_type.setter + def file_type(self, value): + """The MIME type of the content you are attaching + + :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): @@ -91,13 +121,34 @@ 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): @@ -112,7 +163,20 @@ 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): """ diff --git a/sendgrid/helpers/mail/batch_id.py b/sendgrid/helpers/mail/batch_id.py index dec83a63b..53b7378a0 100644 --- a/sendgrid/helpers/mail/batch_id.py +++ b/sendgrid/helpers/mail/batch_id.py @@ -24,6 +24,11 @@ def batch_id(self): @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): diff --git a/sendgrid/helpers/mail/bcc_settings.py b/sendgrid/helpers/mail/bcc_settings.py index 79610122c..87e290d16 100644 --- a/sendgrid/helpers/mail/bcc_settings.py +++ b/sendgrid/helpers/mail/bcc_settings.py @@ -32,6 +32,11 @@ 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 @@ -44,6 +49,11 @@ def email(self): @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): diff --git a/sendgrid/helpers/mail/bcc_settings_email.py b/sendgrid/helpers/mail/bcc_settings_email.py index 00cc756b5..dbbb9645d 100644 --- a/sendgrid/helpers/mail/bcc_settings_email.py +++ b/sendgrid/helpers/mail/bcc_settings_email.py @@ -22,6 +22,11 @@ def bcc_settings_email(self): @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): @@ -31,4 +36,4 @@ def get(self): :returns: This BccSettingsEmail, ready for use in a request body. :rtype: string """ - return self.bcc_settings_email \ No newline at end of file + return self.bcc_settings_email diff --git a/sendgrid/helpers/mail/bypass_list_management.py b/sendgrid/helpers/mail/bypass_list_management.py index 308ca9cfb..ac13e3d75 100644 --- a/sendgrid/helpers/mail/bypass_list_management.py +++ b/sendgrid/helpers/mail/bypass_list_management.py @@ -28,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 a3e885538..541fc2229 100644 --- a/sendgrid/helpers/mail/category.py +++ b/sendgrid/helpers/mail/category.py @@ -22,6 +22,11 @@ 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/click_tracking.py b/sendgrid/helpers/mail/click_tracking.py index 54b98c414..eba5eace5 100644 --- a/sendgrid/helpers/mail/click_tracking.py +++ b/sendgrid/helpers/mail/click_tracking.py @@ -28,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 33554dbee..133b3b3d5 100644 --- a/sendgrid/helpers/mail/content.py +++ b/sendgrid/helpers/mail/content.py @@ -7,50 +7,61 @@ 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._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 - @type.setter - def type(self, value): - self._type = value + @mime_type.setter + def mime_type(self, value): + """The MIME type of the content you are including in your email. + For example, "text/plain" or "text/html". + + :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 + + @content.setter + def content(self, value): + """The actual content (of the specified mime type). - @value.setter - def value(self, value): + :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): """ @@ -60,9 +71,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 index 3e72b391b..f789a0990 100644 --- a/sendgrid/helpers/mail/content_id.py +++ b/sendgrid/helpers/mail/content_id.py @@ -26,6 +26,15 @@ def content_id(self): @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): diff --git a/sendgrid/helpers/mail/custom_arg.py b/sendgrid/helpers/mail/custom_arg.py index cedbfd748..39e15a68e 100644 --- a/sendgrid/helpers/mail/custom_arg.py +++ b/sendgrid/helpers/mail/custom_arg.py @@ -14,8 +14,8 @@ def __init__(self, key=None, value=None, p=None): :type key: string, optional :param value: Value of this CustomArg :type value: string, optional - :param name: p is the Personalization object or Personalization object index - :type name: Personalization or integer, optional + :param p: p is the Personalization object or Personalization object index + :type p: Personalization, integer, optional """ self._key = None self._value = None @@ -38,23 +38,45 @@ 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): diff --git a/sendgrid/helpers/mail/disposition.py b/sendgrid/helpers/mail/disposition.py index adadb1719..9dcb6dc2c 100644 --- a/sendgrid/helpers/mail/disposition.py +++ b/sendgrid/helpers/mail/disposition.py @@ -1,5 +1,6 @@ class Disposition(object): - """The MIME type of the content you are attaching to an Attachment content.""" + """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 @@ -34,6 +35,23 @@ def disposition(self): @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): diff --git a/sendgrid/helpers/mail/dynamic_template_data.py b/sendgrid/helpers/mail/dynamic_template_data.py index 9eef6e342..d22b0c8b1 100644 --- a/sendgrid/helpers/mail/dynamic_template_data.py +++ b/sendgrid/helpers/mail/dynamic_template_data.py @@ -1,15 +1,15 @@ class DynamicTemplateData(object): - """In order to send a dynamic template, specify the template ID with the template_id parameter. + """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 dynamic transactional template. + """Data for a transactional template. Should be JSON-serializeable structure. - :param dynamic_template_data: Data for dynamic transactional template. + :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 or integer, optional + :type name: Personalization, integer, optional """ self._dynamic_template_data = None self._personalization = None @@ -21,7 +21,7 @@ def __init__(self, dynamic_template_data=None, p=0): @property def dynamic_template_data(self): - """Data for dynamic transactional template. + """Data for a transactional template. :rtype: A JSON-serializeable structure """ @@ -29,14 +29,28 @@ def dynamic_template_data(self): @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): @@ -50,7 +64,7 @@ def get(self): """ Get a JSON-ready representation of this DynamicTemplateData object. - :returns: Data for dynamic transactional template. + :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 961277127..2fc67a34e 100644 --- a/sendgrid/helpers/mail/email.py +++ b/sendgrid/helpers/mail/email.py @@ -35,8 +35,10 @@ def __init__(self, :type email: string, optional :param name: Name for this sender or recipient. :type name: string, optional - :param name: p is the Personalization object or Personalization object index - :type name: Personalization or integer, 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 @@ -45,7 +47,7 @@ def __init__(self, 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) @@ -55,14 +57,14 @@ def __init__(self, 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 substitutions is not None: + self.substitutions = substitutions + + if subject is not None: + self.subject = subject - if p is not None: - self.personalization = p + if p is not None: + self.personalization = p @property def name(self): @@ -74,6 +76,11 @@ 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.') @@ -91,53 +98,93 @@ 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 @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) + """ 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 @property def subject(self): + """Subject for this sender or recipient. + + :rtype: string + """ return self._subject @subject.setter def subject(self, value): + """Subject for this sender or recipient. + + :param value: Subject for this sender or recipient. + :type value: string, optional + """ self._subject = value @property def personalization(self): + """The Personalization object or Personalization object index + + :rtype: Personalization, integer + """ return self._personalization @personalization.setter def personalization(self, value): - self._personalization = value - - def get(self): + """The Personalization object or Personalization object index + + :param value: The Personalization object or Personalization object index + :type value: Personalization, integer """ - 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 + 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 @@ -154,3 +201,19 @@ 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 7f44abd84..17a2fb135 100644 --- a/sendgrid/helpers/mail/exceptions.py +++ b/sendgrid/helpers/mail/exceptions.py @@ -40,6 +40,11 @@ def expression(self): @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 @@ -52,4 +57,9 @@ def message(self): @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 index 79414646f..a7bc130c9 100644 --- a/sendgrid/helpers/mail/file_content.py +++ b/sendgrid/helpers/mail/file_content.py @@ -22,6 +22,11 @@ def file_content(self): @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): diff --git a/sendgrid/helpers/mail/file_name.py b/sendgrid/helpers/mail/file_name.py index 29ea0d5f6..89124a77c 100644 --- a/sendgrid/helpers/mail/file_name.py +++ b/sendgrid/helpers/mail/file_name.py @@ -22,6 +22,11 @@ def file_name(self): @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): diff --git a/sendgrid/helpers/mail/file_type.py b/sendgrid/helpers/mail/file_type.py index 7204332f4..27db03c5b 100644 --- a/sendgrid/helpers/mail/file_type.py +++ b/sendgrid/helpers/mail/file_type.py @@ -1,5 +1,5 @@ class FileType(object): - """The MIME type of the content you are attaching to an Attachment content.""" + """The MIME type of the content you are attaching to an Attachment.""" def __init__(self, file_type=None): """Create a FileType object @@ -21,8 +21,13 @@ def file_type(self): return self._file_type @file_type.setter - def file_type(self, value): - self._file_type = value + 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): """ diff --git a/sendgrid/helpers/mail/footer_html.py b/sendgrid/helpers/mail/footer_html.py index 3a60331ce..ceca52a28 100644 --- a/sendgrid/helpers/mail/footer_html.py +++ b/sendgrid/helpers/mail/footer_html.py @@ -1,5 +1,5 @@ class FooterHtml(object): - """The FooterHtml of an Attachment.""" + """The HTML in a Footer.""" def __init__(self, footer_html=None): """Create a FooterHtml object @@ -21,8 +21,13 @@ def footer_html(self): return self._footer_html @footer_html.setter - def footer_html(self, value): - self._footer_html = value + 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): """ diff --git a/sendgrid/helpers/mail/footer_settings.py b/sendgrid/helpers/mail/footer_settings.py index 39b7c56b2..662222ad4 100644 --- a/sendgrid/helpers/mail/footer_settings.py +++ b/sendgrid/helpers/mail/footer_settings.py @@ -34,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 @@ -46,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 @@ -58,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): diff --git a/sendgrid/helpers/mail/footer_text.py b/sendgrid/helpers/mail/footer_text.py index 3082942f9..d87acdaa6 100644 --- a/sendgrid/helpers/mail/footer_text.py +++ b/sendgrid/helpers/mail/footer_text.py @@ -1,5 +1,5 @@ class FooterText(object): - """The FooterText of an Footer.""" + """The text in an Footer.""" def __init__(self, footer_text=None): """Create a FooterText object @@ -22,6 +22,11 @@ def footer_text(self): @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): diff --git a/sendgrid/helpers/mail/ganalytics.py b/sendgrid/helpers/mail/ganalytics.py index 302b2ded7..a3e89e918 100644 --- a/sendgrid/helpers/mail/ganalytics.py +++ b/sendgrid/helpers/mail/ganalytics.py @@ -42,7 +42,7 @@ def __set_field(self, field, value): :param field: Name of the field :type field: string - :param value: value to be set, ignored if None + :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): diff --git a/sendgrid/helpers/mail/group_id.py b/sendgrid/helpers/mail/group_id.py index 56941e60b..89a0a9c37 100644 --- a/sendgrid/helpers/mail/group_id.py +++ b/sendgrid/helpers/mail/group_id.py @@ -1,5 +1,5 @@ class GroupId(object): - """The unsubscribe group to associate with this email.""" + """The unsubscribe group ID to associate with this email.""" def __init__(self, group_id=None): """Create a GroupId object @@ -22,6 +22,11 @@ def group_id(self): @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): diff --git a/sendgrid/helpers/mail/groups_to_display.py b/sendgrid/helpers/mail/groups_to_display.py index e8df7830d..0dd84b0cb 100644 --- a/sendgrid/helpers/mail/groups_to_display.py +++ b/sendgrid/helpers/mail/groups_to_display.py @@ -1,11 +1,12 @@ class GroupsToDisplay(object): - """The unsubscribe groups that you would like to be displayed on the unsubscribe preferences page..""" + """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. + :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 @@ -16,14 +17,23 @@ def __init__(self, groups_to_display=None): @property def groups_to_display(self): """An array containing the unsubscribe groups that you would like to be - displayed on the unsubscribe preferences page. + displayed on the unsubscribe preferences page. - :rtype: array of integers + :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): diff --git a/sendgrid/helpers/mail/header.py b/sendgrid/helpers/mail/header.py index dd45edc67..4dddde4d9 100644 --- a/sendgrid/helpers/mail/header.py +++ b/sendgrid/helpers/mail/header.py @@ -15,7 +15,7 @@ def __init__(self, key=None, value=None, p=None): :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 or integer, optional + :type name: Personalization, integer, optional """ self._key = None self._value = None @@ -38,6 +38,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 @@ -50,14 +55,28 @@ 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): diff --git a/sendgrid/helpers/mail/html_content.py b/sendgrid/helpers/mail/html_content.py index 91aedb64c..40abcf8f1 100644 --- a/sendgrid/helpers/mail/html_content.py +++ b/sendgrid/helpers/mail/html_content.py @@ -5,38 +5,43 @@ class HtmlContent(Content): """HTML content to be included in your email.""" - def __init__(self, value = None): - """Create an HtmlContent with the specified MIME type and value. + def __init__(self, content): + """Create an HtmlContent with the specified MIME type and content. - :param value: The HTML content. - :type value: string, optional + :param content: The HTML content. + :type content: string """ - self._value = None + self._content = None self._validator = ValidateApiKey() - if value is not None: - self.value = value + if content is not None: + self.content = content @property - def type(self): - """The actual text content. + def mime_type(self): + """The MIME type for HTML content. :rtype: string """ return "text/html" @property - def value(self): + def content(self): """The actual HTML content. :rtype: string """ - return self._value + return self._content - @value.setter - def value(self, value): + @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._value = value + self._content = value def get(self): """ @@ -46,6 +51,9 @@ def get(self): :rtype: dict """ content = {} - content["type"] = self.type - content["value"] = self.value + 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 index 4763bdd8a..93ed840f1 100644 --- a/sendgrid/helpers/mail/ip_pool_name.py +++ b/sendgrid/helpers/mail/ip_pool_name.py @@ -1,5 +1,5 @@ class IpPoolName(object): - """The IpPoolName of an Attachment.""" + """The IP Pool that you would like to send this email from.""" def __init__(self, ip_pool_name=None): """Create a IpPoolName object @@ -22,6 +22,11 @@ def ip_pool_name(self): @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): diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index a66728f2e..7a222beb5 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -1,35 +1,44 @@ -"""v3/mail/send response body builder""" -from collections import OrderedDict +"""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): """Creates the response body for v3/mail/send""" def __init__( self, from_email=None, - subject=None, to_emails=None, + subject=None, plain_text_content=None, html_content=None, global_substitutions=None, - is_multiple=False - ): - """Create Mail object + is_multiple=False): + """ + Creates the response body for a v3/mail/send API call :param from_email: The email address of the sender - :type from_email: From, optional + :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: string, optional + :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 @@ -56,37 +65,80 @@ def __init__( # 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_emails is not None: - self._set_emails(to_emails, global_substitutions, is_multiple) if plain_text_content is not None: - self.add_content(plain_text_content) + self.add_content(plain_text_content, MimeType.text) if html_content is not None: - self.add_content(html_content) + self.add_content(html_content, MimeType.html) def __str__(self): + """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 _ensure_append(self, new_items, append_to, index=0): + """Ensure an item is appended to a list or create a new empty list + + :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 + """ append_to = append_to or [] append_to.insert(index, new_items) return append_to def _ensure_insert(self, new_items, insert_to): + """Ensure an item is inserted to a list or create a new empty list + + :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 def _flatten_dicts(self, dicts): + """Flatten a dict + + :param dicts: Flatten a dict + :type dicts: list(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()} def _get_or_none(self, from_obj): + """Get the JSON representation of the object, else return None + + :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): - # Send Multiple Emails to Multiple Recipients + """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 == True: if isinstance(emails, list): for email in emails: @@ -118,6 +170,7 @@ def _set_emails(self, emails, global_substitutions=None, is_multiple=False, p=0) 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: @@ -130,11 +183,22 @@ def _set_emails(self, emails, global_substitutions=None, is_multiple=False, p=0) @property def personalizations(self): + """A list of one or more Personaliztion objects + + :rtype: list(Personalization) + """ return self._personalizations - def add_personalization(self, personalizations, index=0): + def add_personalization(self, personalization, index=0): + """Add a Personaliztion object + + :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( - personalizations, self._personalizations, index) + personalization, self._personalizations, index) @property def to(self): @@ -142,16 +206,59 @@ def to(self): @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_emails, global_substitutions=None, is_multiple=False, p=0): - if isinstance(to_emails, Email): - p = to_emails.personalization - self._set_emails(to_emails, None, is_multiple=is_multiple, p=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 + """ + + 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 cc(self): @@ -159,16 +266,50 @@ def cc(self): @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 + """ 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_emails, global_substitutions=None, is_multiple=False, p=0): - if isinstance(cc_emails, Email): - p = cc_emails.personalization - self._set_emails(cc_emails, None, is_multiple=is_multiple, p=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) @property def bcc(self): @@ -176,23 +317,66 @@ def bcc(self): @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_emails, global_substitutions=None, is_multiple=False, p=0): - if isinstance(bcc_emails, Email): - p = bcc_emails.personalization - self._set_emails(bcc_emails, None, is_multiple=is_multiple, p=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 Subject object + + :rtype: Subject + """ return self._subject @subject.setter def subject(self, 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: @@ -212,6 +396,10 @@ def subject(self, value): @property def headers(self): + """A list of global Header objects + + :rtype: list(Header) + """ return self._headers @property @@ -219,14 +407,24 @@ def header(self): pass @header.setter - def header(self, header): - if isinstance(header, list): - for h in header: + def header(self, headers): + """Add headers to the email + + :param value: A list of Header objects or a dict of header key/values + :type value: Header, list(Header), dict + """ + if isinstance(headers, list): + for h in headers: self.add_header(h) else: - self.add_header(header) + self.add_header(headers) def add_header(self, header): + """Add headers to the email globaly or to a specific Personalization + + :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] @@ -255,6 +453,11 @@ def substitution(self): @substitution.setter def substitution(self, substitution): + """Add substitutions to the email + + :param value: Add substitutions to the email + :type value: Substitution, list(Substitution) + """ if isinstance(substitution, list): for s in substitution: self.add_substitution(s) @@ -262,6 +465,11 @@ def substitution(self, substitution): self.add_substitution(substitution) def add_substitution(self, substitution): + """Add a substitution to the email + + :param value: Add a substitution to the email + :type value: Substitution + """ if substitution.personalization: try: personalization = self._personalizations[substitution.personalization] @@ -273,7 +481,7 @@ def add_substitution(self, substitution): if not has_internal_personalization: self.add_personalization(personalization, index=substitution.personalization) - else: + else: if isinstance(substitution, list): for s in substitution: for p in self.personalizations: @@ -284,6 +492,10 @@ def add_substitution(self, substitution): @property def custom_args(self): + """A list of global CustomArg objects + + :rtype: list(CustomArg) + """ return self._custom_args @property @@ -292,6 +504,11 @@ def custom_arg(self): @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 + """ if isinstance(custom_arg, list): for c in custom_arg: self.add_custom_arg(c) @@ -299,6 +516,11 @@ def custom_arg(self, custom_arg): self.add_custom_arg(custom_arg) 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] @@ -323,10 +545,21 @@ def add_custom_arg(self, custom_arg): @property def send_at(self): + """The global SendAt object + + :rtype: SendAt + """ return self._send_at @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: @@ -350,6 +583,11 @@ def dynamic_template_data(self): @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 + """ if not isinstance(value, DynamicTemplateData): value = DynamicTemplateData(value) try: @@ -365,22 +603,52 @@ def dynamic_template_data(self, value): @property def from_email(self): + """The email address of the sender + + :rtype: From + """ return self._from_email @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 reply to email address + + :rtype: ReplyTo + """ return self._reply_to @reply_to.setter def reply_to(self, value): + """The reply to email address + + :param value: The reply to email address + :type value: ReplyTo, str, tuple + """ + if isinstance(value, str): + value = ReplyTo(value, None) + if isinstance(value, tuple): + value = ReplyTo(value[0], value[1]) self._reply_to = value @property def contents(self): + """The contents of the email + + :rtype: list(Content) + """ return self._contents @property @@ -388,15 +656,30 @@ def content(self): pass @content.setter - def content(self, content): - if isinstance(content, list): - for c in content: + 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(content) + self.add_content(contents) - def add_content(self, content): - if content.type == "text/plain": + 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 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: if self._contents: @@ -407,6 +690,10 @@ def add_content(self, content): @property def attachments(self): + """The attachments to this email + + :rtype: list(Attachment) + """ return self._attachments @property @@ -415,6 +702,11 @@ def attachment(self): @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) @@ -422,18 +714,39 @@ def attachment(self, attachment): self.add_attachment(attachment) def add_attachment(self, attachment): + """Add an attachment to this email + + :param attachment: Add an attachment to this email + :type attachment: Attachment + """ self._attachments = self._ensure_append(attachment, self._attachments) @property def template_id(self): + """The transactional template id for this email + + :rtype: TemplateId + """ return self._template_id @template_id.setter def template_id(self, value): - self._template_id = value + """The transactional template id for this email + + :param value: The transactional template id for this email + :type value: TemplateId + """ + if isinstance(value, TemplateId): + self._template_id = value + else: + self._template_id = TemplateId(value) @property def sections(self): + """The block sections of code to be used as substitutions + + :rtype: Section + """ return self._sections @property @@ -442,6 +755,10 @@ def section(self): @section.setter def section(self, section): + """The block sections of code to be used as substitutions + + :rtype: Section, list(Section) + """ if isinstance(section, list): for h in section: self.add_section(h) @@ -449,10 +766,19 @@ def section(self, section): 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 assigned to this message + + :rtype: list(Category) + """ return self._categories @property @@ -460,59 +786,115 @@ def category(self): pass @category.setter - def category(self, category): - if isinstance(category, list): - for c in category: + 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(category) + self.add_category(categories) def add_category(self, category): + """Add a category assigned to this message + + :rtype: Category + """ self._categories = self._ensure_append(category, self._categories) @property def batch_id(self): + """The batch id for this email + + :rtype: BatchId + """ return self._batch_id @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): """ - :return: request body dict + 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), @@ -541,6 +923,7 @@ def get(self): def from_EmailMessage(cls, message): """Create a Mail object from an instance of email.message.EmailMessage. + :type message: email.message.EmailMessage :rtype: Mail """ diff --git a/sendgrid/helpers/mail/mail_settings.py b/sendgrid/helpers/mail/mail_settings.py index 665ee81c0..1df8458a9 100644 --- a/sendgrid/helpers/mail/mail_settings.py +++ b/sendgrid/helpers/mail/mail_settings.py @@ -52,6 +52,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 @@ -64,6 +69,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 @@ -76,6 +86,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 @@ -88,6 +103,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 @@ -100,6 +120,11 @@ 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 index 52dffb77a..9e89253f9 100644 --- a/sendgrid/helpers/mail/mime_type.py +++ b/sendgrid/helpers/mail/mime_type.py @@ -1,5 +1,5 @@ class MimeType(object): - """The MIME type of the content of your email. + """The MIME type of the content included in your email. """ text = "text/plain" html = "text/html" \ No newline at end of file diff --git a/sendgrid/helpers/mail/open_tracking.py b/sendgrid/helpers/mail/open_tracking.py index 194b22f61..4765170f3 100644 --- a/sendgrid/helpers/mail/open_tracking.py +++ b/sendgrid/helpers/mail/open_tracking.py @@ -32,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 @@ -46,6 +51,16 @@ 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): diff --git a/sendgrid/helpers/mail/open_tracking_substitution_tag.py b/sendgrid/helpers/mail/open_tracking_substitution_tag.py index 83c8fedc9..79724fcff 100644 --- a/sendgrid/helpers/mail/open_tracking_substitution_tag.py +++ b/sendgrid/helpers/mail/open_tracking_substitution_tag.py @@ -1,5 +1,5 @@ class OpenTrackingSubstitutionTag(object): - """The OpenTrackingSubstitutionTag of an SubscriptionTracking.""" + """The open tracking substitution tag of an SubscriptionTracking object.""" def __init__(self, open_tracking_substitution_tag=None): """Create a OpenTrackingSubstitutionTag object @@ -25,6 +25,15 @@ def open_tracking_substitution_tag(self): @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): diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py index 288f94eee..18fdf9d81 100644 --- a/sendgrid/helpers/mail/personalization.py +++ b/sendgrid/helpers/mail/personalization.py @@ -46,8 +46,16 @@ def add_to(self, email): :type email: Email """ if email.substitutions: - for substition in email.substitutions: - self.add_substitution(substition) + 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 @@ -96,6 +104,7 @@ def subject(self): Char length requirements, according to the RFC: https://stackoverflow.com/a/1592310 + :rtype: string """ return self._subject @@ -140,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): diff --git a/sendgrid/helpers/mail/plain_text_content.py b/sendgrid/helpers/mail/plain_text_content.py index a6051a5c9..f2609d512 100644 --- a/sendgrid/helpers/mail/plain_text_content.py +++ b/sendgrid/helpers/mail/plain_text_content.py @@ -6,38 +6,44 @@ class PlainTextContent(Content): """Plain text content to be included in your email. """ - def __init__(self, value): - """Create a PlainTextContent with the specified MIME type and value. + def __init__(self, content): + """Create a PlainTextContent with the specified MIME type and content. - :param value: The actual text content. + :param content: The actual text content. + :type content: string """ - self._value = None + self._content = None self._validator = ValidateApiKey() - if value is not None: - self.value = value + if content is not None: + self.content = content @property - def type(self): - """The actual text content. + def mime_type(self): + """The MIME type. :rtype: string """ return "text/plain" @property - def value(self): + def content(self): """The actual text content. :rtype: string """ - return self._value + return self._content + + @content.setter + def content(self, value): + """The actual text content. - @value.setter - def value(self, value): + :param value: The actual text content. + :type value: string + """ self._validator.validate_message_dict(value) - self._value = value + self._content = value def get(self): """ @@ -47,6 +53,9 @@ def get(self): :rtype: dict """ content = {} - content["type"] = self.type - content["value"] = self.value + 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/sandbox_mode.py b/sendgrid/helpers/mail/sandbox_mode.py index 72ca6be4a..84506d87b 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. """ @@ -25,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 664e38c60..428a184b8 100644 --- a/sendgrid/helpers/mail/section.py +++ b/sendgrid/helpers/mail/section.py @@ -2,7 +2,13 @@ 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.""" + """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 @@ -13,18 +19,36 @@ def __init__(self, key=None, value=None): @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 index 0190f6300..d88b605eb 100644 --- a/sendgrid/helpers/mail/send_at.py +++ b/sendgrid/helpers/mail/send_at.py @@ -16,7 +16,7 @@ def __init__(self, send_at=None, p=None): :param send_at: Unix timestamp :type send_at: integer :param name: p is the Personalization object or Personalization object index - :type name: Personalization or integer, optional + :type name: Personalization, integer, optional """ self._send_at = None self._personalization = None @@ -36,14 +36,28 @@ def send_at(self): @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): diff --git a/sendgrid/helpers/mail/spam_check.py b/sendgrid/helpers/mail/spam_check.py index fa1616ad7..958438678 100644 --- a/sendgrid/helpers/mail/spam_check.py +++ b/sendgrid/helpers/mail/spam_check.py @@ -35,20 +35,34 @@ 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): + """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: @@ -57,14 +71,21 @@ def threshold(self, 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): + """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: diff --git a/sendgrid/helpers/mail/spam_threshold.py b/sendgrid/helpers/mail/spam_threshold.py index 0e99b7d57..2ee7fd44c 100644 --- a/sendgrid/helpers/mail/spam_threshold.py +++ b/sendgrid/helpers/mail/spam_threshold.py @@ -22,7 +22,7 @@ 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.. + considered as spam. :rtype: integer """ @@ -30,6 +30,17 @@ def spam_threshold(self): @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): diff --git a/sendgrid/helpers/mail/spam_url.py b/sendgrid/helpers/mail/spam_url.py index 676a7cce2..fe39fb557 100644 --- a/sendgrid/helpers/mail/spam_url.py +++ b/sendgrid/helpers/mail/spam_url.py @@ -25,6 +25,13 @@ def spam_url(self): @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): diff --git a/sendgrid/helpers/mail/subject.py b/sendgrid/helpers/mail/subject.py index a39cfe34d..e220e42bf 100644 --- a/sendgrid/helpers/mail/subject.py +++ b/sendgrid/helpers/mail/subject.py @@ -7,7 +7,7 @@ def __init__(self, subject, p=None): :param subject: The subject for an email :type subject: string :param name: p is the Personalization object or Personalization object index - :type name: Personalization or integer, optional + :type name: Personalization, integer, optional """ self._subject = None self._personalization = None @@ -26,14 +26,28 @@ def subject(self): @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): diff --git a/sendgrid/helpers/mail/subscription_html.py b/sendgrid/helpers/mail/subscription_html.py index 9e19678fa..4f2222ae0 100644 --- a/sendgrid/helpers/mail/subscription_html.py +++ b/sendgrid/helpers/mail/subscription_html.py @@ -1,5 +1,5 @@ class SubscriptionHtml(object): - """The SubscriptionHtml of an SubscriptionTracking.""" + """The HTML of an SubscriptionTracking.""" def __init__(self, subscription_html=None): """Create a SubscriptionHtml object @@ -25,6 +25,13 @@ def subscription_html(self): @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): diff --git a/sendgrid/helpers/mail/subscription_substitution_tag.py b/sendgrid/helpers/mail/subscription_substitution_tag.py index 5e5ab93eb..ed55fb999 100644 --- a/sendgrid/helpers/mail/subscription_substitution_tag.py +++ b/sendgrid/helpers/mail/subscription_substitution_tag.py @@ -1,5 +1,5 @@ class SubscriptionSubstitutionTag(object): - """The SubscriptionSubstitutionTag of an SubscriptionTracking.""" + """The subscription substitution tag of an SubscriptionTracking.""" def __init__(self, subscription_substitution_tag=None): """Create a SubscriptionSubstitutionTag object @@ -28,6 +28,17 @@ def subscription_substitution_tag(self): @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): diff --git a/sendgrid/helpers/mail/subscription_text.py b/sendgrid/helpers/mail/subscription_text.py index 2b36829c2..a5ff211c0 100644 --- a/sendgrid/helpers/mail/subscription_text.py +++ b/sendgrid/helpers/mail/subscription_text.py @@ -1,5 +1,5 @@ class SubscriptionText(object): - """The SubscriptionText of an SubscriptionTracking.""" + """The text of an SubscriptionTracking.""" def __init__(self, subscription_text=None): """Create a SubscriptionText object @@ -25,6 +25,13 @@ def subscription_text(self): @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): diff --git a/sendgrid/helpers/mail/subscription_tracking.py b/sendgrid/helpers/mail/subscription_tracking.py index 22e207a07..f0e1f80e7 100644 --- a/sendgrid/helpers/mail/subscription_tracking.py +++ b/sendgrid/helpers/mail/subscription_tracking.py @@ -40,30 +40,51 @@ 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 @@ -72,12 +93,24 @@ 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): diff --git a/sendgrid/helpers/mail/substitution.py b/sendgrid/helpers/mail/substitution.py index 98ad4b3f6..0f3b9a932 100644 --- a/sendgrid/helpers/mail/substitution.py +++ b/sendgrid/helpers/mail/substitution.py @@ -13,7 +13,7 @@ def __init__(self, key=None, value=None, p=None): :param name: p is the Personalization object or Personalization object index :type name: Personalization or integer :param name: p is the Personalization object or Personalization object index - :type name: Personalization or integer, optional + :type name: Personalization, integer, optional """ self._key = None self._value = None @@ -28,26 +28,53 @@ def __init__(self, key=None, value=None, p=None): @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): diff --git a/sendgrid/helpers/mail/template_id.py b/sendgrid/helpers/mail/template_id.py index 50b8cc9ff..260da42ea 100644 --- a/sendgrid/helpers/mail/template_id.py +++ b/sendgrid/helpers/mail/template_id.py @@ -1,5 +1,5 @@ class TemplateId(object): - """The TemplateId of an Attachment.""" + """The template ID of an Attachment object.""" def __init__(self, template_id=None): """Create a TemplateId object @@ -22,6 +22,11 @@ def template_id(self): @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): diff --git a/sendgrid/helpers/mail/tracking_settings.py b/sendgrid/helpers/mail/tracking_settings.py index cb19e2bae..064c50fe5 100644 --- a/sendgrid/helpers/mail/tracking_settings.py +++ b/sendgrid/helpers/mail/tracking_settings.py @@ -1,13 +1,43 @@ 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 +48,11 @@ 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 +65,11 @@ 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 +82,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 +99,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 index 045e1dc78..0b786ed73 100644 --- a/sendgrid/helpers/mail/utm_campaign.py +++ b/sendgrid/helpers/mail/utm_campaign.py @@ -1,5 +1,5 @@ class UtmCampaign(object): - """The UtmCampaign of an Ganalytics.""" + """The utm campaign of an Ganalytics object.""" def __init__(self, utm_campaign=None): """Create a UtmCampaign object @@ -23,6 +23,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): diff --git a/sendgrid/helpers/mail/utm_content.py b/sendgrid/helpers/mail/utm_content.py index e16bc1fd7..d34ad083d 100644 --- a/sendgrid/helpers/mail/utm_content.py +++ b/sendgrid/helpers/mail/utm_content.py @@ -1,5 +1,5 @@ class UtmContent(object): - """The UtmContent of an Ganalytics.""" + """The utm content of an Ganalytics object.""" def __init__(self, utm_content=None): """Create a UtmContent object @@ -23,6 +23,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 def get(self): diff --git a/sendgrid/helpers/mail/utm_medium.py b/sendgrid/helpers/mail/utm_medium.py index 328c81eb9..1d662c615 100644 --- a/sendgrid/helpers/mail/utm_medium.py +++ b/sendgrid/helpers/mail/utm_medium.py @@ -1,5 +1,5 @@ class UtmMedium(object): - """The UtmMedium of an Ganalytics.""" + """The utm medium of an Ganalytics object.""" def __init__(self, utm_medium=None): """Create a UtmMedium object @@ -23,6 +23,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 def get(self): diff --git a/sendgrid/helpers/mail/utm_source.py b/sendgrid/helpers/mail/utm_source.py index 55b6cd769..55207caca 100644 --- a/sendgrid/helpers/mail/utm_source.py +++ b/sendgrid/helpers/mail/utm_source.py @@ -1,5 +1,5 @@ class UtmSource(object): - """The UtmSource of an Ganalytics.""" + """The utm source of an Ganalytics object.""" def __init__(self, utm_source=None): """Create a UtmSource object @@ -24,6 +24,13 @@ def utm_source(self): @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): diff --git a/sendgrid/helpers/mail/utm_term.py b/sendgrid/helpers/mail/utm_term.py index 04fadb044..795b09985 100644 --- a/sendgrid/helpers/mail/utm_term.py +++ b/sendgrid/helpers/mail/utm_term.py @@ -1,6 +1,6 @@ class UtmTerm(object): - """The UtmTerm of an Ganalytics.""" - + """The utm term of an Ganalytics object.""" + def __init__(self, utm_term=None): """Create a UtmTerm object @@ -23,6 +23,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 def get(self): diff --git a/sendgrid/helpers/mail/validators.py b/sendgrid/helpers/mail/validators.py index 293d626d6..c044de384 100644 --- a/sendgrid/helpers/mail/validators.py +++ b/sendgrid/helpers/mail/validators.py @@ -1,7 +1,4 @@ from .exceptions import ApiKeyIncludedException -################################################################ -# Email content validators -################################################################ class ValidateApiKey(object): @@ -10,10 +7,12 @@ class ValidateApiKey(object): 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 @@ -30,12 +29,11 @@ def __init__(self, regex_strings=None, use_default=True): 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 + 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 @@ -57,12 +55,11 @@ def validate_message_dict(self, request_body): 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: 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 d89c87ec7..a4673922f 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -65,6 +65,7 @@ def __init__( @property def _default_headers(self): + """Set the default header for a SendGrid v3 API call""" headers = { "Authorization": 'Bearer {}'.format(self.api_key), "User-agent": self.useragent, @@ -76,8 +77,17 @@ def _default_headers(self): return headers def reset_request_headers(self): + self.client.request_headers = self._default_headers def send(self, message): - response = self.client.mail.send.post(request_body=message.get()) + """Make a SendGrid v3 API request with the request body generated by the Mail object + + :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/test/test_send.py b/test/test_inbound_send.py similarity index 100% rename from test/test_send.py rename to test/test_inbound_send.py diff --git a/test/test_mail.py b/test/test_mail.py index b0154b08d..933cbf278 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -22,6 +22,7 @@ DynamicTemplateData, Email, FooterSettings, + From, Ganalytics, Header, Mail, @@ -32,58 +33,17 @@ Section, SendGridException, SpamCheck, + Subject, SubscriptionTracking, Substitution, TrackingSettings, + To, ValidateApiKey ) class UnitTests(unittest.TestCase): - 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 as e: - pass - - # Exception not thrown - else: - self.fail("Should have failed as SendGrid API key included") - # 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 @@ -216,23 +176,23 @@ def test_send_a_single_email_to_multiple_recipients(self): ) def test_multiple_emails_to_multiple_recipients(self): - from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException, Substitution + 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={ + 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={ + 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'), @@ -242,7 +202,7 @@ def test_multiple_emails_to_multiple_recipients(self): 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'''{ @@ -275,6 +235,7 @@ def test_multiple_emails_to_multiple_recipients(self): ] }, { + "subject": "Override Global Subject", "substitutions": { "-github-": "https://example.com/test0", "-name-": "Example Name Substitution 0", @@ -294,31 +255,28 @@ def test_multiple_emails_to_multiple_recipients(self): def test_kitchen_sink(self): 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, + 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) - import time - import datetime + self.maxDiff = None message = Mail() # Define Personalizations - message.to = To('test1@sendgrid.com', 'Example User1', p=0) + message.to = To('test1@example.com', 'Example User1', p=0) message.to = [ - To('test2@sendgrid.com', 'Example User2', p=0), - To('test3@sendgrid.com', 'Example User3', p=0) + 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) @@ -403,9 +361,9 @@ def test_kitchen_sink(self): # The values below this comment are global to entire message - message.from_email = From('dx@sendgrid.com', 'DX') + message.from_email = From('dx@example.com', 'DX') - message.reply_to = ReplyTo('dx_reply@sendgrid.com', 'DX Reply') + message.reply_to = ReplyTo('dx_reply@example.com', 'DX Reply') message.subject = Subject('Sending with SendGrid is Fun 2') @@ -417,19 +375,19 @@ def test_kitchen_sink(self): ] message.attachment = Attachment(FileContent('base64 encoded content 1'), - FileType('application/pdf'), FileName('balance_001.pdf'), + FileType('application/pdf'), Disposition('attachment'), ContentId('Content ID 1')) message.attachment = [ Attachment(FileContent('base64 encoded content 2'), - FileType('image/png'), FileName('banner.png'), + FileType('image/png'), Disposition('inline'), ContentId('Content ID 2')), Attachment(FileContent('base64 encoded content 3'), - FileType('image/png'), FileName('banner2.png'), + FileType('image/png'), Disposition('inline'), ContentId('Content ID 3')) ] @@ -562,7 +520,7 @@ def test_kitchen_sink(self): "transactional6": "false" }, "from": { - "email": "dx@sendgrid.com", + "email": "dx@example.com", "name": "DX" }, "headers": { @@ -646,15 +604,15 @@ def test_kitchen_sink(self): }, "to": [ { - "email": "test1@sendgrid.com", + "email": "test1@example.com", "name": "Example User1" }, { - "email": "test2@sendgrid.com", + "email": "test2@example.com", "name": "Example User2" }, { - "email": "test3@sendgrid.com", + "email": "test3@example.com", "name": "Example User3" } ] @@ -725,7 +683,7 @@ def test_kitchen_sink(self): } ], "reply_to": { - "email": "dx_reply@sendgrid.com", + "email": "dx_reply@example.com", "name": "DX Reply" }, "sections": { @@ -763,7 +721,7 @@ def test_kitchen_sink(self): }''') ) - # Send a Single Email to a Single Recipient + # 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 @@ -857,103 +815,102 @@ def test_single_email_to_a_single_recipient_with_dynamic_templates(self): }''') ) + 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): - return - # """ 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) - # ) + 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): - return - # self.assertRaises(ValueError, Asm, 1, list(range(26))) + self.assertRaises(ValueError, Asm, 1, list(range(26))) def test_disable_tracking(self): - return - # 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): - return - # personalization = Personalization() - # personalization.substitutions = [{'a': 0}] - - def test_from_emailmessage(self): - return - # message = EmailMessage() - # body = 'message that is not urgent' - # try: - # message.set_content(body) - # except AttributeError: - # # Python2 - # message.set_payload(body) - # message.set_default_type('text/plain') - # message['Subject'] = 'URGENT TITLE' - # message['From'] = 'test@example.com' - # message['To'] = 'test@sendgrid.com' - # mail = Mail.from_EmailMessage(message) - # self.assertEqual(mail.subject.get(), 'URGENT TITLE') - # self.assertEqual(mail.from_email.email, 'test@example.com') - # self.assertEqual(len(mail.personalizations), 1) - # self.assertEqual(len(mail.personalizations[0].tos), 1) - # self.assertEqual(mail.personalizations[0].tos[0], {'email': 'test@sendgrid.com'}) - # self.assertEqual(len(mail.contents), 1) - # content = mail.contents[0] - # self.assertEqual(content.type, 'text/plain') - # self.assertEqual(content.value, 'message that is not urgent') + 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_sendgrid.py b/test/test_sendgrid.py index 24666ca5b..d88234efb 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -19,52 +19,6 @@ def setUpClass(cls): 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_api_key_init(self): self.assertEqual(self.sg.api_key, os.environ.get('SENDGRID_API_KEY')) # Support the previous naming convention for API keys @@ -123,28 +77,6 @@ def test_reset_request_headers(self): 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 = 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) - self.assertEqual( - 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} diff --git a/use_cases/README.md b/use_cases/README.md index e9af0ba7e..14b36e638 100644 --- a/use_cases/README.md +++ b/use_cases/README.md @@ -4,20 +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 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) -* [Sending HTML-Only Content](sending_html_content.md) -* [Transactional Templates](transational_templates.md) -* [Integrate with Slack Events API](slack_event_api_integration.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 6cd6f0a79..ff0ff5072 100644 --- a/use_cases/aws.md +++ b/use_cases/aws.md @@ -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) 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/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/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 index 86029dd59..ba38b19aa 100644 --- a/use_cases/sending_html_content.md +++ b/use_cases/sending_html_content.md @@ -6,9 +6,9 @@ Currently, we require both HTML and Plain Text content for improved deliverabili ## Using `beautifulsoup4` ```python -import sendgrid import os -from sendgrid.helpers.mail import Email, Content, Mail +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import From, To, Subject, PlainTextContent, HtmlContent, Mail try: # Python 3 import urllib.request as urllib @@ -33,26 +33,25 @@ html_text = """ """ -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) -from_email = Email("test@exmaple.com") -subject = "subject" -to_emila = Email("to_email@example.com") -html_content = Content("text/html", html_text) - -mail = Mail(from_email, subject, to_email, html_content) +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_content = Content("text/plain", plain_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 = sg.client.mail.send.post(request_body=mail.get()) + 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() - -print(response.status_code) -print(response.body) -print(response.headers) ``` \ No newline at end of file diff --git a/use_cases/slack_event_api_integration.md b/use_cases/slack_event_api_integration.md index eac8d195a..828149433 100644 --- a/use_cases/slack_event_api_integration.md +++ b/use_cases/slack_event_api_integration.md @@ -14,10 +14,11 @@ Once this is done, we can subscribe to [events on Slack](https://api.slack.com/e from slackeventsapi import SlackEventAdapter from slackclient import SlackClient import os -import sendgrid -from sendgrid.helpers.mail import * +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')) -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) SLACK_VERIFICATION_TOKEN = os.environ["SLACK_VERIFICATION_TOKEN"] slack_events_adapter = SlackEventAdapter(SLACK_VERIFICATION_TOKEN, "/slack/events") @@ -33,12 +34,13 @@ def handle_message(event_data): def send_email(message): - from_email = Email("slack_integration@example.com") - to_email = Email("test@example.com") - subject = "Psst... Someone needs help!" - content = Content("text/plain", message) - mail = Mail(from_email, subject, to_email, content) - response = sg.client.mail.send.post(request_body=mail.get()) + 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 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 From 2fd62903bbbfb133eac8c98b5e364d84758967e9 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Mon, 1 Apr 2019 18:20:58 -0700 Subject: [PATCH 189/462] pep8 --- sendgrid/helpers/endpoints/ip/unassigned.py | 20 +- sendgrid/helpers/inbound/send.py | 3 +- sendgrid/helpers/mail/asm.py | 5 +- sendgrid/helpers/mail/attachment.py | 61 +- sendgrid/helpers/mail/batch_id.py | 10 +- sendgrid/helpers/mail/bcc_settings.py | 2 +- sendgrid/helpers/mail/bcc_settings_email.py | 5 +- sendgrid/helpers/mail/category.py | 3 +- sendgrid/helpers/mail/click_tracking.py | 8 +- sendgrid/helpers/mail/content.py | 3 +- sendgrid/helpers/mail/content_id.py | 17 +- sendgrid/helpers/mail/custom_arg.py | 18 +- sendgrid/helpers/mail/disposition.py | 48 +- .../helpers/mail/dynamic_template_data.py | 17 +- sendgrid/helpers/mail/email.py | 49 +- sendgrid/helpers/mail/exceptions.py | 2 +- sendgrid/helpers/mail/file_content.py | 2 +- sendgrid/helpers/mail/file_name.py | 2 +- sendgrid/helpers/mail/file_type.py | 2 +- sendgrid/helpers/mail/footer_html.py | 4 +- sendgrid/helpers/mail/footer_settings.py | 4 +- sendgrid/helpers/mail/footer_text.py | 4 +- sendgrid/helpers/mail/ganalytics.py | 2 +- sendgrid/helpers/mail/group_id.py | 4 +- sendgrid/helpers/mail/groups_to_display.py | 22 +- sendgrid/helpers/mail/header.py | 12 +- sendgrid/helpers/mail/html_content.py | 2 +- sendgrid/helpers/mail/ip_pool_name.py | 7 +- sendgrid/helpers/mail/mail.py | 280 +++++--- sendgrid/helpers/mail/mail_settings.py | 25 +- sendgrid/helpers/mail/mime_type.py | 2 +- sendgrid/helpers/mail/open_tracking.py | 9 +- .../mail/open_tracking_substitution_tag.py | 35 +- sendgrid/helpers/mail/personalization.py | 2 +- sendgrid/helpers/mail/plain_text_content.py | 3 +- sendgrid/helpers/mail/reply_to.py | 2 +- sendgrid/helpers/mail/sandbox_mode.py | 2 +- sendgrid/helpers/mail/section.py | 10 +- sendgrid/helpers/mail/send_at.py | 30 +- sendgrid/helpers/mail/spam_check.py | 8 +- sendgrid/helpers/mail/spam_threshold.py | 32 +- sendgrid/helpers/mail/spam_url.py | 18 +- sendgrid/helpers/mail/subject.py | 10 +- sendgrid/helpers/mail/subscription_html.py | 17 +- .../mail/subscription_substitution_tag.py | 47 +- sendgrid/helpers/mail/subscription_text.py | 17 +- .../helpers/mail/subscription_tracking.py | 29 +- sendgrid/helpers/mail/substitution.py | 20 +- sendgrid/helpers/mail/template_id.py | 2 +- sendgrid/helpers/mail/tracking_settings.py | 44 +- sendgrid/helpers/mail/utm_campaign.py | 4 +- sendgrid/helpers/mail/utm_content.py | 4 +- sendgrid/helpers/mail/utm_medium.py | 4 +- sendgrid/helpers/mail/utm_source.py | 16 +- sendgrid/helpers/mail/utm_term.py | 6 +- sendgrid/helpers/mail/validators.py | 11 +- sendgrid/sendgrid.py | 32 +- test/test_inbound_send.py | 20 +- test/test_mail.py | 675 ++++++++++-------- test/test_sendgrid.py | 4 +- test/test_spam_check.py | 2 +- 61 files changed, 978 insertions(+), 782 deletions(-) diff --git a/sendgrid/helpers/endpoints/ip/unassigned.py b/sendgrid/helpers/endpoints/ip/unassigned.py index 51ddc8b2a..ba41e7b9c 100644 --- a/sendgrid/helpers/endpoints/ip/unassigned.py +++ b/sendgrid/helpers/endpoints/ip/unassigned.py @@ -20,18 +20,24 @@ def unassigned(data, as_json=False): 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 + unassigned returns a listing of the IP addresses that are allocated but have 0 users assigned - - data (response.body from sg.client.ips.get()) + + data (response.body from sg.client.ips.get()) as_json False -> get list of dicts True -> get json object example: - sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) - - params = {'subuser': 'test_string', 'ip': 'test_string', 'limit': 1, 'exclude_whitelabels': 'true', 'offset': 1} + 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 @@ -49,4 +55,4 @@ def unassigned(data, as_json=False): no_subusers.add(current_ip) ret_val = format_ret(no_subusers, as_json=as_json) - return ret_val \ No newline at end of file + return ret_val diff --git a/sendgrid/helpers/inbound/send.py b/sendgrid/helpers/inbound/send.py index e3526eb7c..8dbfa68d2 100644 --- a/sendgrid/helpers/inbound/send.py +++ b/sendgrid/helpers/inbound/send.py @@ -40,7 +40,8 @@ def url(self): 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') diff --git a/sendgrid/helpers/mail/asm.py b/sendgrid/helpers/mail/asm.py index 086d178d1..62db8372a 100644 --- a/sendgrid/helpers/mail/asm.py +++ b/sendgrid/helpers/mail/asm.py @@ -1,6 +1,7 @@ from .group_id import GroupId from .groups_to_display import GroupsToDisplay + class Asm(object): """An object specifying unsubscribe behavior.""" @@ -52,8 +53,8 @@ def groups_to_display(self): @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. Max of 25 groups. + """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 diff --git a/sendgrid/helpers/mail/attachment.py b/sendgrid/helpers/mail/attachment.py index fa5ca45cf..f8b53a688 100644 --- a/sendgrid/helpers/mail/attachment.py +++ b/sendgrid/helpers/mail/attachment.py @@ -4,10 +4,17 @@ from .disposition import Disposition from .content_id import ContentId + class Attachment(object): """An attachment to be included with an email.""" - def __init__(self, file_content=None, file_name=None, file_type=None, disposition=None, content_id=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 @@ -16,17 +23,21 @@ def __init__(self, file_content=None, file_name=None, file_type=None, dispositi :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. + :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. + 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 @@ -37,16 +48,16 @@ def __init__(self, file_content=None, file_name=None, file_type=None, dispositi 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 @@ -136,13 +147,16 @@ def disposition(self, value): 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. + :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): @@ -169,8 +183,9 @@ def content_id(self, value): 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. + 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): diff --git a/sendgrid/helpers/mail/batch_id.py b/sendgrid/helpers/mail/batch_id.py index 53b7378a0..de9960ca2 100644 --- a/sendgrid/helpers/mail/batch_id.py +++ b/sendgrid/helpers/mail/batch_id.py @@ -1,8 +1,10 @@ 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.""" + """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. diff --git a/sendgrid/helpers/mail/bcc_settings.py b/sendgrid/helpers/mail/bcc_settings.py index 87e290d16..eeb8ba100 100644 --- a/sendgrid/helpers/mail/bcc_settings.py +++ b/sendgrid/helpers/mail/bcc_settings.py @@ -18,7 +18,7 @@ def __init__(self, enable=None, email=None): if enable is not None: self.enable = enable - + if email is not None: self.email = email diff --git a/sendgrid/helpers/mail/bcc_settings_email.py b/sendgrid/helpers/mail/bcc_settings_email.py index dbbb9645d..2c2847e23 100644 --- a/sendgrid/helpers/mail/bcc_settings_email.py +++ b/sendgrid/helpers/mail/bcc_settings_email.py @@ -4,11 +4,12 @@ class BccSettingsEmail(object): 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 + :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 diff --git a/sendgrid/helpers/mail/category.py b/sendgrid/helpers/mail/category.py index 541fc2229..0a6394c25 100644 --- a/sendgrid/helpers/mail/category.py +++ b/sendgrid/helpers/mail/category.py @@ -24,7 +24,8 @@ def name(self): 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. + :param value: The name of this Category. Must be less than 255 + characters. :type value: string """ self._name = value diff --git a/sendgrid/helpers/mail/click_tracking.py b/sendgrid/helpers/mail/click_tracking.py index eba5eace5..edfba41e8 100644 --- a/sendgrid/helpers/mail/click_tracking.py +++ b/sendgrid/helpers/mail/click_tracking.py @@ -14,7 +14,7 @@ def __init__(self, enable=None, enable_text=None): if enable is not None: self.enable = enable - + if enable_text is not None: self.enable_text = enable_text @@ -39,7 +39,7 @@ def enable(self, value): def enable_text(self): """Indicates if this setting should be included in the text/plain portion of your email. - + :rtype: boolean """ return self._enable_text @@ -48,8 +48,8 @@ def enable_text(self): 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 + + :param value: Indicates if this setting should be included in the text/plain portion of your email. :type value: boolean """ diff --git a/sendgrid/helpers/mail/content.py b/sendgrid/helpers/mail/content.py index 133b3b3d5..88bab33d0 100644 --- a/sendgrid/helpers/mail/content.py +++ b/sendgrid/helpers/mail/content.py @@ -39,7 +39,8 @@ def mime_type(self, value): """The MIME type of the content you are including in your email. For example, "text/plain" or "text/html". - :param value: The MIME type of the content you are including in your email. + :param value: The MIME type of the content you are including in your + email. For example, "text/plain" or "text/html". :type value: string """ diff --git a/sendgrid/helpers/mail/content_id.py b/sendgrid/helpers/mail/content_id.py index f789a0990..0fff30107 100644 --- a/sendgrid/helpers/mail/content_id.py +++ b/sendgrid/helpers/mail/content_id.py @@ -5,20 +5,22 @@ 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. + 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. + 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 """ @@ -27,8 +29,9 @@ def content_id(self): @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. + 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 diff --git a/sendgrid/helpers/mail/custom_arg.py b/sendgrid/helpers/mail/custom_arg.py index 39e15a68e..63b225573 100644 --- a/sendgrid/helpers/mail/custom_arg.py +++ b/sendgrid/helpers/mail/custom_arg.py @@ -9,18 +9,19 @@ class CustomArg(object): 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 + :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: @@ -48,7 +49,7 @@ def key(self, value): @property def value(self): """Value of this CustomArg. - + :rtype: string """ return self._value @@ -56,7 +57,7 @@ def value(self): @value.setter def value(self, value): """Value of this CustomArg. - + :param value: Value of this CustomArg. :type value: string """ @@ -65,7 +66,7 @@ def value(self, value): @property def personalization(self): """The Personalization object or Personalization object index - + :rtype: Personalization, integer """ return self._personalization @@ -73,8 +74,9 @@ def personalization(self): @personalization.setter def personalization(self, value): """The Personalization object or Personalization object index - - :param value: The Personalization object or Personalization object index + + :param value: The Personalization object or Personalization object + index :type value: Personalization, integer """ self._personalization = value diff --git a/sendgrid/helpers/mail/disposition.py b/sendgrid/helpers/mail/disposition.py index 9dcb6dc2c..a0bdc3543 100644 --- a/sendgrid/helpers/mail/disposition.py +++ b/sendgrid/helpers/mail/disposition.py @@ -1,21 +1,25 @@ class Disposition(object): - """The content-disposition of the Attachment specifying how you would like the attachment - to be displayed.""" + """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. + :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 @@ -23,11 +27,12 @@ def __init__(self, disposition=None): 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. + - "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. + If unspecified, "attachment" is used. Must be one of the two + choices. :rtype: string """ @@ -37,19 +42,22 @@ def disposition(self): 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. + - "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. + If unspecified, "attachment" is used. Must be one of the two + choices. - :param value: The content-disposition of the attachment, specifying display style. + :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. + - "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. + If unspecified, "attachment" is used. Must be one of the two + choices. :type value: string """ self._disposition = value diff --git a/sendgrid/helpers/mail/dynamic_template_data.py b/sendgrid/helpers/mail/dynamic_template_data.py index d22b0c8b1..d682dbf2d 100644 --- a/sendgrid/helpers/mail/dynamic_template_data.py +++ b/sendgrid/helpers/mail/dynamic_template_data.py @@ -1,14 +1,16 @@ class DynamicTemplateData(object): - """To send a dynamic template, specify the template ID with the template_id parameter. + """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. + 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 + :param name: p is the Personalization object or Personalization object + index :type name: Personalization, integer, optional """ self._dynamic_template_data = None @@ -39,7 +41,7 @@ def dynamic_template_data(self, value): @property def personalization(self): """The Personalization object or Personalization object index - + :rtype: Personalization, integer """ return self._personalization @@ -47,8 +49,9 @@ def personalization(self): @personalization.setter def personalization(self, value): """The Personalization object or Personalization object index - - :param value: The Personalization object or Personalization object index + + :param value: The Personalization object or Personalization object + index :type value: Personalization, integer """ self._personalization = value diff --git a/sendgrid/helpers/mail/email.py b/sendgrid/helpers/mail/email.py index 2fc67a34e..5f2d541a5 100644 --- a/sendgrid/helpers/mail/email.py +++ b/sendgrid/helpers/mail/email.py @@ -37,7 +37,8 @@ def __init__(self, :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 + :param p: p is the Personalization object or Personalization object + index :type p: Personalization, integer, optional """ self._name = None @@ -56,10 +57,10 @@ def __init__(self, if name is not None: self.name = name - + if substitutions is not None: self.substitutions = substitutions - + if subject is not None: self.subject = subject @@ -121,26 +122,29 @@ def email(self, value): @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. - + """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) """ 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 + """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 @@ -165,7 +169,7 @@ def subject(self, value): @property def personalization(self): """The Personalization object or Personalization object index - + :rtype: Personalization, integer """ return self._personalization @@ -173,16 +177,18 @@ def personalization(self): @personalization.setter def personalization(self, value): """The Personalization object or Personalization object index - - :param 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 " + + :param email_info: Allows passing emails as + "Example Name " :type email_info: string """ name, email = rfc822.parseaddr(email_info) @@ -216,4 +222,3 @@ def get(self): 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 17a2fb135..db17848c5 100644 --- a/sendgrid/helpers/mail/exceptions.py +++ b/sendgrid/helpers/mail/exceptions.py @@ -15,7 +15,7 @@ def __init__(self, expression="Email body", message="SendGrid API Key detected"): """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 diff --git a/sendgrid/helpers/mail/file_content.py b/sendgrid/helpers/mail/file_content.py index a7bc130c9..c5c0d6995 100644 --- a/sendgrid/helpers/mail/file_content.py +++ b/sendgrid/helpers/mail/file_content.py @@ -8,7 +8,7 @@ def __init__(self, file_content=None): :type file_content: string, optional """ self._file_content = None - + if file_content is not None: self.file_content = file_content diff --git a/sendgrid/helpers/mail/file_name.py b/sendgrid/helpers/mail/file_name.py index 89124a77c..3a4e3ff2b 100644 --- a/sendgrid/helpers/mail/file_name.py +++ b/sendgrid/helpers/mail/file_name.py @@ -8,7 +8,7 @@ def __init__(self, file_name=None): :type file_name: string, optional """ self._file_name = None - + if file_name is not None: self.file_name = file_name diff --git a/sendgrid/helpers/mail/file_type.py b/sendgrid/helpers/mail/file_type.py index 27db03c5b..a30e6edfa 100644 --- a/sendgrid/helpers/mail/file_type.py +++ b/sendgrid/helpers/mail/file_type.py @@ -8,7 +8,7 @@ def __init__(self, file_type=None): :type file_type: string, optional """ self._file_type = None - + if file_type is not None: self.file_type = file_type diff --git a/sendgrid/helpers/mail/footer_html.py b/sendgrid/helpers/mail/footer_html.py index ceca52a28..c8b5ac1a5 100644 --- a/sendgrid/helpers/mail/footer_html.py +++ b/sendgrid/helpers/mail/footer_html.py @@ -8,7 +8,7 @@ def __init__(self, footer_html=None): :type footer_html: string, optional """ self._footer_html = None - + if footer_html is not None: self.footer_html = footer_html @@ -36,4 +36,4 @@ def get(self): :returns: This FooterHtml, ready for use in a request body. :rtype: string """ - return self.footer_html \ No newline at end of file + return self.footer_html diff --git a/sendgrid/helpers/mail/footer_settings.py b/sendgrid/helpers/mail/footer_settings.py index 662222ad4..143ab6853 100644 --- a/sendgrid/helpers/mail/footer_settings.py +++ b/sendgrid/helpers/mail/footer_settings.py @@ -17,10 +17,10 @@ def __init__(self, enable=None, text=None, 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 diff --git a/sendgrid/helpers/mail/footer_text.py b/sendgrid/helpers/mail/footer_text.py index d87acdaa6..06f968920 100644 --- a/sendgrid/helpers/mail/footer_text.py +++ b/sendgrid/helpers/mail/footer_text.py @@ -8,7 +8,7 @@ def __init__(self, footer_text=None): :type footer_text: string, optional """ self._footer_text = None - + if footer_text is not None: self.footer_text = footer_text @@ -36,4 +36,4 @@ def get(self): :returns: This FooterText, ready for use in a request body. :rtype: string """ - return self.footer_text \ No newline at end of file + return self.footer_text diff --git a/sendgrid/helpers/mail/ganalytics.py b/sendgrid/helpers/mail/ganalytics.py index a3e89e918..f1e88f4b9 100644 --- a/sendgrid/helpers/mail/ganalytics.py +++ b/sendgrid/helpers/mail/ganalytics.py @@ -78,7 +78,7 @@ def utm_source(self): 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 diff --git a/sendgrid/helpers/mail/group_id.py b/sendgrid/helpers/mail/group_id.py index 89a0a9c37..667785310 100644 --- a/sendgrid/helpers/mail/group_id.py +++ b/sendgrid/helpers/mail/group_id.py @@ -8,7 +8,7 @@ def __init__(self, group_id=None): :type group_id: integer, optional """ self._group_id = None - + if group_id is not None: self.group_id = group_id @@ -36,4 +36,4 @@ def get(self): :returns: This GroupId, ready for use in a request body. :rtype: integer """ - return self.group_id \ No newline at end of file + return self.group_id diff --git a/sendgrid/helpers/mail/groups_to_display.py b/sendgrid/helpers/mail/groups_to_display.py index 0dd84b0cb..2e3fca77a 100644 --- a/sendgrid/helpers/mail/groups_to_display.py +++ b/sendgrid/helpers/mail/groups_to_display.py @@ -1,22 +1,23 @@ class GroupsToDisplay(object): - """The unsubscribe groups that you would like to be displayed on the unsubscribe - preferences page..""" + """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. + :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 + """An array containing the unsubscribe groups that you would like to be displayed on the unsubscribe preferences page. :rtype: array(int) @@ -25,11 +26,12 @@ def groups_to_display(self): @groups_to_display.setter def groups_to_display(self, value): - """An array containing the unsubscribe groups that you would like to be + """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. + :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: @@ -43,4 +45,4 @@ def get(self): :returns: This GroupsToDisplay, ready for use in a request body. :rtype: array of integers """ - return self.groups_to_display \ No newline at end of file + return self.groups_to_display diff --git a/sendgrid/helpers/mail/header.py b/sendgrid/helpers/mail/header.py index 4dddde4d9..7f3bd4c4d 100644 --- a/sendgrid/helpers/mail/header.py +++ b/sendgrid/helpers/mail/header.py @@ -14,7 +14,8 @@ def __init__(self, key=None, value=None, p=None): :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 + :param name: p is the Personalization object or Personalization object + index :type name: Personalization, integer, optional """ self._key = None @@ -61,11 +62,11 @@ def value(self, value): :type value: string """ self._value = value - + @property def personalization(self): """The Personalization object or Personalization object index - + :rtype: Personalization, integer """ return self._personalization @@ -73,8 +74,9 @@ def personalization(self): @personalization.setter def personalization(self, value): """The Personalization object or Personalization object index - - :param value: The Personalization object or Personalization object index + + :param value: The Personalization object or Personalization object + index :type value: Personalization, integer """ self._personalization = value diff --git a/sendgrid/helpers/mail/html_content.py b/sendgrid/helpers/mail/html_content.py index 40abcf8f1..c3f40d53c 100644 --- a/sendgrid/helpers/mail/html_content.py +++ b/sendgrid/helpers/mail/html_content.py @@ -53,7 +53,7 @@ def get(self): 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 index 93ed840f1..8ba8d91b3 100644 --- a/sendgrid/helpers/mail/ip_pool_name.py +++ b/sendgrid/helpers/mail/ip_pool_name.py @@ -4,11 +4,12 @@ class IpPoolName(object): 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. + :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 @@ -36,4 +37,4 @@ def get(self): :returns: This IpPoolName, ready for use in a request body. :rtype: string """ - return self.ip_pool_name \ No newline at end of file + return self.ip_pool_name diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 7a222beb5..3636ac653 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -32,7 +32,7 @@ def __init__( is_multiple=False): """ 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 @@ -84,7 +84,7 @@ def __str__(self): def _ensure_append(self, new_items, append_to, index=0): """Ensure an item is appended to a list or create a new empty list - + :param new_items: the item(s) to append :type new_items: list(obj) :param append_to: the list on which to append the items @@ -98,7 +98,7 @@ def _ensure_append(self, new_items, append_to, index=0): def _ensure_insert(self, new_items, insert_to): """Ensure an item is inserted to a list or create a new empty list - + :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 @@ -110,36 +110,39 @@ def _ensure_insert(self, new_items, insert_to): def _flatten_dicts(self, dicts): """Flatten a dict - + :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()} def _get_or_none(self, from_obj): """Get the JSON representation of the object, else return None - - :param from_obj: Get the JSON representation of the object, + + :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): + 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 + :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 == True: + if is_multiple is True: if isinstance(emails, list): for email in emails: personalization = Personalization() @@ -157,7 +160,7 @@ def _set_emails(self, emails, global_substitutions=None, is_multiple=False, p=0) else: for p in self.personalizations: p.add_substitution(global_substitutions) - else: + else: try: personalization = self._personalizations[p] has_internal_personalization = True @@ -177,21 +180,21 @@ def _set_emails(self, emails, global_substitutions=None, is_multiple=False, p=0) personalization.add_substitution(substitution) else: personalization.add_substitution(global_substitutions) - + if not has_internal_personalization: self.add_personalization(personalization, index=p) @property def personalizations(self): """A list of one or more Personaliztion objects - + :rtype: list(Personalization) """ return self._personalizations def add_personalization(self, personalization, index=0): """Add a Personaliztion object - + :param personalizations: Add a Personalization object :type personalizations: Personalization :param index: The index where to add the Personalization @@ -203,18 +206,19 @@ def add_personalization(self, personalization, index=0): @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 + :param p: p is the Personalization object or Personalization object + index :type p: Personalization, integer, optional """ if isinstance(to_emails, list): @@ -231,19 +235,21 @@ def to(self, to_emails, global_substitutions=None, is_multiple=False, p=0): 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): + 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 + :param p: p is the Personalization object or Personalization object + index :type p: Personalization, integer, optional """ - + if isinstance(to_email, list): for email in to_email: if isinstance(email, str): @@ -263,18 +269,19 @@ def add_to(self, to_email, global_substitutions=None, is_multiple=False, p=0): @property 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 + :param p: p is the Personalization object or Personalization object + index :type p: Personalization, integer, optional """ if isinstance(cc_emails, list): @@ -291,16 +298,18 @@ def cc(self, cc_emails, global_substitutions=None, is_multiple=False, p=0): 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): + 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 + :param p: p is the Personalization object or Personalization object + index :type p: Personalization, integer, optional """ if isinstance(cc_email, str): @@ -309,25 +318,32 @@ def add_cc(self, cc_email, global_substitutions=None, is_multiple=False, p=0): 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) + self._set_emails( + cc_email, global_substitutions, is_multiple=is_multiple, p=p) @property def bcc(self): pass - + @bcc.setter - def bcc(self, bcc_emails, global_substitutions=None, is_multiple=False, p=0): + 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 + :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): @@ -342,16 +358,22 @@ def bcc(self, bcc_emails, global_substitutions=None, is_multiple=False, p=0): 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): + 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 + :param p: p is the Personalization object or Personalization object + index :type p: Personalization, integer, optional """ if isinstance(bcc_email, str): @@ -360,35 +382,42 @@ def add_bcc(self, bcc_email, global_substitutions=None, is_multiple=False, p=0): 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) + self._set_emails( + bcc_email, + global_substitutions, + is_multiple=is_multiple, + p=p) @property def subject(self): """The global Subject object - + :rtype: Subject """ return self._subject - + @subject.setter def subject(self, 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] + 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) + self.add_personalization( + personalization, + index=value.personalization) else: self._subject = value else: @@ -397,7 +426,7 @@ def subject(self, value): @property def headers(self): """A list of global Header objects - + :rtype: list(Header) """ return self._headers @@ -427,7 +456,8 @@ def add_header(self, header): """ if header.personalization is not None: try: - personalization = self._personalizations[header.personalization] + personalization = \ + self._personalizations[header.personalization] has_internal_personalization = True except IndexError: personalization = Personalization() @@ -437,13 +467,16 @@ def add_header(self, header): personalization.add_header(Header(k, v)) else: personalization.add_header(header) - + if not has_internal_personalization: - self.add_personalization(personalization, index=header.personalization) - else: + 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) + self._headers = self._ensure_append( + Header(k, v), self._headers) else: self._headers = self._ensure_append(header, self._headers) @@ -472,15 +505,17 @@ def add_substitution(self, substitution): """ if substitution.personalization: try: - personalization = self._personalizations[substitution.personalization] + 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) + self.add_personalization( + personalization, index=substitution.personalization) else: if isinstance(substitution, list): for s in substitution: @@ -493,7 +528,7 @@ def add_substitution(self, substitution): @property def custom_args(self): """A list of global CustomArg objects - + :rtype: list(CustomArg) """ return self._custom_args @@ -506,7 +541,8 @@ def custom_arg(self): 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 + :param value: A list of CustomArg objects or a dict of custom arg + key/values :type value: CustomArg, list(CustomArg), dict """ if isinstance(custom_arg, list): @@ -523,7 +559,8 @@ def add_custom_arg(self, custom_arg): """ if custom_arg.personalization is not None: try: - personalization = self._personalizations[custom_arg.personalization] + personalization = \ + self._personalizations[custom_arg.personalization] has_internal_personalization = True except IndexError: personalization = Personalization() @@ -533,45 +570,50 @@ def add_custom_arg(self, custom_arg): 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: + 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) + self._custom_args = self._ensure_append( + CustomArg(k, v), self._custom_args) else: - self._custom_args = self._ensure_append(custom_arg, self._custom_args) + self._custom_args = self._ensure_append( + custom_arg, self._custom_args) @property def send_at(self): """The global SendAt object - + :rtype: SendAt """ return self._send_at - + @send_at.setter def send_at(self, value): - """A unix timestamp specifying when your email should + """A unix timestamp specifying when your email should be delivered. - - :param value: A unix timestamp specifying when your email should + + :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] + 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) + self.add_personalization( + personalization, index=value.personalization) else: self._send_at = value else: @@ -580,11 +622,11 @@ def send_at(self, value): @property def dynamic_template_data(self): pass - + @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 """ @@ -597,22 +639,23 @@ def dynamic_template_data(self, value): personalization = Personalization() has_internal_personalization = False personalization.dynamic_template_data = value.dynamic_template_data - + if not has_internal_personalization: - self.add_personalization(personalization, index=value.personalization) + self.add_personalization( + personalization, index=value.personalization) @property def from_email(self): """The email address of the sender - + :rtype: From """ return self._from_email - + @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 """ @@ -625,15 +668,15 @@ def from_email(self, value): @property def reply_to(self): """The reply to email address - + :rtype: ReplyTo """ return self._reply_to - + @reply_to.setter def reply_to(self, value): """The reply to email address - + :param value: The reply to email address :type value: ReplyTo, str, tuple """ @@ -646,7 +689,7 @@ def reply_to(self, value): @property def contents(self): """The contents of the email - + :rtype: list(Content) """ return self._contents @@ -654,11 +697,11 @@ def contents(self): @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) """ @@ -670,7 +713,7 @@ def content(self, contents): 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 @@ -686,24 +729,25 @@ def add_content(self, content, mime_type=None): index = len(self._contents) else: index = 0 - self._contents = self._ensure_append(content, self._contents, index=index) + self._contents = self._ensure_append( + content, self._contents, index=index) @property def attachments(self): """The attachments to this email - + :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) """ @@ -715,7 +759,7 @@ def attachment(self, attachment): def add_attachment(self, attachment): """Add an attachment to this email - + :param attachment: Add an attachment to this email :type attachment: Attachment """ @@ -724,15 +768,15 @@ def add_attachment(self, attachment): @property def template_id(self): """The transactional template id for this email - + :rtype: TemplateId """ return self._template_id - + @template_id.setter def template_id(self, value): """The transactional template id for this email - + :param value: The transactional template id for this email :type value: TemplateId """ @@ -744,7 +788,7 @@ def template_id(self, value): @property def sections(self): """The block sections of code to be used as substitutions - + :rtype: Section """ return self._sections @@ -752,22 +796,22 @@ def sections(self): @property def section(self): pass - + @section.setter def section(self, section): """The block sections of code to be used as substitutions - + :rtype: Section, list(Section) """ if isinstance(section, list): for h in section: self.add_section(h) else: - self.add_section(section) + 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 """ @@ -776,7 +820,7 @@ def add_section(self, section): @property def categories(self): """The categories assigned to this message - + :rtype: list(Category) """ return self._categories @@ -784,13 +828,13 @@ def categories(self): @property def category(self): pass - + @category.setter - def category(self, categories): + 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) @@ -799,23 +843,23 @@ def category(self, categories): def add_category(self, category): """Add a category assigned to this message - + :rtype: Category - """ + """ self._categories = self._ensure_append(category, self._categories) @property def batch_id(self): """The batch id for this email - + :rtype: BatchId """ return self._batch_id - + @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 """ @@ -824,15 +868,15 @@ def batch_id(self, 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 """ @@ -841,15 +885,15 @@ def asm(self, 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 """ @@ -858,15 +902,15 @@ def ip_pool_name(self, 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 """ @@ -875,15 +919,15 @@ def mail_settings(self, 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 """ diff --git a/sendgrid/helpers/mail/mail_settings.py b/sendgrid/helpers/mail/mail_settings.py index 1df8458a9..45b7db77f 100644 --- a/sendgrid/helpers/mail/mail_settings.py +++ b/sendgrid/helpers/mail/mail_settings.py @@ -2,24 +2,26 @@ class MailSettings(object): """A collection of mail settings that specify how to handle this email.""" def __init__(self, - bcc_settings = None, - bypass_list_management = None, - footer_settings = None, - sandbox_mode = None, - spam_check = None): + 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 + :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 + :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 + :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 @@ -29,7 +31,7 @@ def __init__(self, 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 @@ -122,7 +124,8 @@ def spam_check(self): 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. + :param value: How this MailSettings requests email to be checked + for spam. :type value: SpamCheck """ self._spam_check = value diff --git a/sendgrid/helpers/mail/mime_type.py b/sendgrid/helpers/mail/mime_type.py index 9e89253f9..0d0c9b3b3 100644 --- a/sendgrid/helpers/mail/mime_type.py +++ b/sendgrid/helpers/mail/mime_type.py @@ -2,4 +2,4 @@ class MimeType(object): """The MIME type of the content included in your email. """ text = "text/plain" - html = "text/html" \ No newline at end of file + html = "text/html" diff --git a/sendgrid/helpers/mail/open_tracking.py b/sendgrid/helpers/mail/open_tracking.py index 4765170f3..32d90e841 100644 --- a/sendgrid/helpers/mail/open_tracking.py +++ b/sendgrid/helpers/mail/open_tracking.py @@ -18,7 +18,7 @@ def __init__(self, enable=None, substitution_tag=None): if enable is not None: self.enable = enable - + if substitution_tag is not None: self.substitution_tag = substitution_tag @@ -55,9 +55,10 @@ def substitution_tag(self, value): 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. + :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 """ diff --git a/sendgrid/helpers/mail/open_tracking_substitution_tag.py b/sendgrid/helpers/mail/open_tracking_substitution_tag.py index 79724fcff..d9967f9cd 100644 --- a/sendgrid/helpers/mail/open_tracking_substitution_tag.py +++ b/sendgrid/helpers/mail/open_tracking_substitution_tag.py @@ -4,20 +4,22 @@ class OpenTrackingSubstitutionTag(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. + :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 + 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. + """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 """ @@ -25,13 +27,13 @@ def open_tracking_substitution_tag(self): @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. + """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. + :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 @@ -40,7 +42,8 @@ def get(self): """ Get a JSON-ready representation of this OpenTrackingSubstitutionTag. - :returns: This OpenTrackingSubstitutionTag, ready for use in a request body. + :returns: This OpenTrackingSubstitutionTag, ready for use in a request + body. :rtype: string """ - return self.open_tracking_substitution_tag \ No newline at end of file + return self.open_tracking_substitution_tag diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py index 18fdf9d81..835933017 100644 --- a/sendgrid/helpers/mail/personalization.py +++ b/sendgrid/helpers/mail/personalization.py @@ -104,7 +104,7 @@ def subject(self): Char length requirements, according to the RFC: https://stackoverflow.com/a/1592310 - + :rtype: string """ return self._subject diff --git a/sendgrid/helpers/mail/plain_text_content.py b/sendgrid/helpers/mail/plain_text_content.py index f2609d512..78bce1a85 100644 --- a/sendgrid/helpers/mail/plain_text_content.py +++ b/sendgrid/helpers/mail/plain_text_content.py @@ -17,7 +17,6 @@ def __init__(self, content): if content is not None: self.content = content - @property def mime_type(self): @@ -55,7 +54,7 @@ def get(self): 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 index faf90f674..731e2ad8f 100644 --- a/sendgrid/helpers/mail/reply_to.py +++ b/sendgrid/helpers/mail/reply_to.py @@ -2,4 +2,4 @@ class ReplyTo(Email): - """A reply to email address with an optional name.""" \ No newline at end of file + """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 84506d87b..e8990ee93 100644 --- a/sendgrid/helpers/mail/sandbox_mode.py +++ b/sendgrid/helpers/mail/sandbox_mode.py @@ -10,7 +10,7 @@ def __init__(self, enable=None): :type enable: boolean, optional """ self._enable = None - + if enable is not None: self.enable = enable diff --git a/sendgrid/helpers/mail/section.py b/sendgrid/helpers/mail/section.py index 428a184b8..cea949b14 100644 --- a/sendgrid/helpers/mail/section.py +++ b/sendgrid/helpers/mail/section.py @@ -3,7 +3,7 @@ class Section(object): def __init__(self, key=None, value=None): """Create a section with the given key and value. - + :param key: section of code key :type key: string :param value: section of code value @@ -20,7 +20,7 @@ def __init__(self, key=None, value=None): @property def key(self): """A section of code's key. - + :rtype key: string """ return self._key @@ -28,7 +28,7 @@ def key(self): @key.setter def key(self, value): """A section of code's key. - + :param key: section of code key :type key: string """ @@ -37,7 +37,7 @@ def key(self, value): @property def value(self): """A section of code's value. - + :rtype: string """ return self._value @@ -45,7 +45,7 @@ def value(self): @value.setter def value(self, value): """A section of code's value. - + :param value: A section of code's value. :type value: string """ diff --git a/sendgrid/helpers/mail/send_at.py b/sendgrid/helpers/mail/send_at.py index d88b605eb..6e3a1541a 100644 --- a/sendgrid/helpers/mail/send_at.py +++ b/sendgrid/helpers/mail/send_at.py @@ -1,21 +1,22 @@ 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 + """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 + """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 + :param name: p is the Personalization object or Personalization object + index :type name: Personalization, integer, optional """ self._send_at = None @@ -46,7 +47,7 @@ def send_at(self, value): @property def personalization(self): """The Personalization object or Personalization object index - + :rtype: Personalization, integer """ return self._personalization @@ -54,8 +55,9 @@ def personalization(self): @personalization.setter def personalization(self, value): """The Personalization object or Personalization object index - - :param value: The Personalization object or Personalization object index + + :param value: The Personalization object or Personalization object + index :type value: Personalization, integer """ self._personalization = value diff --git a/sendgrid/helpers/mail/spam_check.py b/sendgrid/helpers/mail/spam_check.py index 958438678..c584f8cff 100644 --- a/sendgrid/helpers/mail/spam_check.py +++ b/sendgrid/helpers/mail/spam_check.py @@ -1,6 +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.""" @@ -58,9 +59,10 @@ def threshold(self, value): 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. + :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): diff --git a/sendgrid/helpers/mail/spam_threshold.py b/sendgrid/helpers/mail/spam_threshold.py index 2ee7fd44c..45053dbdf 100644 --- a/sendgrid/helpers/mail/spam_threshold.py +++ b/sendgrid/helpers/mail/spam_threshold.py @@ -1,27 +1,27 @@ 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 + """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 + :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 + """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 @@ -30,14 +30,14 @@ def spam_threshold(self): @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 + """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 + :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 """ @@ -50,4 +50,4 @@ def get(self): :returns: This SpamThreshold, ready for use in a request body. :rtype: integer """ - return self.spam_threshold \ No newline at end of file + return self.spam_threshold diff --git a/sendgrid/helpers/mail/spam_url.py b/sendgrid/helpers/mail/spam_url.py index fe39fb557..529438ced 100644 --- a/sendgrid/helpers/mail/spam_url.py +++ b/sendgrid/helpers/mail/spam_url.py @@ -1,22 +1,22 @@ class SpamUrl(object): - """An Inbound Parse URL that you would like a copy of your email + """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. + :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 + """An Inbound Parse URL that you would like a copy of your email along with the spam report to be sent to. :rtype: string @@ -25,11 +25,11 @@ def spam_url(self): @spam_url.setter def spam_url(self, value): - """An Inbound Parse URL that you would like a copy of your email + """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. + :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 @@ -41,4 +41,4 @@ def get(self): :returns: This SpamUrl, ready for use in a request body. :rtype: string """ - return self.spam_url \ No newline at end of file + return self.spam_url diff --git a/sendgrid/helpers/mail/subject.py b/sendgrid/helpers/mail/subject.py index e220e42bf..a7fecec8f 100644 --- a/sendgrid/helpers/mail/subject.py +++ b/sendgrid/helpers/mail/subject.py @@ -6,7 +6,8 @@ def __init__(self, subject, p=None): :param subject: The subject for an email :type subject: string - :param name: p is the Personalization object or Personalization object index + :param name: p is the Personalization object or Personalization object + index :type name: Personalization, integer, optional """ self._subject = None @@ -36,7 +37,7 @@ def subject(self, value): @property def personalization(self): """The Personalization object or Personalization object index - + :rtype: Personalization, integer """ return self._personalization @@ -44,8 +45,9 @@ def personalization(self): @personalization.setter def personalization(self, value): """The Personalization object or Personalization object index - - :param value: The Personalization object or Personalization object index + + :param value: The Personalization object or Personalization object + index :type value: Personalization, integer """ self._personalization = value diff --git a/sendgrid/helpers/mail/subscription_html.py b/sendgrid/helpers/mail/subscription_html.py index 4f2222ae0..bfe8629f8 100644 --- a/sendgrid/helpers/mail/subscription_html.py +++ b/sendgrid/helpers/mail/subscription_html.py @@ -4,19 +4,19 @@ class SubscriptionHtml(object): 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 + :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. + """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 @@ -25,11 +25,12 @@ def subscription_html(self): @subscription_html.setter def subscription_html(self, value): - """Html to be appended to the email, with the subscription tracking link. + """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 <% %> + :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 @@ -41,4 +42,4 @@ def get(self): :returns: This SubscriptionHtml, ready for use in a request body. :rtype: string """ - return self.subscription_html \ No newline at end of file + return self.subscription_html diff --git a/sendgrid/helpers/mail/subscription_substitution_tag.py b/sendgrid/helpers/mail/subscription_substitution_tag.py index ed55fb999..9a7d6a95d 100644 --- a/sendgrid/helpers/mail/subscription_substitution_tag.py +++ b/sendgrid/helpers/mail/subscription_substitution_tag.py @@ -4,23 +4,28 @@ class SubscriptionSubstitutionTag(object): 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. + :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. + """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 """ @@ -28,15 +33,16 @@ def subscription_substitution_tag(self): @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. + """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 @@ -45,7 +51,8 @@ def get(self): """ Get a JSON-ready representation of this SubscriptionSubstitutionTag. - :returns: This SubscriptionSubstitutionTag, ready for use in a request body. + :returns: This SubscriptionSubstitutionTag, ready for use in a request + body. :rtype: string """ - return self.subscription_substitution_tag \ No newline at end of file + return self.subscription_substitution_tag diff --git a/sendgrid/helpers/mail/subscription_text.py b/sendgrid/helpers/mail/subscription_text.py index a5ff211c0..ca20894af 100644 --- a/sendgrid/helpers/mail/subscription_text.py +++ b/sendgrid/helpers/mail/subscription_text.py @@ -4,19 +4,19 @@ class SubscriptionText(object): 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 + :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. + """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 @@ -25,11 +25,12 @@ def subscription_text(self): @subscription_text.setter def subscription_text(self, value): - """Text to be appended to the email, with the subscription tracking link. + """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 <% %> + :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 @@ -41,4 +42,4 @@ def get(self): :returns: This SubscriptionText, ready for use in a request body. :rtype: string """ - return self.subscription_text \ No newline at end of file + return self.subscription_text diff --git a/sendgrid/helpers/mail/subscription_tracking.py b/sendgrid/helpers/mail/subscription_tracking.py index f0e1f80e7..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,7 +14,8 @@ 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 = None @@ -61,8 +63,9 @@ 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 <% %> + :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 @@ -81,8 +84,9 @@ 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 <% %> + :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 @@ -105,10 +109,12 @@ def substitution_tag(self, value): `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. + :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 @@ -131,5 +137,6 @@ def get(self): subscription_tracking["html"] = self.html.get() if self.substitution_tag is not None: - subscription_tracking["substitution_tag"] = self.substitution_tag.get() + 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 0f3b9a932..d0e59837e 100644 --- a/sendgrid/helpers/mail/substitution.py +++ b/sendgrid/helpers/mail/substitution.py @@ -10,9 +10,8 @@ def __init__(self, key=None, value=None, p=None): :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 or integer - :param name: p is the Personalization object or Personalization object index + :param name: p is the Personalization object or Personalization object + index :type name: Personalization, integer, optional """ self._key = None @@ -29,7 +28,7 @@ def __init__(self, key=None, value=None, p=None): @property def key(self): """The substitution key. - + :rtype key: string """ return self._key @@ -37,7 +36,7 @@ def key(self): @key.setter def key(self, value): """The substitution key. - + :param key: The substitution key. :type key: string """ @@ -46,7 +45,7 @@ def key(self, value): @property def value(self): """The substitution value. - + :rtype value: string """ return self._value @@ -54,7 +53,7 @@ def value(self): @value.setter def value(self, value): """The substitution value. - + :param value: The substitution value. :type value: string """ @@ -63,7 +62,7 @@ def value(self, value): @property def personalization(self): """The Personalization object or Personalization object index - + :rtype: Personalization, integer """ return self._personalization @@ -71,8 +70,9 @@ def personalization(self): @personalization.setter def personalization(self, value): """The Personalization object or Personalization object index - - :param value: The Personalization object or Personalization object index + + :param value: The Personalization object or Personalization object + index :type value: Personalization, integer """ self._personalization = value diff --git a/sendgrid/helpers/mail/template_id.py b/sendgrid/helpers/mail/template_id.py index 260da42ea..f18c5f588 100644 --- a/sendgrid/helpers/mail/template_id.py +++ b/sendgrid/helpers/mail/template_id.py @@ -8,7 +8,7 @@ def __init__(self, template_id=None): :type template_id: string, optional """ self._template_id = None - + if template_id is not None: self.template_id = template_id diff --git a/sendgrid/helpers/mail/tracking_settings.py b/sendgrid/helpers/mail/tracking_settings.py index 064c50fe5..d5f2e4fd5 100644 --- a/sendgrid/helpers/mail/tracking_settings.py +++ b/sendgrid/helpers/mail/tracking_settings.py @@ -2,23 +2,29 @@ class TrackingSettings(object): """Settings to track how recipients interact with your email.""" def __init__(self, - click_tracking = None, - open_tracking = None, - subscription_tracking = None, - ganalytics = None): + 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. + + :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. + :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. + :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. + :param ganalytics: Allows you to enable tracking provided by Google + Analytics. :type ganalytics: Ganalytics, optional """ self._click_tracking = None @@ -28,13 +34,13 @@ def __init__(self, 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 @@ -50,7 +56,8 @@ def click_tracking(self): 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. + :param value: Allows you to track whether a recipient clicked a link + in your email. :type value: ClickTracking """ self._click_tracking = value @@ -67,7 +74,8 @@ def open_tracking(self): 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. + :param value: Allows you to track whether a recipient opened your + email. :type value: OpenTracking """ self._open_tracking = value diff --git a/sendgrid/helpers/mail/utm_campaign.py b/sendgrid/helpers/mail/utm_campaign.py index 0b786ed73..5b2006824 100644 --- a/sendgrid/helpers/mail/utm_campaign.py +++ b/sendgrid/helpers/mail/utm_campaign.py @@ -9,7 +9,7 @@ def __init__(self, utm_campaign=None): :type utm_campaign: string, optional """ self._utm_campaign = None - + if utm_campaign is not None: self.utm_campaign = utm_campaign @@ -37,4 +37,4 @@ def get(self): :returns: This UtmCampaign, ready for use in a request body. :rtype: string """ - return self.utm_campaign \ No newline at end of file + return self.utm_campaign diff --git a/sendgrid/helpers/mail/utm_content.py b/sendgrid/helpers/mail/utm_content.py index d34ad083d..e2a8ccff6 100644 --- a/sendgrid/helpers/mail/utm_content.py +++ b/sendgrid/helpers/mail/utm_content.py @@ -9,7 +9,7 @@ def __init__(self, utm_content=None): :type utm_content: string, optional """ self._utm_content = None - + if utm_content is not None: self.utm_content = utm_content @@ -37,4 +37,4 @@ def get(self): :returns: This UtmContent, ready for use in a request body. :rtype: string """ - return self.utm_content \ No newline at end of file + return self.utm_content diff --git a/sendgrid/helpers/mail/utm_medium.py b/sendgrid/helpers/mail/utm_medium.py index 1d662c615..3687a0b2b 100644 --- a/sendgrid/helpers/mail/utm_medium.py +++ b/sendgrid/helpers/mail/utm_medium.py @@ -9,7 +9,7 @@ def __init__(self, utm_medium=None): :type utm_medium: string, optional """ self._utm_medium = None - + if utm_medium is not None: self.utm_medium = utm_medium @@ -37,4 +37,4 @@ def get(self): :returns: This UtmMedium, ready for use in a request body. :rtype: string """ - return self.utm_medium \ No newline at end of file + return self.utm_medium diff --git a/sendgrid/helpers/mail/utm_source.py b/sendgrid/helpers/mail/utm_source.py index 55207caca..841ba0a80 100644 --- a/sendgrid/helpers/mail/utm_source.py +++ b/sendgrid/helpers/mail/utm_source.py @@ -4,19 +4,19 @@ class UtmSource(object): def __init__(self, utm_source=None): """Create a UtmSource object - :param utm_source: Name of the referrer source. + :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) + """Name of the referrer source. (e.g. Google, SomeDomain.com, or + Marketing Email) :rtype: string """ @@ -24,10 +24,10 @@ def utm_source(self): @utm_source.setter def utm_source(self, value): - """Name of the referrer source. - (e.g. Google, SomeDomain.com, or Marketing Email) + """Name of the referrer source. (e.g. Google, SomeDomain.com, or + Marketing Email) - :param value: Name of the referrer source. + :param value: Name of the referrer source. (e.g. Google, SomeDomain.com, or Marketing Email) :type value: string """ @@ -40,4 +40,4 @@ def get(self): :returns: This UtmSource, ready for use in a request body. :rtype: string """ - return self.utm_source \ No newline at end of file + return self.utm_source diff --git a/sendgrid/helpers/mail/utm_term.py b/sendgrid/helpers/mail/utm_term.py index 795b09985..e8a9518a6 100644 --- a/sendgrid/helpers/mail/utm_term.py +++ b/sendgrid/helpers/mail/utm_term.py @@ -1,6 +1,6 @@ class UtmTerm(object): """The utm term of an Ganalytics object.""" - + def __init__(self, utm_term=None): """Create a UtmTerm object @@ -9,7 +9,7 @@ def __init__(self, utm_term=None): :type utm_term: string, optional """ self._utm_term = None - + if utm_term is not None: self.utm_term = utm_term @@ -37,4 +37,4 @@ def get(self): :returns: This UtmTerm, ready for use in a request body. :rtype: string """ - return self.utm_term \ No newline at end of file + return self.utm_term diff --git a/sendgrid/helpers/mail/validators.py b/sendgrid/helpers/mail/validators.py index c044de384..00e3276e4 100644 --- a/sendgrid/helpers/mail/validators.py +++ b/sendgrid/helpers/mail/validators.py @@ -30,10 +30,12 @@ def __init__(self, regex_strings=None, use_default=True): def validate_message_dict(self, request_body): """With the JSON dict that will be sent to SendGrid's API, check the content for SendGrid API keys - throw exception if found. - - :param request_body: The JSON dict that will be sent to SendGrid's API. + + :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 + :raise ApiKeyIncludedException: If any content in request_body + matches regex """ # Handle string in edge-case @@ -58,7 +60,8 @@ def validate_message_text(self, message_string): :param message_string: message that will be sent :type message_string: string - :raises ApiKeyIncludedException: If message_string matches a regex string + :raises ApiKeyIncludedException: If message_string matches a regex + string """ if isinstance(message_string, str): for regex in self.regexes: diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index a4673922f..166466d02 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -23,7 +23,7 @@ class SendGridAPIClient(object): """The SendGrid API Client. Use this object to interact with the v3 API. For example: - sg = sendgrid.SendGridAPIClient(api_key=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()) @@ -39,15 +39,18 @@ def __init__( impersonate_subuser=None): """ Construct SendGrid v3 API object. - Note that the underlying client is being set up during initialization, therefore changing - attributes in runtime will not affect HTTP client behaviour. + 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" + :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 + :param impersonate_subuser: the subuser to impersonate. Will be passed + by "On-Behalf-Of" header by underlying + client. See + https://sendgrid.com/docs/User_Guide/Settings/subusers.html + for more details :type impersonate_subuser: string :param host: base URL for API calls :type host: string @@ -59,9 +62,10 @@ def __init__( 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): @@ -81,9 +85,11 @@ def reset_request_headers(self): self.client.request_headers = self._default_headers def send(self, message): - """Make a SendGrid v3 API request with the request body generated by the Mail object + """Make a SendGrid v3 API request with the request body generated by + the Mail object - :param message: The SendGrid v3 API request body generated by the Mail object + :param message: The SendGrid v3 API request body generated by the Mail + object :type message: Mail """ if isinstance(message, dict): diff --git a/test/test_inbound_send.py b/test/test_inbound_send.py index 360ea7a82..19ee5de1d 100644 --- a/test/test_inbound_send.py +++ b/test/test_inbound_send.py @@ -27,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 index 933cbf278..4a7e9e458 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -46,209 +46,226 @@ class UnitTests(unittest.TestCase): # 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 + 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 = 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", + "type": "text/plain", "value": "and easy to do anywhere, even with Python" - }, + }, { - "type": "text/html", + "type": "text/html", "value": "and easy to do anywhere, even with Python" } - ], + ], "from": { - "email": "test+from@example.com", + "email": "test+from@example.com", "name": "Example From Name" - }, + }, "personalizations": [ { "to": [ { - "email": "test+to@example.com", + "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 + """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') + 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", + "type": "text/plain", "value": "and easy to do anywhere, even with Python" - }, + }, { - "type": "text/html", + "type": "text/html", "value": "and easy to do anywhere, even with Python" } - ], + ], "from": { - "email": "test+from@example.com", + "email": "test+from@example.com", "name": "Example From Name" - }, + }, "personalizations": [ { "to": [ { - "email": "test+to@example.com", + "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 + 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')) + 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", + "type": "text/plain", "value": "and easy to do anywhere, even with Python" - }, + }, { - "type": "text/html", + "type": "text/html", "value": "and easy to do anywhere, even with Python" } - ], + ], "from": { - "email": "test+from@example.com", + "email": "test+from@example.com", "name": "Example From Name" - }, + }, "personalizations": [ { "to": [ { - "email": "test+to0@example.com", + "email": "test+to0@example.com", "name": "Example To Name 0" - }, + }, { - "email": "test+to1@example.com", + "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 + 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')), + 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'), - ]) + 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) + 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", + "type": "text/plain", "value": "Hello -name-, your URL is -github-, email sent at -time-" - }, + }, { - "type": "text/html", + "type": "text/html", "value": "Hello -name-, your URL is here email sent at -time-" } - ], + ], "from": { - "email": "test+from@example.com", + "email": "test+from@example.com", "name": "Example From Name" - }, + }, "personalizations": [ { "substitutions": { - "-github-": "https://example.com/test1", - "-name-": "Example Name Substitution 1", + "-github-": "https://example.com/test1", + "-name-": "Example Name Substitution 1", "-time-": "2019-01-01 00:00:00" - }, + }, "to": [ { - "email": "test+to1@example.com", + "email": "test+to1@example.com", "name": "Example Name 1" } ] - }, + }, { "subject": "Override Global Subject", "substitutions": { - "-github-": "https://example.com/test0", - "-name-": "Example Name Substitution 0", + "-github-": "https://example.com/test0", + "-name-": "Example Name Substitution 0", "-time-": "2019-01-01 00:00:00" - }, + }, "to": [ { - "email": "test+to0@example.com", + "email": "test+to0@example.com", "name": "Example Name 0" } ] } - ], + ], "subject": "Hi -name-" }''') ) @@ -256,37 +273,37 @@ def test_multiple_emails_to_multiple_recipients(self): def test_kitchen_sink(self): from sendgrid.helpers.mail import ( Mail, From, To, Cc, Bcc, Subject, Substitution, Header, - CustomArg, SendAt, Content, MimeType, Attachment, FileName, + CustomArg, SendAt, Content, MimeType, Attachment, FileName, FileContent, FileType, Disposition, ContentId, TemplateId, Section, ReplyTo, Category, BatchId, Asm, GroupId, GroupsToDisplay, - IpPoolName, MailSettings, BccSettings, BccSettingsEmail, + 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 + # Define Personalizations message.to = To('test1@example.com', 'Example User1', p=0) - message.to = [ + 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 = [ + 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 = [ + message.bcc = [ Bcc('test8@example.com', 'Example User8', p=0), Bcc('test9@example.com', 'Example User9', p=0) ] @@ -317,19 +334,19 @@ def test_kitchen_sink(self): message.send_at = SendAt(1461775051, p=0) message.to = To('test10@example.com', 'Example User10', p=1) - message.to = [ + 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 = [ + 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 = [ + message.bcc = [ Bcc('test17@example.com', 'Example User17', p=1), Bcc('test18@example.com', 'Example User18', p=1) ] @@ -367,37 +384,46 @@ def test_kitchen_sink(self): 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( + 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 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')) + 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.template_id = TemplateId( + '13b8f94f-bcae-4ec6-b752-70d6cb59f932') - message.section = Section('%section1%', 'Substitution for Section 1 Tag') + 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') + Section('%section3%', 'Substitution for Section 3 Tag') ] message.header = Header('X-Test9', 'Test9') @@ -425,23 +451,27 @@ def test_kitchen_sink(self): message.batch_id = BatchId("HkJ5yLYULb7Rj8GKSx7u025ouWVlMgAi") - message.asm = Asm(GroupId(1), GroupsToDisplay([1,2,3,4])) + 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.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.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")) + 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.open_tracking = OpenTracking( + True, OpenTrackingSubstitutionTag("open_tracking")) tracking_settings.subscription_tracking = SubscriptionTracking( - True, + True, SubscriptionText("Goodbye"), SubscriptionHtml("Goodbye!"), SubscriptionSubstitutionTag("unsubscribe")) @@ -457,264 +487,264 @@ def test_kitchen_sink(self): message.get(), json.loads(r'''{ "asm": { - "group_id": 1, + "group_id": 1, "groups_to_display": [ - 1, - 2, - 3, + 1, + 2, + 3, 4 ] - }, + }, "attachments": [ { - "content": "base64 encoded content 3", - "content_id": "Content ID 3", - "disposition": "inline", - "filename": "banner2.png", + "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", + "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", + "content": "base64 encoded content 1", + "content_id": "Content ID 1", + "disposition": "attachment", + "filename": "balance_001.pdf", "type": "application/pdf" } - ], - "batch_id": "HkJ5yLYULb7Rj8GKSx7u025ouWVlMgAi", + ], + "batch_id": "HkJ5yLYULb7Rj8GKSx7u025ouWVlMgAi", "categories": [ - "Category 2", - "Category 1", - "Category 2", + "Category 2", + "Category 1", + "Category 2", "Category 1" - ], + ], "content": [ { - "type": "text/plain", + "type": "text/plain", "value": "and easy to do anywhere, even with Python" - }, + }, { - "type": "text/html", + "type": "text/html", "value": "and easy to do anywhere, even with Python" - }, + }, { - "type": "text/calendar", + "type": "text/calendar", "value": "Party Time!!" - }, + }, { - "type": "text/custom", + "type": "text/custom", "value": "Party Time 2!!" } - ], + ], "custom_args": { - "marketing5": "false", - "marketing6": "true", - "transactional5": "true", + "marketing5": "false", + "marketing6": "true", + "transactional5": "true", "transactional6": "false" - }, + }, "from": { - "email": "dx@example.com", + "email": "dx@example.com", "name": "DX" - }, + }, "headers": { - "X-Test10": "Test10", - "X-Test11": "Test11", - "X-Test12": "Test12", + "X-Test10": "Test10", + "X-Test11": "Test11", + "X-Test12": "Test12", "X-Test9": "Test9" - }, - "ip_pool_name": "IP Pool Name", + }, + "ip_pool_name": "IP Pool Name", "mail_settings": { "bcc": { - "email": "bcc@twilio.com", + "email": "bcc@twilio.com", "enable": false - }, + }, "bypass_list_management": { "enable": false - }, + }, "footer": { - "enable": true, - "html": "w00t!", + "enable": true, + "html": "w00t!", "text": "w00t" - }, + }, "sandbox_mode": { "enable": true - }, + }, "spam_check": { - "enable": true, - "post_to_url": "https://example.com", + "enable": true, + "post_to_url": "https://example.com", "threshold": 5 } - }, + }, "personalizations": [ { "bcc": [ { - "email": "test7@example.com", + "email": "test7@example.com", "name": "Example User7" - }, + }, { - "email": "test8@example.com", + "email": "test8@example.com", "name": "Example User8" - }, + }, { - "email": "test9@example.com", + "email": "test9@example.com", "name": "Example User9" } - ], + ], "cc": [ { - "email": "test4@example.com", + "email": "test4@example.com", "name": "Example User4" - }, + }, { - "email": "test5@example.com", + "email": "test5@example.com", "name": "Example User5" - }, + }, { - "email": "test6@example.com", + "email": "test6@example.com", "name": "Example User6" } - ], + ], "custom_args": { - "marketing1": "true", - "marketing2": "false", - "transactional1": "false", + "marketing1": "true", + "marketing2": "false", + "transactional1": "false", "transactional2": "true" - }, + }, "headers": { - "X-Test1": "Test1", - "X-Test2": "Test2", - "X-Test3": "Test3", + "X-Test1": "Test1", + "X-Test2": "Test2", + "X-Test3": "Test3", "X-Test4": "Test4" - }, - "send_at": 1461775051, - "subject": "Sending with SendGrid is Fun 0", + }, + "send_at": 1461775051, + "subject": "Sending with SendGrid is Fun 0", "substitutions": { - "%city1%": "Example City 1", - "%city2%": "Example City 2", - "%name1%": "Example Name 1", + "%city1%": "Example City 1", + "%city2%": "Example City 2", + "%name1%": "Example Name 1", "%name2%": "Example Name 2" - }, + }, "to": [ { - "email": "test1@example.com", + "email": "test1@example.com", "name": "Example User1" - }, + }, { - "email": "test2@example.com", + "email": "test2@example.com", "name": "Example User2" - }, + }, { - "email": "test3@example.com", + "email": "test3@example.com", "name": "Example User3" } ] - }, + }, { "bcc": [ { - "email": "test16@example.com", + "email": "test16@example.com", "name": "Example User16" - }, + }, { - "email": "test17@example.com", + "email": "test17@example.com", "name": "Example User17" - }, + }, { - "email": "test18@example.com", + "email": "test18@example.com", "name": "Example User18" } - ], + ], "cc": [ { - "email": "test13@example.com", + "email": "test13@example.com", "name": "Example User13" - }, + }, { - "email": "test14@example.com", + "email": "test14@example.com", "name": "Example User14" - }, + }, { - "email": "test15@example.com", + "email": "test15@example.com", "name": "Example User15" } - ], + ], "custom_args": { - "marketing3": "true", - "marketing4": "false", - "transactional3": "false", + "marketing3": "true", + "marketing4": "false", + "transactional3": "false", "transactional4": "true" - }, + }, "headers": { - "X-Test5": "Test5", - "X-Test6": "Test6", - "X-Test7": "Test7", + "X-Test5": "Test5", + "X-Test6": "Test6", + "X-Test7": "Test7", "X-Test8": "Test8" - }, - "send_at": 1461775052, - "subject": "Sending with SendGrid is Fun 1", + }, + "send_at": 1461775052, + "subject": "Sending with SendGrid is Fun 1", "substitutions": { - "%city3%": "Example City 3", - "%city4%": "Example City 4", - "%name3%": "Example Name 3", + "%city3%": "Example City 3", + "%city4%": "Example City 4", + "%name3%": "Example Name 3", "%name4%": "Example Name 4" - }, + }, "to": [ { - "email": "test10@example.com", + "email": "test10@example.com", "name": "Example User10" - }, + }, { - "email": "test11@example.com", + "email": "test11@example.com", "name": "Example User11" - }, + }, { - "email": "test12@example.com", + "email": "test12@example.com", "name": "Example User12" } ] } - ], + ], "reply_to": { - "email": "dx_reply@example.com", + "email": "dx_reply@example.com", "name": "DX Reply" - }, + }, "sections": { - "%section1%": "Substitution for Section 1 Tag", - "%section2%": "Substitution for Section 2 Tag", + "%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", + }, + "send_at": 1461775053, + "subject": "Sending with SendGrid is Fun 2", + "template_id": "13b8f94f-bcae-4ec6-b752-70d6cb59f932", "tracking_settings": { "click_tracking": { - "enable": true, + "enable": true, "enable_text": false - }, + }, "ganalytics": { - "enable": true, - "utm_campaign": "utm_campaign", - "utm_content": "utm_content", - "utm_medium": "utm_medium", - "utm_source": "utm_source", + "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, + "enable": true, "substitution_tag": "open_tracking" - }, + }, "subscription_tracking": { - "enable": true, - "html": "Goodbye!", - "substitution_tag": "unsubscribe", + "enable": true, + "html": "Goodbye!", + "substitution_tag": "unsubscribe", "text": "Goodbye" } } @@ -723,94 +753,98 @@ def test_kitchen_sink(self): # 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 + 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 = 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":[ + "total": "$ 239.85", + "items": [ { - "text":"New Line Sneakers", - "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/8dda1131320a6d978b515cc04ed479df259a458d5d45d58b6b381cae0bf9588113e80ef912f69e8c4cc1ef1a0297e8eefdb7b270064cc046b79a44e21b811802.png", + "price": "$ 79.95" }, { - "text":"Old 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/3629f54390ead663d4eb7c53702e492de63299d7c5f7239efdc693b09b9b28c82c924225dcd8dcb65732d5ca7b7b753c5f17e056405bbd4596e4e63a96ae5018.png", + "price": "$ 79.95" }, { - "text":"Blue Line Sneakers", - "image":"https://marketing-image-production.s3.amazonaws.com/uploads/00731ed18eff0ad5da890d876c456c3124a4e44cb48196533e9b95fb2b959b7194c2dc7637b788341d1ff4f88d1dc88e23f7e3704726d313c57f350911dd2bd0.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" + "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", + "type": "text/plain", "value": "and easy to do anywhere, even with Python" - }, + }, { - "type": "text/html", + "type": "text/html", "value": "and easy to do anywhere, even with Python" } - ], + ], "from": { - "email": "test+from@example.com", + "email": "test+from@example.com", "name": "Example From Name" - }, + }, "personalizations": [ { "dynamic_template_data": { - "address01": "1234 Fake St.", - "address02": "Apt. 123", - "city": "Place", + "address01": "1234 Fake St.", + "address02": "Apt. 123", + "city": "Place", "items": [ { - "image": "https://marketing-image-production.s3.amazonaws.com/uploads/8dda1131320a6d978b515cc04ed479df259a458d5d45d58b6b381cae0bf9588113e80ef912f69e8c4cc1ef1a0297e8eefdb7b270064cc046b79a44e21b811802.png", - "price": "$ 79.95", + "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", + "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", + "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", + ], + "name": "Sample Name", + "receipt": true, + "state": "CO", + "total": "$ 239.85", "zip": "80202" - }, + }, "to": [ { - "email": "test+to@example.com", + "email": "test+to@example.com", "name": "Example To Name" } ] } - ], + ], "subject": "Sending with SendGrid is Fun" }''') ) @@ -832,7 +866,10 @@ def test_sendgrid_api_key(self): # Try to include SendGrid API key try: - mail.add_content(Content("text/plain", "some SG.2123b1B.1212lBaC here")) + mail.add_content( + Content( + "text/plain", + "some SG.2123b1B.1212lBaC here")) mail.add_content( Content( "text/html", @@ -859,36 +896,40 @@ def test_sendgrid_api_key(self): 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 + 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 = 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", + "type": "text/plain", "value": "and easy to do anywhere, even with Python" - }, + }, { - "type": "text/html", + "type": "text/html", "value": "and easy to do anywhere, even with Python" } - ], + ], "from": { - "email": "test+from@example.com", + "email": "test+from@example.com", "name": "Example From Name" - }, + }, "personalizations": [ { "to": [ { - "email": "test+to@example.com", + "email": "test+to@example.com", "name": "Example To Name" } ] @@ -898,7 +939,7 @@ def test_unicode_values_in_substitutions_helper(self): "%city%": "Αθήνα" } } - ], + ], "subject": "Sending with SendGrid is Fun" }''') ) diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index d88234efb..f4687ffca 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -2303,7 +2303,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 index cf0aff767..79a51998b 100644 --- a/test/test_spam_check.py +++ b/test/test_spam_check.py @@ -30,7 +30,7 @@ def test_has_values_but_not_enabled(self): 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 From ae4b1fe307ebf35658500ce61657d858ff65e189 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 2 Apr 2019 14:41:17 -0700 Subject: [PATCH 190/462] Cutoff before merge into master --- README.rst | 1 + sendgrid/helpers/endpoints/ip/unassigned.py | 3 +- test/{test_mail.py => test_mail_helpers.py} | 73 +++++++++++++-------- test/test_sendgrid.py | 6 +- 4 files changed, 52 insertions(+), 31 deletions(-) rename test/{test_mail.py => test_mail_helpers.py} (95%) diff --git a/README.rst b/README.rst index ed78ea382..7a65eb9f3 100644 --- a/README.rst +++ b/README.rst @@ -6,6 +6,7 @@ **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.** diff --git a/sendgrid/helpers/endpoints/ip/unassigned.py b/sendgrid/helpers/endpoints/ip/unassigned.py index ba41e7b9c..816050d3d 100644 --- a/sendgrid/helpers/endpoints/ip/unassigned.py +++ b/sendgrid/helpers/endpoints/ip/unassigned.py @@ -41,7 +41,8 @@ def unassigned(data, as_json=False): response = sg.client.ips.get(query_params=params) if response.status_code == 201: data = response.body - unused = unassinged(data) """ + unused = unassigned(data) + """ no_subusers = set() diff --git a/test/test_mail.py b/test/test_mail_helpers.py similarity index 95% rename from test/test_mail.py rename to test/test_mail_helpers.py index 4a7e9e458..5b04d9037 100644 --- a/test/test_mail.py +++ b/test/test_mail_helpers.py @@ -10,40 +10,55 @@ 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 + 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, diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index f4687ffca..e1e12ab6d 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -1,4 +1,5 @@ import sendgrid +from sendgrid.helpers.endpoints.ip.unassigned import unassigned from sendgrid.helpers.mail import * import os import datetime @@ -859,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} From 792f9b6f1eab900066c3b08efc3b1ee3603bf1a3 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 2 Apr 2019 21:50:48 -0700 Subject: [PATCH 191/462] Version Bump v6.0.0: BREAKING CHANGE --- CHANGELOG.md | 74 ++++++++++++++++++++++++++++++++++++++------ docker/README.md | 3 +- sendgrid/VERSION.txt | 2 +- 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c37509e86..1280c2af2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,70 @@ # 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 +## [6.0.0] - 2019-04-02 ## + +### BREAKING CHANGE + +- The `Mail` helper signature has changed. +- Setting up a `SendGridAPIClient` has changed. + +Please see the [use cases documentation](https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/README.md) for implemenation details. + +This refactor was based on [this issue](https://github.com/sendgrid/sendgrid-python/issues/347). BIG thanks to all of those who [participated](https://github.com/sendgrid/sendgrid-python/issues/323) in shaping this release. + +In particular, BIG THANKS to: +[@yothinix](https://github.com/yothinix) +[@jeffoneill](https://github.com/jeffoneill) +[@elbuo8](https://github.com/elbuo8) +[@Jakobovski](https://github.com/Jakobovski) +[@andriisoldatenko](https://github.com/andriisoldatenko) +[@dibyadas](https://github.com/dibyadas) +[@belfazt](https://github.com/belfazt) +[@iandouglas](https://github.com/iandouglas) +[@mehronkugler](https://github.com/mehronkugler) + +### Fixed +- [PR #727](https://github.com/sendgrid/sendgrid-python/pull/727): Use raw-string notation for regex to avoid invalid escape sequence (BIG thanks to [@](https://github.com/)) +- [PR #715](https://github.com/sendgrid/sendgrid-python/pull/715): Correct attribution links formating (BIG thanks to [@hugovk](https://github.com/hugovk)) +- [PR #640](https://github.com/sendgrid/sendgrid-python/pull/640): Changes suggested by grammarly (BIG thanks to [@xeon-zolt](https://github.com/xeon-zolt)) +- [PR #697](https://github.com/sendgrid/sendgrid-python/pull/697): PEP8 Fixes and String Formatting Enhancement (BIG thanks to [@vkmrishad](https://github.com/vkmrishad)) +- [PR #647](https://github.com/sendgrid/sendgrid-python/pull/647): TROUBLESHOOTING.md broken link fix (BIG thanks to [@arshadkazmi42](https://github.com/arshadkazmi42)) +- [PR #638](https://github.com/sendgrid/sendgrid-python/pull/638): Fixed syntax errors in Kitchen sink Python example code (BIG thanks to [@vinayak42](https://github.com/vinayak42)) +- [PR #687](https://github.com/sendgrid/sendgrid-python/pull/687): Remove references to "Whitelabel" (BIG thanks to [@crweiner](https://github.com/crweiner)) +- [PR #690](https://github.com/sendgrid/sendgrid-python/pull/690): Corrected links in CoC (BIG thanks to [@bhavinjawade](https://github.com/bhavinjawade)) +- [PR #656](https://github.com/sendgrid/sendgrid-python/pull/656): Fix helper mail_example redirection link (BIG thanks to [@joshuadeguzman](https://github.com/joshuadeguzman)) +- [PR #636](https://github.com/sendgrid/sendgrid-python/pull/636): Fix broken link for mail example (BIG thanks to [@mattjegan](https://github.com/mattjegan)) +- [PR #630](https://github.com/sendgrid/sendgrid-python/pull/630): Update requirements.txt (BIG thanks to [@rahulkumaran](https://github.com/rahulkumaran)) +- [PR #628](https://github.com/sendgrid/sendgrid-python/pull/628): Update job description in README (BIG thanks to [@Jeremyyang920](https://github.com/Jeremyyang920)) +- [PR #618](https://github.com/sendgrid/sendgrid-python/pull/618): Quote names containing comma or semicolon (BIG thanks to [@cmccandless](https://github.com/cmccandless)) +- [PR #613](https://github.com/sendgrid/sendgrid-python/pull/613): Fix typos (BIG thanks to [@Bharat123rox](https://github.com/Bharat123rox)) +- [PR #616](https://github.com/sendgrid/sendgrid-python/pull/616): Fix typos (BIG thanks to [@hugovk](https://github.com/hugovk)) +- [PR #619](https://github.com/sendgrid/sendgrid-python/pull/619): Fix format of dependency pytest (BIG thanks to [@cmccandless](https://github.com/cmccandless)) +- [PR #611](https://github.com/sendgrid/sendgrid-python/pull/611): Fix broken link (BIG thanks to [@themousepotato](https://github.com/themousepotato)) +- [PR #488](https://github.com/sendgrid/sendgrid-python/pull/488): Fix similar code issue in mail.py helper (BIG thanks to [@adiman9](https://github.com/adiman9)) +- [PR #496](https://github.com/sendgrid/sendgrid-python/pull/496): Fix issues in sendgrid/helpers/mail/mail.py (BIG thanks to [@galihmelon](https://github.com/galihmelon)) +- [PR #510](https://github.com/sendgrid/sendgrid-python/pull/510): Fix similar code issue in sendgrid/helpers/mail/mail.py (BIG thanks to [@nanspro](https://github.com/nanspro)) +- [PR #524](https://github.com/sendgrid/sendgrid-python/pull/524): Fix master failure on travis (relating to ASM raise-assertion). (BIG thanks to [@extemporalgenome](https://github.com/extemporalgenome)) + +### Added +- [PR #666](https://github.com/sendgrid/sendgrid-python/pull/666): Created First-timers.md File (BIG thanks to [@jaykay12](https://github.com/jaykay12)) +- [PR #655](https://github.com/sendgrid/sendgrid-python/pull/655): Update USAGE.md (BIG thanks to [@ChatPion](https://github.com/ChatPion)) +- [PR #665](https://github.com/sendgrid/sendgrid-python/pull/665): Add use case for generation of Plain Text Content from HTML (BIG thanks to [@cmccandless](https://github.com/cmccandless)) +- [PR #718](https://github.com/sendgrid/sendgrid-python/pull/718): Update prerequisites (BIG thanks to [@Rishabh04-02](https://github.com/Rishabh04-02)) +- [PR #722](https://github.com/sendgrid/sendgrid-python/pull/722): Updated README.md (BIG thanks to [@rahulpuroht](https://github.com/rahulpuroht)) +- [PR #711](https://github.com/sendgrid/sendgrid-python/pull/711): Cleanup Dockerfiles (BIG thanks to [@rawkode](https://github.com/rawkode)) +- [PR #709](https://github.com/sendgrid/sendgrid-python/pull/709): Cleanup Env Documentation (BIG thanks to [@rawkode](https://github.com/rawkode)) +- [PR #631](https://github.com/sendgrid/sendgrid-python/pull/631): Allow creation of Mail from EmailMessage (BIG thanks to [@cmccandless](https://github.com/cmccandless)) +- [PR #683](https://github.com/sendgrid/sendgrid-python/pull/683): Create README.md for mail_example.py (BIG thanks to [@tulikavijay](https://github.com/tulikavijay)) +- [PR #663](https://github.com/sendgrid/sendgrid-python/pull/663): Converted README to reStructuredText and version as plain text file (BIG thanks to [@StrikerRUS](https://github.com/StrikerRUS)) +- [PR #643](https://github.com/sendgrid/sendgrid-python/pull/643): Add test to increase test coverage on config.py (BIG thanks to [@zkan](https://github.com/zkan)) +- [PR #692](https://github.com/sendgrid/sendgrid-python/pull/692): Add unit tests for spam check (BIG thanks to [@pyasi](https://github.com/pyasi)) +- [PR #637](https://github.com/sendgrid/sendgrid-python/pull/637): Add support for Python 3.7 (BIG thanks to [@hugovk](https://github.com/hugovk)) +- [PR #626](https://github.com/sendgrid/sendgrid-python/pull/626): Drop support for EOL Python 2.6 and 3.0-3.3 (BIG thanks to [@hugovk](https://github.com/hugovk)) +- [PR #486](https://github.com/sendgrid/sendgrid-python/pull/486): Refactor sengrid get method of Mail class (BIG thanks to [@Prashant-Surya](https://github.com/Prashant-Surya)) +- [PR #493](https://github.com/sendgrid/sendgrid-python/pull/493): Refactor personalization.py (BIG thanks to [@defaults](https://github.com/defaults)) +- [PR #509](https://github.com/sendgrid/sendgrid-python/pull/509): Refactor mail.py (BIG thanks to [@palash16](https://github.com/palash16)) +- [PR #512](https://github.com/sendgrid/sendgrid-python/pull/512): Refactor mail.py (BIG thanks to [@extemporalgenome](https://github.com/extemporalgenome)) ## [5.4.1] - 2018-06-26 ## ### Fixed diff --git a/docker/README.md b/docker/README.md index f7e04f73a..155f2e348 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,5 +1,6 @@ # Supported tags and respective `Dockerfile` links - - `v5.6.0`, `latest` [(Dockerfile)](https://github.com/sendgrid/sendgrid-python/blob/master/docker/Dockerfile) + - `v6.0.0`, `latest` [(Dockerfile)](https://github.com/sendgrid/sendgrid-python/blob/master/docker/Dockerfile) + - `v5.6.0` - `v5.5.0` - `v5.4.1` - `v5.4.0` diff --git a/sendgrid/VERSION.txt b/sendgrid/VERSION.txt index ade65226e..09b254e90 100644 --- a/sendgrid/VERSION.txt +++ b/sendgrid/VERSION.txt @@ -1 +1 @@ -5.4.1 +6.0.0 From eb2294c6b7519391f53687e437330bd1231ad85e Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 2 Apr 2019 22:09:27 -0700 Subject: [PATCH 192/462] Updating dependencies --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 426222575..3d4da1297 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ def getRequires(): - deps = ['python_http_client>=3.0'] + deps = ['python_http_client>=3.0','PyYAML>=5.1', 'six>=1.12.0', 'Werkzeug>=0.15.2'] return deps From ddee6c25313003792b5cc8ae4e8d2b2abcca7420 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 2 Apr 2019 22:14:44 -0700 Subject: [PATCH 193/462] Do not auto-install inbound parser --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 3d4da1297..80528bab4 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ def getRequires(): - deps = ['python_http_client>=3.0','PyYAML>=5.1', 'six>=1.12.0', 'Werkzeug>=0.15.2'] + deps = ['python_http_client>=3.0'] return deps @@ -22,7 +22,7 @@ def getRequires(): author='Elmer Thomas, Yamil Asusta', author_email='dx@sendgrid.com', url='https://github.com/sendgrid/sendgrid-python/', - packages=find_packages(exclude=["temp*.py", "test"]), + packages=find_packages(exclude=["temp*.py", "test", "inbound"]), include_package_data=True, license='MIT', description='SendGrid library for Python', From 3c8db45afa31d695c5978c46816440acfc22ed14 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 2 Apr 2019 22:15:49 -0700 Subject: [PATCH 194/462] Auto-deploy is broken --- .travis.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index cb4a7a0a6..872bd38d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,16 +37,16 @@ script: after_script: - codecov - ./cc-test-reporter after-build --exit-code $? -deploy: - provider: pypi - user: thinkingserious - password: - secure: DoM21KiMKkt/7AS6zOqTs7j3fgInrpswRTPG3cqBNRSzyfkXeXmKecCPruooxvYKLM7fPNDOuIH2phgCjdx/XBtJwghNh34n+TzhNFEiI/6pV0iS4a9gW0+QU+GMYvQmfNlA9DKQ5N20FMy4XeK8QQFarJXQwW1/a5wWftbUYvQ= - skip_cleanup: true - distributions: sdist bdist_wheel - on: - tags: true - python: '3.6' +# deploy: +# provider: pypi +# user: thinkingserious +# password: +# secure: DoM21KiMKkt/7AS6zOqTs7j3fgInrpswRTPG3cqBNRSzyfkXeXmKecCPruooxvYKLM7fPNDOuIH2phgCjdx/XBtJwghNh34n+TzhNFEiI/6pV0iS4a9gW0+QU+GMYvQmfNlA9DKQ5N20FMy4XeK8QQFarJXQwW1/a5wWftbUYvQ= +# skip_cleanup: true +# distributions: sdist bdist_wheel +# on: +# tags: true +# python: '3.6' notifications: hipchat: rooms: From b07bbfaeb9d43cca7f87a17829706af5e1297b3f Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 2 Apr 2019 22:25:45 -0700 Subject: [PATCH 195/462] Do not require inbound parser to be installed --- sendgrid/VERSION.txt | 2 +- sendgrid/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sendgrid/VERSION.txt b/sendgrid/VERSION.txt index 09b254e90..9b9a24420 100644 --- a/sendgrid/VERSION.txt +++ b/sendgrid/VERSION.txt @@ -1 +1 @@ -6.0.0 +6.0.2 diff --git a/sendgrid/__init__.py b/sendgrid/__init__.py index a40ac5ecc..22e4cdc81 100644 --- a/sendgrid/__init__.py +++ b/sendgrid/__init__.py @@ -19,7 +19,7 @@ from .sendgrid import SendGridAPIClient # noqa from .helpers.mail import * # noqa from .helpers.endpoints import * # noqa -from .helpers.inbound import * # noqa +# from .helpers.inbound import * # noqa from .helpers.stats import * # noqa dir_path = os.path.dirname(os.path.realpath(__file__)) diff --git a/setup.py b/setup.py index 80528bab4..426222575 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ def getRequires(): author='Elmer Thomas, Yamil Asusta', author_email='dx@sendgrid.com', url='https://github.com/sendgrid/sendgrid-python/', - packages=find_packages(exclude=["temp*.py", "test", "inbound"]), + packages=find_packages(exclude=["temp*.py", "test"]), include_package_data=True, license='MIT', description='SendGrid library for Python', From 1e05fa7d65c63e92ec26e4b8082501ff9b9c2ff6 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 4 Apr 2019 16:27:07 -0700 Subject: [PATCH 196/462] Useablity update. --- use_cases/transactional_templates.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/use_cases/transactional_templates.md b/use_cases/transactional_templates.md index f38c39655..2a237b946 100644 --- a/use_cases/transactional_templates.md +++ b/use_cases/transactional_templates.md @@ -1,6 +1,6 @@ # Transactional Templates -For this example, we assume you have created a [transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). Following is the template content we used for testing. +For this example, we assume you have created a [transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html) in the UI or via the API. Following is the template content we used for testing. Email Subject: From cfa5023025a5aa930b511304833fd56d54372843 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Fri, 5 Apr 2019 20:28:04 -0700 Subject: [PATCH 197/462] Twilio update --- .github/PULL_REQUEST_TEMPLATE | 1 + .gitignore | 2 + CHANGELOG.md | 7 + CODE_OF_CONDUCT.md | 20 +- CONTRIBUTING.md | 18 +- FIRST_TIMERS.md | 18 +- LICENSE.txt | 2 +- README.rst | 46 +++-- TROUBLESHOOTING.md | 16 +- USAGE.md | 188 +++++++++--------- app.json | 6 +- docker-test/Dockerfile | 2 +- docker-test/entrypoint.sh | 4 - docker/README.md | 13 +- examples/accesssettings/accesssettings.py | 2 +- examples/alerts/alerts.py | 2 +- examples/apikeys/apikeys.py | 2 +- examples/asm/asm.py | 2 +- examples/browsers/browsers.py | 2 +- examples/campaigns/campaigns.py | 2 +- examples/categories/categories.py | 2 +- examples/clients/clients.py | 2 +- examples/contactdb/contactdb.py | 2 +- examples/devices/devices.py | 2 +- examples/geo/geo.py | 2 +- examples/helpers/mail_example.py | 56 +----- examples/helpers/stats/stats_example.py | 2 +- examples/ips/ips.py | 2 +- examples/mail/mail.py | 2 +- examples/mailboxproviders/mailboxproviders.py | 2 +- examples/mailsettings/mailsettings.py | 2 +- examples/partnersettings/partnersettings.py | 2 +- examples/scopes/scopes.py | 2 +- .../senderauthentication.py | 2 +- examples/senders/senders.py | 2 +- examples/stats/stats.py | 2 +- examples/subusers/subusers.py | 2 +- examples/suppression/suppression.py | 2 +- examples/templates/templates.py | 2 +- examples/trackingsettings/trackingsettings.py | 2 +- examples/user/user.py | 2 +- proposals/mail-helper-refactor.md | 22 +- sendgrid/__init__.py | 4 +- .../inbound/sample_data/default_data.txt | 4 +- .../helpers/inbound/sample_data/raw_data.txt | 4 +- .../sample_data/raw_data_with_attachments.txt | 4 +- sendgrid/helpers/mail/README.md | 2 +- sendgrid/helpers/mail/exceptions.py | 8 +- sendgrid/helpers/mail/mail.py | 2 +- sendgrid/sendgrid.py | 18 +- setup.py | 2 +- test/test_sendgrid.py | 2 +- use_cases/README.md | 7 +- use_cases/asynchronous_mail_send.md | 2 +- use_cases/attachment.md | 2 +- use_cases/aws.md | 16 +- use_cases/django.md | 14 +- use_cases/domain_authentication.md | 2 +- use_cases/email_stats.md | 2 +- use_cases/error_handling.md | 2 +- use_cases/flask_heroku.md | 6 +- use_cases/kitchen_sink.md | 6 +- use_cases/legacy_templates.md | 4 +- ...nd_a_single_email_to_a_single_recipient.md | 2 +- ...d_a_single_email_to_multiple_recipients.md | 2 +- use_cases/slack_event_api_integration.md | 6 +- use_cases/sms.md | 58 ++++++ 67 files changed, 338 insertions(+), 316 deletions(-) create mode 100644 use_cases/sms.md diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE index 2444957f4..c3817318c 100644 --- a/.github/PULL_REQUEST_TEMPLATE +++ b/.github/PULL_REQUEST_TEMPLATE @@ -1,6 +1,7 @@ -# Fixes #X - -### Checklist -- [ ] I acknowledge that all my contributions will be made under the project's license -- [ ] I have made a material change to the repo (functionality, testing, spelling, grammar) -- [ ] I have read the [Contribution Guide] and my PR follows them -- [ ] I updated my branch with the master branch -- [ ] I have added tests that prove my fix is effective or that my feature works -- [ ] I have added necessary documentation about the functionality in the appropriate .md file -- [ ] I have added in line documentation to the code I modified - -### Short description of what this PR does: -- - -If you have questions, please send an email to [Twilio SendGrid](mailto:dx@sendgrid.com), or file a GitHub Issue in this repository. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 52e42a1e1..2f0727ed5 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,38 +1,73 @@ -# Twilio SendGrid Community Code of Conduct - - The Twilio 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. +# Contributor Covenant Code of Conduct - ### Be Considerate - Members of the community are considerate of their peers, which include other contributors and users of Twilio SendGrid. We're thoughtful when addressing the efforts of others, keeping in mind that often the labor was completed with the intent of the good of the community. We're attentive in our communications, whether in person or online, and we're tactful when approaching differing views. +## Our Pledge - ### Be Respectful - Members of the community are respectful. We're respectful of others, their positions, their skills, their commitments, and their efforts. We're respectful of the volunteer efforts that permeate the Twilio SendGrid community. We're respectful of the processes outlined in the community, and we work within them. When we disagree, we are courteous in raising our issues. Overall, we're good with each other. We contribute to this community not because we have to, but because we want to. If we remember that, these guidelines will come naturally. +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. - ## Additional Guidance +## Our Standards - ### Disclose Potential Conflicts of Interest - Community discussions often involve interested parties. We expect participants to be aware when they are conflicted due to employment or other projects they are involved in and disclose those interests to other project members. When in doubt, over-disclose. Perceived conflicts of interest are important to address so that the community’s decisions are credible even when unpopular, difficult or favorable to the interests of one group over another. +Examples of behavior that contributes to creating a positive environment +include: - ### 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 [Twilio 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 Twilio 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. +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members - ## If you have concerns about someone’s conduct - **Initiate Direct Contact** - It is always appropriate to email a community member (if contact information is available), mention that you think their behavior was out of line, and (if necessary) point them to this Code. +Examples of unacceptable behavior by participants include: - **Discuss Publicly** - Discussing publicly is always acceptable. Note, though, that approaching the person directly may be better, as it tends to make them less defensive, and it respects the time of other community members, so you probably want to try direct contact first. +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting - **Contact the Moderators** - You can reach the Twilio SendGrid moderators by emailing dx@sendgrid.com. - - ## Attribution +## Our Responsibilities - Twilio 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) +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at open-source@twilio.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5f9444912..1c931fab1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,7 +55,7 @@ Before you decide to create a new issue, please try the following: ### Please use our Bug Report Template -In order to make the process easier, we've included a [sample bug report template]((https://github.com/sendgrid/sendgrid-python/.github/ISSUE_TEMPLATE)) (borrowed from [Ghost](https://github.com/TryGhost/Ghost/)). The template uses [GitHub flavored markdown](https://help.github.com/articles/github-flavored-markdown/) for formatting. +In order to make the process easier, we've included a [sample bug report template]((https://github.com/sendgrid/sendgrid-python/ISSUE_TEMPLATE.md)) (borrowed from [Ghost](https://github.com/TryGhost/Ghost/)). The template uses [GitHub flavored markdown](https://help.github.com/articles/github-flavored-markdown/) for formatting. ## Improvements to the Codebase diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..a4b1cc240 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,26 @@ + + +### Issue Summary +A summary of the issue and the environment in which it occurs. If suitable, include the steps required to reproduce the bug. Please feel free to include screenshots, screencasts, or code examples. + +### Steps to Reproduce +1. This is the first step +2. This is the second step +3. Further steps, etc. + +### Code Snippet +```python +# paste code here +``` + +### Exception/Log +``` +# paste exception/log here +``` + +### Technical details: +* sendgrid-python version: +* python version: + diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..29aba592a --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (C) 2020, Twilio SendGrid, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 56fd3ede7..000000000 --- a/LICENSE.txt +++ /dev/null @@ -1,17 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2012-2020 Twilio SendGrid, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of -the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in index 594e724b7..f2ec0f7aa 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include README.rst -include LICENSE.txt +include LICENSE.md include app.json include Procfile include requirements.txt diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..215059a96 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,31 @@ + + +# Fixes # + +A short description of what this PR does. + +### Checklist +- [ ] I acknowledge that all my contributions will be made under the project's license +- [ ] I have made a material change to the repo (functionality, testing, spelling, grammar) +- [ ] I have read the [Contribution Guidelines](CONTRIBUTING.md) and my PR follows them +- [ ] I have titled the PR appropriately +- [ ] I have updated my branch with the master branch +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] I have added necessary documentation about the functionality in the appropriate .md file +- [ ] I have added inline documentation to the code I modified + +If you have questions, please file a [support ticket](https://twilio.com/help/contact), or create a GitHub Issue in this repository. diff --git a/README.rst b/README.rst index 01e5df688..7d78db946 100644 --- a/README.rst +++ b/README.rst @@ -303,7 +303,7 @@ License .. _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 +.. _The MIT License (MIT): https://github.com/sendgrid/sendgrid-python/blob/master/LICENSE.md .. |Travis Badge| image:: https://travis-ci.org/sendgrid/sendgrid-python.svg?branch=master :target: https://travis-ci.org/sendgrid/sendgrid-python @@ -318,7 +318,7 @@ License .. |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 + :target: ./LICENSE.md .. |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 diff --git a/test/test_project.py b/test/test_project.py index 680898f04..27d0befb9 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -40,17 +40,17 @@ def test_code_of_conduct(self): def test_contributing(self): self.assertTrue(os.path.isfile('./CONTRIBUTING.md')) - # ./.github/ISSUE_TEMPLATE + # ./ISSUE_TEMPLATE.md def test_issue_template(self): - self.assertTrue(os.path.isfile('./.github/ISSUE_TEMPLATE')) + self.assertTrue(os.path.isfile('./ISSUE_TEMPLATE.md')) # ./LICENSE.md def test_license(self): - self.assertTrue(os.path.isfile('./LICENSE.txt')) + self.assertTrue(os.path.isfile('./LICENSE.md')) - # ./.github/PULL_REQUEST_TEMPLATE + # ./PULL_REQUEST_TEMPLATE.md def test_pr_template(self): - self.assertTrue(os.path.isfile('./.github/PULL_REQUEST_TEMPLATE')) + self.assertTrue(os.path.isfile('./PULL_REQUEST_TEMPLATE.md')) # ./README.rst def test_readme(self): diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index 15063e088..29f40924e 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -2304,7 +2304,7 @@ def test_whitelabel_links__link_id__subuser_post(self): self.assertEqual(response.status_code, 200) def test_license_year(self): - LICENSE_FILE = 'LICENSE.txt' + LICENSE_FILE = 'LICENSE.md' copyright_line='' with open(LICENSE_FILE, 'r') as f: for line in f: @@ -2312,7 +2312,7 @@ def test_license_year(self): copyright_line = line.strip() break self.assertEqual( - 'Copyright (c) 2012-%s Twilio SendGrid, Inc.' % datetime.datetime.now().year, + 'Copyright (C) %s, Twilio SendGrid, Inc. ' % datetime.datetime.now().year, copyright_line) # @classmethod From 9e94def93cc60aea6d9a592c382aa65fcde0625f Mon Sep 17 00:00:00 2001 From: Twilio Date: Fri, 24 Jan 2020 07:26:14 +0000 Subject: [PATCH 235/462] [Librarian] Version Bump --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77ef75032..088b20f2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-01-24] Version 6.1.1 +-------------------------- +**Library - Docs** +- [PR #865](https://github.com/sendgrid/sendgrid-python/pull/865): baseline all the templated markdown docs. Thanks to [@childish-sambino](https://github.com/childish-sambino)! + +**Library - Chore** +- [PR #853](https://github.com/sendgrid/sendgrid-python/pull/853): clean up imports. Thanks to [@DucarrougeR](https://github.com/DucarrougeR)! +- [PR #862](https://github.com/sendgrid/sendgrid-python/pull/862): prep the repo for automated releasing. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + +**Library - Fix** +- [PR #863](https://github.com/sendgrid/sendgrid-python/pull/863): improve make test command. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + + [2019-09-12] Version 6.1.0 --------------------------- From 71c6802f66df62e32f0305bb1ebf0a60f6716e52 Mon Sep 17 00:00:00 2001 From: Twilio Date: Fri, 24 Jan 2020 07:28:19 +0000 Subject: [PATCH 236/462] Release 6.1.1 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 9f62d7687..cf37bf506 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.1.0' +__version__ = '6.1.1' From 076f3767c15ebba1f28de3c4c249248d9fa25bce Mon Sep 17 00:00:00 2001 From: Phawin Khongkhasawan Date: Wed, 19 Feb 2020 20:47:35 +0700 Subject: [PATCH 237/462] Convert integer substitution value to string (#838) --- sendgrid/helpers/mail/substitution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/helpers/mail/substitution.py b/sendgrid/helpers/mail/substitution.py index d0e59837e..d515e885b 100644 --- a/sendgrid/helpers/mail/substitution.py +++ b/sendgrid/helpers/mail/substitution.py @@ -48,7 +48,7 @@ def value(self): :rtype value: string """ - return self._value + return str(self._value) if isinstance(self._value, int) else self._value @value.setter def value(self, value): From fdc3b69e55e778bf39a5a6056cf5a785805eb836 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 19 Feb 2020 23:47:58 +0000 Subject: [PATCH 238/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 088b20f2b..17c66d8a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-02-19] Version 6.1.2 +-------------------------- +**Library - Fix** +- [PR #838](https://github.com/sendgrid/sendgrid-python/pull/838): Convert integer substitution value to string. Thanks to [@lifez](https://github.com/lifez)! + + [2020-01-24] Version 6.1.1 -------------------------- **Library - Docs** From 06b9a0ce113018d95a11778bece81a9420d07561 Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 20 Feb 2020 00:06:41 +0000 Subject: [PATCH 239/462] Release 6.1.2 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index cf37bf506..3bdb88685 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.1.1' +__version__ = '6.1.2' From 5f251e868ca932fd28f8900b2f600ae0c9e6a42d Mon Sep 17 00:00:00 2001 From: childish-sambino Date: Fri, 21 Feb 2020 11:29:45 -0600 Subject: [PATCH 240/462] chore: add Python 3.8 to Travis (#870) --- .travis.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6d42ff9ea..a2a133692 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,12 @@ +dist: xenial # required for Python >= 3.7 language: python -sudo: false cache: pip python: - '2.7' - '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 + - '3.7' + - '3.8' env: global: - CC_TEST_REPORTER_ID=$TRAVIS_CODE_CLIMATE_TOKEN SENDGRID_API_KEY=SGAPIKEY From 83e25d3c366da6c12fcdaef2b5e46b38d89a7228 Mon Sep 17 00:00:00 2001 From: Aman Mehta Date: Wed, 26 Feb 2020 20:48:17 +0530 Subject: [PATCH 241/462] chore: Clean up sendgrid.py (#844) --- sendgrid/sendgrid.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index d2450522a..bc1454d28 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -1,6 +1,5 @@ """ -This library allows you to quickly and easily use the Twilio SendGrid Web API v3 via -Python. +This library allows you to quickly and easily use the Twilio SendGrid Web API v3 via Python. For more information on this library, see the README on GitHub. http://github.com/sendgrid/sendgrid-python @@ -12,9 +11,7 @@ This file provides the Twilio SendGrid API Client. """ - import os -import warnings import python_http_client From b6cca1c054c3bf77d4fbfd9cd8570c555c73c0bf Mon Sep 17 00:00:00 2001 From: Elise Shanholtz Date: Wed, 26 Feb 2020 11:48:59 -0800 Subject: [PATCH 242/462] fix: add config.yml file to pypi distribution files (#872) --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index f2ec0f7aa..cd3c5f680 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,5 +3,6 @@ include LICENSE.md include app.json include Procfile include requirements.txt +include sendgrid/helpers/inbound/config.yml recursive-include sendgrid *.py *.txt recursive-exclude test * From 9aa75b1e69c4bf2b63964eb5797a22d3949b9dc3 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 4 Mar 2020 21:20:19 +0000 Subject: [PATCH 243/462] [Librarian] Version Bump --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17c66d8a2..a5311e421 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-03-04] Version 6.1.3 +-------------------------- +**Library - Chore** +- [PR #844](https://github.com/sendgrid/sendgrid-python/pull/844): Clean up sendgrid.py. Thanks to [@Aman-am](https://github.com/Aman-am)! +- [PR #870](https://github.com/sendgrid/sendgrid-python/pull/870): add Python 3.8 to Travis. Thanks to [@childish-sambino](https://github.com/childish-sambino)! + +**Library - Fix** +- [PR #872](https://github.com/sendgrid/sendgrid-python/pull/872): add config.yml file to pypi distribution files. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + + [2020-02-19] Version 6.1.2 -------------------------- **Library - Fix** From 267b3477f86cd995c094bdd7d95bc83f72af2b1b Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 4 Mar 2020 21:57:31 +0000 Subject: [PATCH 244/462] Release 6.1.3 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 3bdb88685..00957ae80 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.1.2' +__version__ = '6.1.3' From 1abbf5859bd1f7116cc807575c862f7bf907986e Mon Sep 17 00:00:00 2001 From: Elise Shanholtz Date: Thu, 5 Mar 2020 10:18:01 -0800 Subject: [PATCH 245/462] fix: travis autodeploy and Release 6.1.3 --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index a2a133692..21ecfff65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,9 +26,8 @@ after_script: - ./cc-test-reporter after-build --exit-code $? deploy: provider: pypi - user: __token__ - password: - secure: $PYPI_TOKEN + user: "__token__" + password: $PYPI_TOKEN skip_cleanup: true distributions: sdist bdist_wheel on: From 0bc6ad89c39159ecdbd2be32298fe45792ae6995 Mon Sep 17 00:00:00 2001 From: gy741 Date: Sat, 7 Mar 2020 04:11:31 +0900 Subject: [PATCH 246/462] chore: Remove unused Python json modules (#731) --- examples/accesssettings/accesssettings.py | 1 - examples/alerts/alerts.py | 1 - examples/apikeys/apikeys.py | 1 - examples/asm/asm.py | 1 - examples/browsers/browsers.py | 1 - examples/campaigns/campaigns.py | 1 - examples/categories/categories.py | 1 - examples/clients/clients.py | 1 - examples/contactdb/contactdb.py | 1 - examples/devices/devices.py | 1 - examples/geo/geo.py | 1 - examples/ips/ips.py | 1 - examples/mail/mail.py | 1 - examples/mailboxproviders/mailboxproviders.py | 1 - examples/mailsettings/mailsettings.py | 1 - examples/partnersettings/partnersettings.py | 1 - examples/scopes/scopes.py | 1 - examples/senders/senders.py | 1 - examples/stats/stats.py | 1 - examples/subusers/subusers.py | 1 - examples/suppression/suppression.py | 1 - examples/templates/templates.py | 1 - examples/trackingsettings/trackingsettings.py | 1 - examples/user/user.py | 1 - 24 files changed, 24 deletions(-) diff --git a/examples/accesssettings/accesssettings.py b/examples/accesssettings/accesssettings.py index 1c8995299..17d115f9b 100644 --- a/examples/accesssettings/accesssettings.py +++ b/examples/accesssettings/accesssettings.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/alerts/alerts.py b/examples/alerts/alerts.py index bbc75e69c..cdfe008fd 100644 --- a/examples/alerts/alerts.py +++ b/examples/alerts/alerts.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/apikeys/apikeys.py b/examples/apikeys/apikeys.py index cfb92aba8..3e612cb15 100644 --- a/examples/apikeys/apikeys.py +++ b/examples/apikeys/apikeys.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/asm/asm.py b/examples/asm/asm.py index d7b9a94d2..1b081b851 100644 --- a/examples/asm/asm.py +++ b/examples/asm/asm.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/browsers/browsers.py b/examples/browsers/browsers.py index f88493416..43152260a 100644 --- a/examples/browsers/browsers.py +++ b/examples/browsers/browsers.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/campaigns/campaigns.py b/examples/campaigns/campaigns.py index 59ade9794..dbbb6c0e1 100644 --- a/examples/campaigns/campaigns.py +++ b/examples/campaigns/campaigns.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/categories/categories.py b/examples/categories/categories.py index 9f2b98486..b8275713f 100644 --- a/examples/categories/categories.py +++ b/examples/categories/categories.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/clients/clients.py b/examples/clients/clients.py index 847c8df37..023a440db 100644 --- a/examples/clients/clients.py +++ b/examples/clients/clients.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/contactdb/contactdb.py b/examples/contactdb/contactdb.py index 76fcada3c..b702974df 100644 --- a/examples/contactdb/contactdb.py +++ b/examples/contactdb/contactdb.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/devices/devices.py b/examples/devices/devices.py index fe9fcd28e..50c96243d 100644 --- a/examples/devices/devices.py +++ b/examples/devices/devices.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/geo/geo.py b/examples/geo/geo.py index ed2ea8cb1..64265b201 100644 --- a/examples/geo/geo.py +++ b/examples/geo/geo.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/ips/ips.py b/examples/ips/ips.py index f3cfec282..316d0c858 100644 --- a/examples/ips/ips.py +++ b/examples/ips/ips.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/mail/mail.py b/examples/mail/mail.py index 9584edc59..d59901d1d 100644 --- a/examples/mail/mail.py +++ b/examples/mail/mail.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/mailboxproviders/mailboxproviders.py b/examples/mailboxproviders/mailboxproviders.py index dcd3d1e1d..4fbf470e2 100644 --- a/examples/mailboxproviders/mailboxproviders.py +++ b/examples/mailboxproviders/mailboxproviders.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/mailsettings/mailsettings.py b/examples/mailsettings/mailsettings.py index 47ce6e1db..a4d46e399 100644 --- a/examples/mailsettings/mailsettings.py +++ b/examples/mailsettings/mailsettings.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/partnersettings/partnersettings.py b/examples/partnersettings/partnersettings.py index 4eef00b12..d3675a6ba 100644 --- a/examples/partnersettings/partnersettings.py +++ b/examples/partnersettings/partnersettings.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/scopes/scopes.py b/examples/scopes/scopes.py index 369b9a519..99519dc3e 100644 --- a/examples/scopes/scopes.py +++ b/examples/scopes/scopes.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/senders/senders.py b/examples/senders/senders.py index e8aeb2ea3..55eb44631 100644 --- a/examples/senders/senders.py +++ b/examples/senders/senders.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/stats/stats.py b/examples/stats/stats.py index 56cc9fadb..cde422447 100644 --- a/examples/stats/stats.py +++ b/examples/stats/stats.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/subusers/subusers.py b/examples/subusers/subusers.py index f6a28f9d6..0f5ba6fe0 100644 --- a/examples/subusers/subusers.py +++ b/examples/subusers/subusers.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/suppression/suppression.py b/examples/suppression/suppression.py index e235f8f92..430f76f35 100644 --- a/examples/suppression/suppression.py +++ b/examples/suppression/suppression.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/templates/templates.py b/examples/templates/templates.py index c5293b9c2..9b5210191 100644 --- a/examples/templates/templates.py +++ b/examples/templates/templates.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/trackingsettings/trackingsettings.py b/examples/trackingsettings/trackingsettings.py index cfde91e96..b3c49f8b2 100644 --- a/examples/trackingsettings/trackingsettings.py +++ b/examples/trackingsettings/trackingsettings.py @@ -1,5 +1,4 @@ import sendgrid -import json import os diff --git a/examples/user/user.py b/examples/user/user.py index bca521d41..5160f9ff8 100644 --- a/examples/user/user.py +++ b/examples/user/user.py @@ -1,5 +1,4 @@ import sendgrid -import json import os From 499c5f1e1271555f98e91ce96dfad92d46586567 Mon Sep 17 00:00:00 2001 From: Vinayak <32881538+vinayak42@users.noreply.github.com> Date: Sat, 7 Mar 2020 00:53:39 +0530 Subject: [PATCH 247/462] docs: Fix grammatical errors (#706) --- examples/helpers/stats/stats_example.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/helpers/stats/stats_example.py b/examples/helpers/stats/stats_example.py index ba8c94a1a..ebe24f69f 100644 --- a/examples/helpers/stats/stats_example.py +++ b/examples/helpers/stats/stats_example.py @@ -3,10 +3,10 @@ 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. +# NOTE: you will need to 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 +# See: https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) From a8c51126c871950c237bae69c852471ef37a55af Mon Sep 17 00:00:00 2001 From: pktrieu Date: Fri, 6 Mar 2020 11:35:18 -0800 Subject: [PATCH 248/462] docs: Fixed links in examples (#669) --- sendgrid/helpers/mail/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/helpers/mail/README.md b/sendgrid/helpers/mail/README.md index 92569275b..29e21dea5 100644 --- a/sendgrid/helpers/mail/README.md +++ b/sendgrid/helpers/mail/README.md @@ -7,4 +7,4 @@ Please complete the [installation steps](https://github.com/sendgrid/sendgrid-py ## Usage - 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) +- The complete v3 API Documentation can be found [here](https://sendgrid.com/docs/API_Reference/api_v3.html) From ea90f7c40fd28c473dcfef78cd043bd049afcd9f Mon Sep 17 00:00:00 2001 From: Rounak Agarwal Date: Sat, 7 Mar 2020 01:46:49 +0530 Subject: [PATCH 249/462] docs: Give preference to 'to' after visible (#714) --- USAGE.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/USAGE.md b/USAGE.md index 6f79e7f96..343cb8f90 100644 --- a/USAGE.md +++ b/USAGE.md @@ -438,7 +438,7 @@ print(response.headers) Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. -The **name** and **description** of the unsubscribe group will be visible by recipients when they are managing their subscriptions. +The **name** and **description** of the unsubscribe group will be visible to recipients when they are managing their subscriptions. Each user can create up to 25 different suppression groups. @@ -482,7 +482,7 @@ print(response.headers) Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. -The **name** and **description** of the unsubscribe group will be visible by recipients when they are managing their subscriptions. +The **name** and **description** of the unsubscribe group will be visible to recipients when they are managing their subscriptions. Each user can create up to 25 different suppression groups. @@ -507,7 +507,7 @@ print(response.headers) Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. -The **name** and **description** of the unsubscribe group will be visible by recipients when they are managing their subscriptions. +The **name** and **description** of the unsubscribe group will be visible to recipients when they are managing their subscriptions. Each user can create up to 25 different suppression groups. @@ -529,7 +529,7 @@ You can only delete groups that have not been attached to sent mail in the last Suppression groups, or unsubscribe groups, are specific types or categories of emails that you would like your recipients to be able to unsubscribe from. For example: Daily Newsletters, Invoices, System Alerts. -The **name** and **description** of the unsubscribe group will be visible by recipients when they are managing their subscriptions. +The **name** and **description** of the unsubscribe group will be visible to recipients when they are managing their subscriptions. Each user can create up to 25 different suppression groups. From b2ca1f4b9dfa32ad7ff829c52d0a5efd9128a2f3 Mon Sep 17 00:00:00 2001 From: mcintyre94 Date: Fri, 6 Mar 2020 20:28:42 +0000 Subject: [PATCH 250/462] feat: Add equality to Email (#739) Email objects are defined as equal iff they have the same email and the same name. This is useful for unit tests where we want to verify that the Email objects generated are as expected. --- sendgrid/helpers/mail/email.py | 21 +++++++++++++++ test/test_email.py | 48 ++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/sendgrid/helpers/mail/email.py b/sendgrid/helpers/mail/email.py index 3c2cb3bbb..10a8718b1 100644 --- a/sendgrid/helpers/mail/email.py +++ b/sendgrid/helpers/mail/email.py @@ -73,6 +73,27 @@ def __init__(self, if p is not None: self.personalization = p + def __eq__(self, other): + """Email objects are equal if they have the same email and the same name + + :param other: object to compare with + :type other: any + """ + if isinstance(other, Email): + return self.email == other.email and self.name == other.name + return NotImplemented + + def __ne__(self, other): + """Inverse of __eq__ (required for Python 2) + + :param other: object to compare with + :type other: any + """ + x = self.__eq__(other) + if x is not NotImplemented: + return not x + return NotImplemented + @property def name(self): """Name associated with this email. diff --git a/test/test_email.py b/test/test_email.py index eb50374aa..c8614fe17 100644 --- a/test/test_email.py +++ b/test/test_email.py @@ -80,3 +80,51 @@ def test_add_unicode_name_with_comma(self): email.name = name self.assertEqual(email.name, u'"' + name + u'"') + + def test_equality_email_name(self): + address = "test@example.com" + name = "SomeName" + email1 = Email(address, name) + email2 = Email(address, name) + + self.assertEqual(email1, email2) + + def test_equality_email(self): + address = "test@example.com" + email1 = Email(address) + email2 = Email(address) + + self.assertEqual(email1, email2) + + def test_equality_name(self): + name = "SomeName" + email1 = Email() + email1.name = name + email2 = Email() + email2.name = name + + self.assertEqual(email1, email2) + + def test_equality_different_emails(self): + address1 = "test1@example.com" + email1 = Email(address1) + address2 = "test2@example.com" + email2 = Email(address2) + + self.assertNotEqual(email1, email2) + + def test_equality_different_name(self): + name1 = "SomeName1" + email1 = Email() + email1.name = name1 + name2 = "SomeName2" + email2 = Email() + email2.name = name2 + + self.assertNotEqual(email1, email2) + + def test_equality_non_email(self): + address = "test@example.com" + email = Email(address) + + self.assertNotEqual(email, address) From c114681c003bac6334c59e284122b13c09ae5b54 Mon Sep 17 00:00:00 2001 From: Chandler Weiner Date: Fri, 6 Mar 2020 15:41:36 -0500 Subject: [PATCH 251/462] docs: Remove more references to 'whitelabel' (#734) --- USAGE.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/USAGE.md b/USAGE.md index 343cb8f90..560daea71 100644 --- a/USAGE.md +++ b/USAGE.md @@ -1822,7 +1822,7 @@ print(response.headers) IP Pools allow you to group your dedicated Twilio SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. -IP pools can only be used with whitelabeled IP addresses. +IP pools can only be used with authenticated IP addresses. If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. @@ -1844,7 +1844,7 @@ print(response.headers) IP Pools allow you to group your dedicated SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. -IP pools can only be used with whitelabeled IP addresses. +IP pools can only be used with authenticated IP addresses. If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. @@ -1863,7 +1863,7 @@ print(response.headers) IP Pools allow you to group your dedicated Twilio SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. -IP pools can only be used with whitelabeled IP addresses. +IP pools can only be used with authenticated IP addresses. If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. @@ -1886,7 +1886,7 @@ print(response.headers) IP Pools allow you to group your dedicated Twilio SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. -IP pools can only be used with whitelabeled IP addresses. +IP pools can only be used with authenticated IP addresses. If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. @@ -1906,7 +1906,7 @@ print(response.headers) IP Pools allow you to group your dedicated Twilio SendGrid IP addresses together. For example, you could create separate pools for your transactional and marketing email. When sending marketing emails, specify that you want to use the marketing IP pool. This allows you to maintain separate reputations for your different email traffic. -IP pools can only be used with whitelabeled IP addresses. +IP pools can only be used with authenticated IP addresses. If an IP pool is NOT specified for an email, it will use any IP available, including ones in pools. @@ -2728,7 +2728,7 @@ print(response.headers) *You may create up to 100 unique sender identities.* -Sender Identities are required to be verified before use. If your domain has been whitelabeled it will auto verify on creation. Otherwise, an email will be sent to the `from.email`. +Sender Identities are required to be verified before use. If your domain has been authenticated it will auto verify on creation. Otherwise, an email will be sent to the `from.email`. ### POST /senders @@ -2760,7 +2760,7 @@ print(response.headers) **This endpoint allows you to retrieve a list of all sender identities that have been created for your account.** -Sender Identities are required to be verified before use. If your domain has been whitelabeled it will auto verify on creation. Otherwise, an email will be sent to the `from.email`. +Sender Identities are required to be verified before use. If your domain has been authenticated it will auto verify on creation. Otherwise an email will be sent to the `from.email`. ### GET /senders @@ -2775,7 +2775,7 @@ print(response.headers) **This endpoint allows you to update a sender identity.** -Updates to `from.email` require re-verification. If your domain has been whitelabeled it will auto verify on creation. Otherwise, an email will be sent to the `from.email`. +Updates to `from.email` require re-verification. If your domain has been authenticated it will auto verify on creation. Otherwise an email will be sent to the `from.email`. Partial updates are allowed, but fields that are marked as "required" in the POST (create) endpoint must not be nil if that field is included in the PATCH request. @@ -2810,7 +2810,7 @@ print(response.headers) **This endpoint allows you to retrieve a specific sender identity.** -Sender Identities are required to be verified before use. If your domain has been whitelabeled it will auto verify on creation. Otherwise, an email will be sent to the `from.email`. +Sender Identities are required to be verified before use. If your domain has been authenticated it will auto verify on creation. Otherwise an email will be sent to the `from.email`. ### GET /senders/{sender_id} @@ -2826,7 +2826,7 @@ print(response.headers) **This endpoint allows you to delete one of your sender identities.** -Sender Identities are required to be verified before use. If your domain has been whitelabeled it will auto verify on creation. Otherwise, an email will be sent to the `from.email`. +Sender Identities are required to be verified before use. If your domain has been authenticated it will auto verify on creation. Otherwise an email will be sent to the `from.email`. ### DELETE /senders/{sender_id} @@ -2842,7 +2842,7 @@ print(response.headers) **This endpoint allows you to resend a sender identity verification email.** -Sender Identities are required to be verified before use. If your domain has been whitelabeled it will auto verify on creation. Otherwise, an email will be sent to the `from.email`. +Sender Identities are required to be verified before use. If your domain has been authenticated it will auto verify on creation. Otherwise an email will be sent to the `from.email`. ### POST /senders/{sender_id}/resend_verification From a81abdd370ddfcba02d1a5f24fa73ceca29810dc Mon Sep 17 00:00:00 2001 From: childish-sambino Date: Fri, 6 Mar 2020 15:04:40 -0600 Subject: [PATCH 252/462] Update send_a_single_email_to_a_single_recipient.md --- use_cases/send_a_single_email_to_a_single_recipient.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index cfd7f532b..8a2364285 100644 --- a/use_cases/send_a_single_email_to_a_single_recipient.md +++ b/use_cases/send_a_single_email_to_a_single_recipient.md @@ -15,5 +15,5 @@ try: print(response.body) print(response.headers) except Exception as e: - print(e.message) -``` \ No newline at end of file + print(e) +``` From 8a53dc99bd7d854bd8c96503cc8de8dfee339081 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Wed, 11 Mar 2020 15:15:48 -0500 Subject: [PATCH 253/462] docs: correct the API key param name --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e6901f233..ff8bd877f 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ import sendgrid import os from sendgrid.helpers.mail import * -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) from_email = Email("test@example.com") to_email = Email("test@example.com") subject = "Sending with SendGrid is Fun" @@ -115,7 +115,7 @@ The following is the minimum needed code to send an email without the /mail/send import sendgrid import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) data = { "personalizations": [ { @@ -149,7 +149,7 @@ print(response.headers) import sendgrid import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) response = sg.client.suppression.bounces.get() print(response.status_code) print(response.body) @@ -162,7 +162,7 @@ print(response.headers) import sendgrid import os -sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) +sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) response = sg.client._("suppression/bounces").get() print(response.status_code) print(response.body) From 197b61807190b5cbc799ef852eb781192ac2c5cd Mon Sep 17 00:00:00 2001 From: Neeraj Date: Fri, 13 Mar 2020 21:33:38 +0530 Subject: [PATCH 254/462] docs: fix code snippet in README (#878) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ff8bd877f..65078707f 100644 --- a/README.md +++ b/README.md @@ -95,10 +95,10 @@ from sendgrid.helpers.mail import * sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) from_email = Email("test@example.com") -to_email = Email("test@example.com") +to_email = To("test@example.com") subject = "Sending with SendGrid is Fun" content = Content("text/plain", "and easy to do anywhere, even with Python") -mail = Mail(from_email, subject, to_email, content) +mail = Mail(from_email, to_email, subject, content) response = sg.client.mail.send.post(request_body=mail.get()) print(response.status_code) print(response.body) From 63f4370b2f3016129bd15cb540e157f945f95a82 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 18 Mar 2020 19:00:28 +0000 Subject: [PATCH 255/462] [Librarian] Version Bump --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5311e421..a749f4e5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-03-18] Version 6.2.0 +-------------------------- +**Library - Docs** +- [PR #878](https://github.com/sendgrid/sendgrid-python/pull/878): fix code snippet in README. Thanks to [@neerajgupta2407](https://github.com/neerajgupta2407)! +- [PR #734](https://github.com/sendgrid/sendgrid-python/pull/734): Further Remove "Whitelabel" References. Thanks to [@crweiner](https://github.com/crweiner)! +- [PR #714](https://github.com/sendgrid/sendgrid-python/pull/714): Give preference to 'to' after visible. Thanks to [@agarwalrounak](https://github.com/agarwalrounak)! +- [PR #669](https://github.com/sendgrid/sendgrid-python/pull/669): Fixed links in examples. Thanks to [@pktrieu](https://github.com/pktrieu)! +- [PR #706](https://github.com/sendgrid/sendgrid-python/pull/706): Fix grammatical errors. Thanks to [@vinayak42](https://github.com/vinayak42)! +- [PR #682](https://github.com/sendgrid/sendgrid-python/pull/682): Updated link to direct to #L9. Thanks to [@vinayak42](https://github.com/vinayak42)! + +**Library - Feature** +- [PR #739](https://github.com/sendgrid/sendgrid-python/pull/739): Add equality to Email. Thanks to [@mcintyre94](https://github.com/mcintyre94)! + +**Library - Chore** +- [PR #731](https://github.com/sendgrid/sendgrid-python/pull/731): Remove unused Python json modules. Thanks to [@gy741](https://github.com/gy741)! + + [2020-03-04] Version 6.1.3 -------------------------- **Library - Chore** From 6a2eced0f441d500b24e2f3b98b93c3e88015255 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 18 Mar 2020 19:32:27 +0000 Subject: [PATCH 256/462] Release 6.2.0 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 00957ae80..e7c2779ad 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.1.3' +__version__ = '6.2.0' From 40dcb1ed66860d671d001d57e326308a252b67f7 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 31 Mar 2020 10:08:31 -0500 Subject: [PATCH 257/462] docs: support verbiage for login issues (#880) Added support verbiage for login issues due to several recent github issues being opened. --- TROUBLESHOOTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index a5640d10a..f3a64ec3c 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -1,3 +1,5 @@ +If you have an issue logging into your Twilio SendGrid account, please read this [document](https://sendgrid.com/docs/ui/account-and-settings/troubleshooting-login/). For any questions regarding login issues, please contact our [support team](https://support.sendgrid.com). + If you have a non-library Twilio SendGrid issue, please contact our [support team](https://support.sendgrid.com). If you can't find a solution below, please open an [issue](https://github.com/sendgrid/sendgrid-python/issues). From fcfe96dd3e749c6cdb13ff8d30d5b2bb834f9e87 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 1 Apr 2020 19:04:50 +0000 Subject: [PATCH 258/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a749f4e5a..eada85ccd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-04-01] Version 6.2.1 +-------------------------- +**Library - Docs** +- [PR #880](https://github.com/sendgrid/sendgrid-python/pull/880): support verbiage for login issues. Thanks to [@adamchasetaylor](https://github.com/adamchasetaylor)! + + [2020-03-18] Version 6.2.0 -------------------------- **Library - Docs** From f5a0dc788489fac1f70d9bb7d74bb4c21b729b8a Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 1 Apr 2020 19:40:17 +0000 Subject: [PATCH 259/462] Release 6.2.1 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index e7c2779ad..b133e0c3f 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.2.0' +__version__ = '6.2.1' From 1ff5de6b6ae2ab25a20bb37dd0321d90da9578a0 Mon Sep 17 00:00:00 2001 From: childish-sambino Date: Fri, 3 Apr 2020 13:49:32 -0500 Subject: [PATCH 260/462] fix: correct the User-Agent casing (#881) --- sendgrid/sendgrid.py | 192 +++++++++++++++++++++--------------------- test/test_sendgrid.py | 4 +- 2 files changed, 98 insertions(+), 98 deletions(-) diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index bc1454d28..648f8e1cc 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -1,96 +1,96 @@ -""" -This library allows you to quickly and easily use the Twilio SendGrid Web API v3 via Python. - -For more information on this library, see the README on GitHub. - http://github.com/sendgrid/sendgrid-python -For more information on the Twilio SendGrid v3 API, see the v3 docs: - http://sendgrid.com/docs/API_Reference/api_v3.html -For the user guide, code examples, and more, visit the main docs page: - http://sendgrid.com/docs/index.html - -This file provides the Twilio SendGrid API Client. -""" - -import os - -import python_http_client - - -class SendGridAPIClient(object): - """The Twilio SendGrid API Client. - - Use this object to interact with the v3 API. For example: - 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()) - - For examples and detailed use instructions, see - https://github.com/sendgrid/sendgrid-python - """ - - def __init__( - self, - api_key=None, - host='https://api.sendgrid.com', - impersonate_subuser=None): - """ - Construct the Twilio SendGrid v3 API object. - Note that the underlying client is being set up during initialization, - therefore changing attributes in runtime will not affect HTTP client - behaviour. - - :param api_key: Twilio SendGrid API key to use. If not provided, 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: string - """ - from . import __version__ - self.api_key = api_key or os.environ.get('SENDGRID_API_KEY') - self.impersonate_subuser = impersonate_subuser - self.host = host - 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) - - @property - def _default_headers(self): - """Set the default header for a Twilio SendGrid v3 API call""" - headers = { - "Authorization": 'Bearer {}'.format(self.api_key), - "User-agent": self.useragent, - "Accept": 'application/json' - } - if self.impersonate_subuser: - headers['On-Behalf-Of'] = self.impersonate_subuser - - return headers - - def reset_request_headers(self): - - self.client.request_headers = self._default_headers - - def send(self, message): - """Make a Twilio SendGrid v3 API request with the request body generated by - the Mail object - - :param message: The Twilio SendGrid v3 API request body generated by the Mail - object - :type message: Mail - """ - if 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 +""" +This library allows you to quickly and easily use the Twilio SendGrid Web API v3 via Python. + +For more information on this library, see the README on GitHub. + http://github.com/sendgrid/sendgrid-python +For more information on the Twilio SendGrid v3 API, see the v3 docs: + http://sendgrid.com/docs/API_Reference/api_v3.html +For the user guide, code examples, and more, visit the main docs page: + http://sendgrid.com/docs/index.html + +This file provides the Twilio SendGrid API Client. +""" + +import os + +import python_http_client + + +class SendGridAPIClient(object): + """The Twilio SendGrid API Client. + + Use this object to interact with the v3 API. For example: + 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()) + + For examples and detailed use instructions, see + https://github.com/sendgrid/sendgrid-python + """ + + def __init__( + self, + api_key=None, + host='https://api.sendgrid.com', + impersonate_subuser=None): + """ + Construct the Twilio SendGrid v3 API object. + Note that the underlying client is being set up during initialization, + therefore changing attributes in runtime will not affect HTTP client + behaviour. + + :param api_key: Twilio SendGrid API key to use. If not provided, 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: string + """ + from . import __version__ + self.api_key = api_key or os.environ.get('SENDGRID_API_KEY') + self.impersonate_subuser = impersonate_subuser + self.host = host + 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) + + @property + def _default_headers(self): + """Set the default header for a Twilio SendGrid v3 API call""" + headers = { + "Authorization": 'Bearer {}'.format(self.api_key), + "User-Agent": self.useragent, + "Accept": 'application/json' + } + if self.impersonate_subuser: + headers['On-Behalf-Of'] = self.impersonate_subuser + + return headers + + def reset_request_headers(self): + + self.client.request_headers = self._default_headers + + def send(self, message): + """Make a Twilio SendGrid v3 API request with the request body generated by + the Mail object + + :param message: The Twilio SendGrid v3 API request body generated by the Mail + object + :type message: Mail + """ + if 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/test/test_sendgrid.py b/test/test_sendgrid.py index 29f40924e..d158e402e 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -51,14 +51,14 @@ def test_host(self): def test_get_default_headers(self): headers = self.sg._default_headers self.assertIn('Authorization', headers) - self.assertIn('User-agent', headers) + self.assertIn('User-Agent', headers) self.assertIn('Accept', headers) self.assertNotIn('On-Behalf-Of', headers) self.sg.impersonate_subuser = 'ladida@testsubuser.sendgrid' headers = self.sg._default_headers self.assertIn('Authorization', headers) - self.assertIn('User-agent', headers) + self.assertIn('User-Agent', headers) self.assertIn('Accept', headers) self.assertIn('On-Behalf-Of', headers) From c32a93fee85c563cbe0e14b39a8128c26ff5a4e9 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Fri, 3 Apr 2020 15:33:32 -0500 Subject: [PATCH 261/462] chore: update Travis CI Slack notifications --- .travis.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.travis.yml b/.travis.yml index 21ecfff65..5d36e954f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,3 +33,11 @@ deploy: on: tags: true python: '3.6' + +notifications: + slack: + if: branch = master + on_success: never + on_failure: change + rooms: + - secure: Yp7gJ6NPRPNgO77vwS0HynSdnU5LYlLlUNBEzcx+zy230UxuLLWcYZtIqsIqt4oZm45OwgJLBwoCMgmU2Jcj79rGyqWKYtUcLMLKgHVzSgxjm2outt2fxjXIJHIU60S3RCGofBJRkPwEMb7ibgwHYBEsH3wIeLrVVbWvimxka6A= From 6dee5432337a6f5756d2e9d29dfaec98c8fae3ef Mon Sep 17 00:00:00 2001 From: childish-sambino Date: Mon, 6 Apr 2020 11:08:45 -0500 Subject: [PATCH 262/462] Update .travis.yml --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 5d36e954f..8101380a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,6 +37,7 @@ deploy: notifications: slack: if: branch = master + on_pull_requests: false on_success: never on_failure: change rooms: From b10d759b675da0b57ecc835adb22d8d64c2c31cb Mon Sep 17 00:00:00 2001 From: childish-sambino Date: Wed, 8 Apr 2020 15:13:37 -0500 Subject: [PATCH 263/462] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 65078707f..5ce732f3a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![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) +[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE.md) [![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) @@ -232,4 +232,4 @@ sendgrid-python is maintained and funded by SendGrid, Inc. The names and logos f # License -[The MIT License (MIT)](LICENSE.txt) +[The MIT License (MIT)](LICENSE.md) From 8ce1acf3a9587c6c1ac6992f2afc5facd86146c0 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Wed, 8 Apr 2020 16:19:24 -0500 Subject: [PATCH 264/462] docs: cleanup support/help/contact information --- CONTRIBUTING.md | 2 -- FIRST_TIMERS.md | 8 ++---- README.md | 8 ++---- README.rst | 9 ------ docker/README.md | 6 ---- docker/USAGE.md | 7 ----- examples/helpers/mail_example.py | 26 ++++++++--------- live_test.py | 48 ++++++++++++++++---------------- setup.py | 2 +- test/test_mail_helpers.py | 16 +++++------ use_cases/error_handling.md | 6 ++-- use_cases/kitchen_sink.md | 6 ++-- 12 files changed, 58 insertions(+), 86 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1c931fab1..63d07dc75 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -239,8 +239,6 @@ Please run your code through: 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) with a clear title and description against the `master` branch. All tests must be passing before we will review the PR. -If you have any additional questions, please feel free to [email](mailto:dx@sendgrid.com) us or create an issue in this repo. - ## 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/). diff --git a/FIRST_TIMERS.md b/FIRST_TIMERS.md index 990544b59..714d94bc1 100644 --- a/FIRST_TIMERS.md +++ b/FIRST_TIMERS.md @@ -65,11 +65,9 @@ Step 2: Get familiar with Twilio SendGrid Service **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. + Kindly be patient & follow the suggestions as provided by the peer reviewers. Make required amendments & 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 +## [Explore New Issues to work upon](https://github.com/sendgrid/sendgrid-python/labels/difficulty%3A%20easy) diff --git a/README.md b/README.md index 5ce732f3a..277a1d0c6 100644 --- a/README.md +++ b/README.md @@ -192,8 +192,6 @@ Please see [our helper](https://github.com/sendgrid/sendgrid-python/tree/master/ # Announcements -Join an experienced and passionate team that focuses on making an impact. [Opportunities abound](https://sendgrid.com/careers) to grow the product - and grow your career! - 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. @@ -224,11 +222,11 @@ Please see our [troubleshooting guide](https://github.com/sendgrid/sendgrid-pyth # About -sendgrid-python is guided and supported by the SendGrid Developer Experience Team. +sendgrid-python is maintained and funded by Twilio SendGrid, Inc. The names and logos for sendgrid-python are trademarks of Twilio SendGrid, Inc. -Email the Developer Experience Team [here](mailto:dx@sendgrid.com) in case of any queries. +If you need help installing or using the library, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com). -sendgrid-python is maintained and funded by SendGrid, Inc. The names and logos for sendgrid-python are trademarks of SendGrid, Inc. +If you've instead found a bug in the library or would like new features added, go ahead and open issues or pull requests against this repo! # License diff --git a/README.rst b/README.rst index 7d78db946..60be5d2af 100644 --- a/README.rst +++ b/README.rst @@ -221,9 +221,6 @@ Use Cases Announcements ============= -Join an experienced and passionate team that focuses on making an impact. -`Opportunities abound`_ to grow the product - and grow your career! - 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. @@ -255,10 +252,6 @@ Please see our `troubleshooting guide`_ for common library issues. About ===== -**sendgrid-python** is guided and supported by the Twilio Developer Experience Team. - -Email the Developer Experience Team `here `__ in case of any queries. - **sendgrid-python** is maintained and funded by Twilio SendGrid, Inc. The names and logos for **sendgrid-python** are trademarks of Twilio SendGrid, Inc. @@ -290,7 +283,6 @@ License .. _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 @@ -302,7 +294,6 @@ License .. _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.md .. |Travis Badge| image:: https://travis-ci.org/sendgrid/sendgrid-python.svg?branch=master diff --git a/docker/README.md b/docker/README.md index 0467daf15..d1a187d31 100644 --- a/docker/README.md +++ b/docker/README.md @@ -60,10 +60,4 @@ For more detailed information, see [USAGE.md](https://github.com/sendgrid/sendgrid-python/blob/master/docker/USAGE.md). -# About - -sendgrid-python is guided and supported by the Twilio SendGrid [Developer Experience Team](mailto:dx@sendgrid.com). - -sendgrid-python is maintained and funded by Twilio SendGrid, Inc. The names and logos for sendgrid-python are trademarks of Twilio SendGrid, Inc. - ![Twilio SendGrid Logo](https://sendgrid.com/brand/sg-twilio/SG_Twilio_Lockup_RGBx1.png) diff --git a/docker/USAGE.md b/docker/USAGE.md index 0d3f90b90..77a106c9b 100644 --- a/docker/USAGE.md +++ b/docker/USAGE.md @@ -131,11 +131,4 @@ $ docker-compose up -d sendgrid-beta # Testing Testing is easy! Run the container, `cd sendgrid`, and run `tox`. - -# About - -sendgrid-python is guided and supported by the SendGrid [Developer Experience Team](mailto:dx@sendgrid.com). - -sendgrid-python is maintained and funded by SendGrid, Inc. The names and logos for sendgrid-python are trademarks of SendGrid, Inc. - ![SendGrid Logo](https://uiux.s3.amazonaws.com/2016-logos/email-logo%402x.png) diff --git a/examples/helpers/mail_example.py b/examples/helpers/mail_example.py index 040c990d4..c57520a9e 100644 --- a/examples/helpers/mail_example.py +++ b/examples/helpers/mail_example.py @@ -105,8 +105,8 @@ def build_attachment2(): def build_kitchen_sink(): """All settings set""" from sendgrid.helpers.mail import ( - Mail, From, To, Cc, Bcc, Subject, PlainTextContent, - HtmlContent, SendGridException, Substitution, + 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, @@ -123,22 +123,22 @@ def build_kitchen_sink(): message = Mail() - # Define Personalizations + # Define Personalizations message.to = To('test1@sendgrid.com', 'Example User1', p=0) - message.to = [ + 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 = [ + 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 = [ + message.bcc = [ Bcc('test8@example.com', 'Example User8', p=0), Bcc('test9@example.com', 'Example User9', p=0) ] @@ -169,19 +169,19 @@ def build_kitchen_sink(): message.send_at = SendAt(1461775051, p=0) message.to = To('test10@example.com', 'Example User10', p=1) - message.to = [ + 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 = [ + 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 = [ + message.bcc = [ Bcc('test17@example.com', 'Example User17', p=1), Bcc('test18@example.com', 'Example User18', p=1) ] @@ -213,9 +213,9 @@ def build_kitchen_sink(): # The values below this comment are global to entire message - message.from_email = From('dx@sendgrid.com', 'DX') + message.from_email = From('help@twilio.com', 'Twilio SendGrid') - message.reply_to = ReplyTo('dx_reply@sendgrid.com', 'DX Reply') + message.reply_to = ReplyTo('help_reply@twilio.com', 'Twilio SendGrid Reply') message.subject = Subject('Sending with SendGrid is Fun 2') @@ -249,7 +249,7 @@ def build_kitchen_sink(): 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') + Section('%section3%', 'Substitution for Section 3 Tag') ] message.header = Header('X-Test9', 'Test9') @@ -293,7 +293,7 @@ def build_kitchen_sink(): tracking_settings.click_tracking = ClickTracking(True, False) tracking_settings.open_tracking = OpenTracking(True, OpenTrackingSubstitutionTag("open_tracking")) tracking_settings.subscription_tracking = SubscriptionTracking( - True, + True, SubscriptionText("Goodbye"), SubscriptionHtml("Goodbye!"), SubscriptionSubstitutionTag("unsubscribe")) diff --git a/live_test.py b/live_test.py index 12c52eeed..d666140fb 100644 --- a/live_test.py +++ b/live_test.py @@ -4,8 +4,8 @@ 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'), +message = Mail(from_email=From('help@twilio.com', 'Twilio SendGrid'), + to_emails=To('ethomas@twilio.com', 'Elmer Thomas'), subject=Subject('Sending with SendGrid is Fun'), plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), html_content=HtmlContent('and easy to do anywhere, even with Python')) @@ -27,10 +27,10 @@ from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException to_emails = [ - To('elmer.thomas@sendgrid.com', 'Elmer SendGrid'), + To('ethomas@twilio.com', 'Elmer SendGrid'), To('elmer.thomas@gmail.com', 'Elmer Thomas') ] -message = Mail(from_email=From('dx@sendgrid.com', 'DX'), +message = Mail(from_email=From('help@twilio.com', 'Twilio SendGrid'), to_emails=to_emails, subject=Subject('Sending with SendGrid is Fun'), plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), @@ -56,10 +56,10 @@ import datetime to_emails = [ - To(email='elmer.thomas@sendgrid.com', - name='Elmer SendGrid', + To(email='ethomas@twilio.com', + name='Elmer Twilio', substitutions={ - Substitution('-name-', 'Elmer SendGrid'), + Substitution('-name-', 'Elmer Twilio'), Substitution('-github-', 'http://github.com/ethomas'), }, subject=Subject('Override Global Subject')), @@ -72,7 +72,7 @@ ] 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'), +message = Mail(from_email=From('help@twilio.com', 'Twilio SendGrid'), to_emails=to_emails, subject=Subject('Hi -name-'), plain_text_content=PlainTextContent('Hello -name-, your github is -github-, email sent at -time-'), @@ -96,8 +96,8 @@ import json from sendgrid import SendGridAPIClient from sendgrid.helpers.mail import ( - Mail, From, To, Cc, Bcc, Subject, PlainTextContent, - HtmlContent, SendGridException, Substitution, + 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, @@ -114,22 +114,22 @@ message = Mail() -# Define Personalizations +# Define Personalizations message.to = To('elmer+test1@sendgrid.com', 'Example User1', p=0) -message.to = [ +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 = [ +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 = [ +message.bcc = [ Bcc('test8@example.com', 'Example User8', p=0), Bcc('test9@example.com', 'Example User9', p=0) ] @@ -160,19 +160,19 @@ message.send_at = SendAt(1461775051, p=0) message.to = To('test10@example.com', 'Example User10', p=1) -message.to = [ +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 = [ +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 = [ +message.bcc = [ Bcc('test17@example.com', 'Example User17', p=1), Bcc('test18@example.com', 'Example User18', p=1) ] @@ -204,9 +204,9 @@ # The values below this comment are global to entire message -message.from_email = From('dx@sendgrid.com', 'DX') +message.from_email = From('help@twilio.com', 'Twilio SendGrid') -message.reply_to = ReplyTo('dx_reply@sendgrid.com', 'DX Reply') +message.reply_to = ReplyTo('help_reply@twilio.com', 'Twilio SendGrid Reply') message.subject = Subject('Sending with SendGrid is Fun 2') @@ -240,7 +240,7 @@ 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') + Section('%section3%', 'Substitution for Section 3 Tag') ] message.header = Header('X-Test9', 'Test9') @@ -284,7 +284,7 @@ tracking_settings.click_tracking = ClickTracking(True, False) tracking_settings.open_tracking = OpenTracking(True, OpenTrackingSubstitutionTag("open_tracking")) tracking_settings.subscription_tracking = SubscriptionTracking( - True, + True, SubscriptionText("Goodbye"), SubscriptionHtml("Goodbye!"), SubscriptionSubstitutionTag("unsubscribe")) @@ -313,8 +313,8 @@ 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'), +message = Mail(from_email=From('help@twilio.com', 'Twilio SendGrid'), + to_emails=To('ethomas@twilio.com', 'Elmer Thomas'), subject=Subject('Sending with SendGrid is Fun'), plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), html_content=HtmlContent('and easy to do anywhere, even with Python')) @@ -354,4 +354,4 @@ print(response.body) print(response.headers) except SendGridException as e: - print(e.message) \ No newline at end of file + print(e.message) diff --git a/setup.py b/setup.py index a2b57099f..1c6c5a8c4 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ def getRequires(): name='sendgrid', version=str(__version__), author='Elmer Thomas, Yamil Asusta', - author_email='dx@sendgrid.com', + author_email='help@twilio.com', url='https://github.com/sendgrid/sendgrid-python/', packages=find_packages(exclude=["temp*.py", "test"]), include_package_data=True, diff --git a/test/test_mail_helpers.py b/test/test_mail_helpers.py index 5b04d9037..b29d8b8c2 100644 --- a/test/test_mail_helpers.py +++ b/test/test_mail_helpers.py @@ -29,7 +29,7 @@ def test_asm(self): 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) @@ -48,7 +48,7 @@ def test_attachment(self): '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_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()) @@ -393,9 +393,9 @@ def test_kitchen_sink(self): # The values below this comment are global to entire message - message.from_email = From('dx@example.com', 'DX') + message.from_email = From('help@twilio.com', 'Twilio SendGrid') - message.reply_to = ReplyTo('dx_reply@example.com', 'DX Reply') + message.reply_to = ReplyTo('help_reply@twilio.com', 'Twilio SendGrid Reply') message.subject = Subject('Sending with SendGrid is Fun 2') @@ -565,8 +565,8 @@ def test_kitchen_sink(self): "transactional6": "false" }, "from": { - "email": "dx@example.com", - "name": "DX" + "email": "help@twilio.com", + "name": "Twilio SendGrid" }, "headers": { "X-Test10": "Test10", @@ -728,8 +728,8 @@ def test_kitchen_sink(self): } ], "reply_to": { - "email": "dx_reply@example.com", - "name": "DX Reply" + "email": "help_reply@twilio.com", + "name": "Twilio SendGrid Reply" }, "sections": { "%section1%": "Substitution for Section 1 Tag", diff --git a/use_cases/error_handling.md b/use_cases/error_handling.md index 5ef2e2dac..eaeabad18 100644 --- a/use_cases/error_handling.md +++ b/use_cases/error_handling.md @@ -12,8 +12,8 @@ There are also email specific exceptions located [here](https://github.com/sendg from python_http_client import exceptions sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) - from_email = From("dx@sendgrid.com") - to_email = To("elmer.thomas@sendgrid.com") + from_email = From("help@twilio.com") + to_email = To("ethomas@twilio.com") subject = Subject("Sending with Twilio SendGrid is Fun") plain_text_content = PlainTextContent("and easy to do anywhere, even with Python") html_content = HtmlContent("and easy to do anywhere, even with Python") @@ -26,4 +26,4 @@ There are also email specific exceptions located [here](https://github.com/sendg except exceptions.BadRequestsError as e: print(e.body) exit() -``` \ No newline at end of file +``` diff --git a/use_cases/kitchen_sink.md b/use_cases/kitchen_sink.md index 92b033588..68c9e057e 100644 --- a/use_cases/kitchen_sink.md +++ b/use_cases/kitchen_sink.md @@ -107,9 +107,9 @@ message.subject = Subject('Sending with Twilio SendGrid is Fun 1', p=1) # The values below this comment are global to entire message -message.from_email = From('dx@example.com', 'DX') +message.from_email = From('help@twilio.com', 'Twilio SendGrid') -message.reply_to = ReplyTo('dx_reply@example.com', 'DX Reply') +message.reply_to = ReplyTo('help_reply@twilio.com', 'Twilio SendGrid Reply') message.subject = Subject('Sending with Twilio SendGrid is Fun 2') @@ -223,4 +223,4 @@ try: print(response.headers) except Exception as e: print(e.message) -``` \ No newline at end of file +``` From 1b56eeebdf12ec356f21392059c53fda02b002f9 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 15 Apr 2020 19:16:07 +0000 Subject: [PATCH 265/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eada85ccd..6c45a1872 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-04-15] Version 6.2.2 +-------------------------- +**Library - Fix** +- [PR #881](https://github.com/sendgrid/sendgrid-python/pull/881): correct the User-Agent casing. Thanks to [@childish-sambino](https://github.com/childish-sambino)! + + [2020-04-01] Version 6.2.1 -------------------------- **Library - Docs** From 83b28b882690d296c1d6b4eb979119d5012abbab Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 15 Apr 2020 19:32:50 +0000 Subject: [PATCH 266/462] Release 6.2.2 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index b133e0c3f..ac1df1f77 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.2.1' +__version__ = '6.2.2' From 1adc410499cfbfbc7a4ec3c062c114d55cb66539 Mon Sep 17 00:00:00 2001 From: childish-sambino Date: Tue, 21 Apr 2020 12:38:28 -0500 Subject: [PATCH 267/462] feat: add support for Twilio Email (#882) --- sendgrid/__init__.py | 8 ++--- sendgrid/base_interface.py | 62 ++++++++++++++++++++++++++++++++ sendgrid/sendgrid.py | 56 +++++------------------------ sendgrid/twilio_email.py | 73 ++++++++++++++++++++++++++++++++++++++ test/test_sendgrid.py | 18 +++------- test/test_twilio_email.py | 37 +++++++++++++++++++ use_cases/README.md | 6 ++-- use_cases/sms.md | 50 +++++--------------------- use_cases/twilio-email.md | 16 +++++++++ use_cases/twilio-setup.md | 54 ++++++++++++++++++++++++++++ 10 files changed, 273 insertions(+), 107 deletions(-) create mode 100644 sendgrid/base_interface.py create mode 100644 sendgrid/twilio_email.py create mode 100644 test/test_twilio_email.py create mode 100644 use_cases/twilio-email.md create mode 100644 use_cases/twilio-setup.md diff --git a/sendgrid/__init__.py b/sendgrid/__init__.py index 8923a4484..eb6d58961 100644 --- a/sendgrid/__init__.py +++ b/sendgrid/__init__.py @@ -15,9 +15,9 @@ Modules to help with common tasks. """ -from .version import __version__ -from .sendgrid import SendGridAPIClient # noqa -from .helpers.mail import * # noqa from .helpers.endpoints import * # noqa -# from .helpers.inbound import * # noqa +from .helpers.mail import * # noqa from .helpers.stats import * # noqa +from .sendgrid import SendGridAPIClient # noqa +from .twilio_email import TwilioEmailAPIClient # noqa +from .version import __version__ diff --git a/sendgrid/base_interface.py b/sendgrid/base_interface.py new file mode 100644 index 000000000..92b38247e --- /dev/null +++ b/sendgrid/base_interface.py @@ -0,0 +1,62 @@ +import python_http_client + + +class BaseInterface(object): + def __init__(self, auth, host, impersonate_subuser): + """ + Construct the Twilio SendGrid v3 API object. + Note that the underlying client is being set up during initialization, + therefore changing attributes in runtime will not affect HTTP client + behaviour. + + :param auth: the authorization header + :type auth: string + :param impersonate_subuser: the subuser to impersonate. Will be passed + by "On-Behalf-Of" header by underlying + client. See + https://sendgrid.com/docs/User_Guide/Settings/subusers.html + for more details + :type impersonate_subuser: string + :param host: base URL for API calls + :type host: string + """ + from . import __version__ + self.auth = auth + self.host = host + self.impersonate_subuser = impersonate_subuser + 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) + + @property + def _default_headers(self): + """Set the default header for a Twilio SendGrid v3 API call""" + headers = { + "Authorization": self.auth, + "User-Agent": self.useragent, + "Accept": 'application/json' + } + if self.impersonate_subuser: + headers['On-Behalf-Of'] = self.impersonate_subuser + + return headers + + def reset_request_headers(self): + self.client.request_headers = self._default_headers + + def send(self, message): + """Make a Twilio SendGrid v3 API request with the request body generated by + the Mail object + + :param message: The Twilio SendGrid v3 API request body generated by the Mail + object + :type message: Mail + """ + if not isinstance(message, dict): + message = message.get() + + return self.client.mail.send.post(request_body=message) diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index 648f8e1cc..912d8336e 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -13,17 +13,17 @@ import os -import python_http_client +from .base_interface import BaseInterface -class SendGridAPIClient(object): +class SendGridAPIClient(BaseInterface): """The Twilio SendGrid API Client. - Use this object to interact with the v3 API. For example: - sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + Use this object to interact with the v3 API. For example: + mail_client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ... mail = Mail(from_email, subject, to_email, content) - response = sg.client.mail.send.post(request_body=mail.get()) + response = mail_client.send(mail) For examples and detailed use instructions, see https://github.com/sendgrid/sendgrid-python @@ -40,8 +40,8 @@ def __init__( therefore changing attributes in runtime will not affect HTTP client behaviour. - :param api_key: Twilio SendGrid API key to use. If not provided, key will be - read from environment variable "SENDGRID_API_KEY" + :param api_key: Twilio SendGrid API key to use. If not provided, value + will be read from environment variable "SENDGRID_API_KEY" :type api_key: string :param impersonate_subuser: the subuser to impersonate. Will be passed by "On-Behalf-Of" header by underlying @@ -52,45 +52,7 @@ def __init__( :param host: base URL for API calls :type host: string """ - from . import __version__ self.api_key = api_key or os.environ.get('SENDGRID_API_KEY') - self.impersonate_subuser = impersonate_subuser - self.host = host - self.version = __version__ - self.useragent = 'sendgrid/{};python'.format(self.version) + auth = 'Bearer {}'.format(self.api_key) - self.client = python_http_client.Client( - host=self.host, - request_headers=self._default_headers, - version=3) - - @property - def _default_headers(self): - """Set the default header for a Twilio SendGrid v3 API call""" - headers = { - "Authorization": 'Bearer {}'.format(self.api_key), - "User-Agent": self.useragent, - "Accept": 'application/json' - } - if self.impersonate_subuser: - headers['On-Behalf-Of'] = self.impersonate_subuser - - return headers - - def reset_request_headers(self): - - self.client.request_headers = self._default_headers - - def send(self, message): - """Make a Twilio SendGrid v3 API request with the request body generated by - the Mail object - - :param message: The Twilio SendGrid v3 API request body generated by the Mail - object - :type message: Mail - """ - if 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 + super(SendGridAPIClient, self).__init__(auth, host, impersonate_subuser) diff --git a/sendgrid/twilio_email.py b/sendgrid/twilio_email.py new file mode 100644 index 000000000..78bd01815 --- /dev/null +++ b/sendgrid/twilio_email.py @@ -0,0 +1,73 @@ +""" +This library allows you to quickly and easily use the Twilio Email Web API v3 via Python. + +For more information on this library, see the README on GitHub. + http://github.com/sendgrid/sendgrid-python +For more information on the Twilio SendGrid v3 API, see the v3 docs: + http://sendgrid.com/docs/API_Reference/api_v3.html +For the user guide, code examples, and more, visit the main docs page: + http://sendgrid.com/docs/index.html + +This file provides the Twilio Email API Client. +""" +import os +from base64 import b64encode + +from .base_interface import BaseInterface + + +class TwilioEmailAPIClient(BaseInterface): + """The Twilio Email API Client. + + Use this object to interact with the v3 API. For example: + mail_client = sendgrid.TwilioEmailAPIClient(os.environ.get('TWILIO_API_KEY'), + os.environ.get('TWILIO_API_SECRET')) + ... + mail = Mail(from_email, subject, to_email, content) + response = mail_client.send(mail) + + For examples and detailed use instructions, see + https://github.com/sendgrid/sendgrid-python + """ + + def __init__( + self, + username=None, + password=None, + host='https://email.twilio.com', + impersonate_subuser=None): + """ + Construct the Twilio Email v3 API object. + Note that the underlying client is being set up during initialization, + therefore changing attributes in runtime will not affect HTTP client + behaviour. + + :param username: Twilio Email API key SID or Account SID to use. If not + provided, value will be read from the environment + variable "TWILIO_API_KEY" or "TWILIO_ACCOUNT_SID" + :type username: string + :param password: Twilio Email API key secret or Account Auth Token to + use. If not provided, value will be read from the + environment variable "TWILIO_API_SECRET" or + "TWILIO_AUTH_TOKEN" + :type password: string + :param impersonate_subuser: the subuser to impersonate. Will be passed + by "On-Behalf-Of" header by underlying + client. See + https://sendgrid.com/docs/User_Guide/Settings/subusers.html + for more details + :type impersonate_subuser: string + :param host: base URL for API calls + :type host: string + """ + self.username = username or \ + os.environ.get('TWILIO_API_KEY') or \ + os.environ.get('TWILIO_ACCOUNT_SID') + + self.password = password or \ + os.environ.get('TWILIO_API_SECRET') or \ + os.environ.get('TWILIO_AUTH_TOKEN') + + auth = 'Basic ' + b64encode('{}:{}'.format(self.username, self.password).encode()).decode() + + super(TwilioEmailAPIClient, self).__init__(auth, host, impersonate_subuser) diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index d158e402e..71b0ab6ff 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -1,10 +1,10 @@ -import sendgrid -from sendgrid.helpers.endpoints.ip.unassigned import unassigned -from sendgrid.helpers.mail import * -import os import datetime +import os import unittest +import sendgrid +from sendgrid.helpers.endpoints.ip.unassigned import unassigned + host = "http://localhost:4010" @@ -18,12 +18,9 @@ def setUpClass(cls): os.path.dirname(__file__)), '/..') cls.sg = sendgrid.SendGridAPIClient(host=host) cls.devnull = open(os.devnull, 'w') - prism_cmd = None 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") @@ -2305,7 +2302,7 @@ def test_whitelabel_links__link_id__subuser_post(self): def test_license_year(self): LICENSE_FILE = 'LICENSE.md' - copyright_line='' + copyright_line = '' with open(LICENSE_FILE, 'r') as f: for line in f: if line.startswith('Copyright'): @@ -2314,8 +2311,3 @@ def test_license_year(self): self.assertEqual( 'Copyright (C) %s, Twilio SendGrid, Inc. ' % datetime.datetime.now().year, copyright_line) - - # @classmethod - # def tearDownClass(cls): - # cls.p.kill() - # print("Prism Shut Down") diff --git a/test/test_twilio_email.py b/test/test_twilio_email.py new file mode 100644 index 000000000..92269acff --- /dev/null +++ b/test/test_twilio_email.py @@ -0,0 +1,37 @@ +import os +import unittest + +from sendgrid import TwilioEmailAPIClient + + +class UnitTests(unittest.TestCase): + + @classmethod + def setUpClass(cls): + os.environ['TWILIO_API_KEY'] = 'api-key' + os.environ['TWILIO_API_SECRET'] = 'api-secret' + os.environ['TWILIO_ACCOUNT_SID'] = 'account-sid' + os.environ['TWILIO_AUTH_TOKEN'] = 'auth-token' + + def test_init_key_over_token(self): + mail_client = TwilioEmailAPIClient() + + self.assertEqual(mail_client.username, 'api-key') + self.assertEqual(mail_client.password, 'api-secret') + self.assertEqual(mail_client.host, 'https://email.twilio.com') + + def test_init_token(self): + del os.environ['TWILIO_API_KEY'] + del os.environ['TWILIO_API_SECRET'] + + mail_client = TwilioEmailAPIClient() + + self.assertEqual(mail_client.username, 'account-sid') + self.assertEqual(mail_client.password, 'auth-token') + + def test_init_args(self): + mail_client = TwilioEmailAPIClient('username', 'password') + + self.assertEqual(mail_client.username, 'username') + self.assertEqual(mail_client.password, 'password') + self.assertEqual(mail_client.auth, 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=') diff --git a/use_cases/README.md b/use_cases/README.md index 46a508795..a91f1f5a4 100644 --- a/use_cases/README.md +++ b/use_cases/README.md @@ -18,8 +18,10 @@ This directory provides examples for specific use cases of this library. Please * [Integrate with Slack Events API](slack_event_api_integration.md) * [Legacy Templates](legacy_templates.md) -### Working with SMS -* [Send a SMS Message](sms.md) +# Twilio Use Cases +* [Twilio Setup](twilio-setup.md) +* [Send an Email With Twilio Email (Pilot)](twilio-email.md) +* [Send an SMS Message](sms.md) ### Troubleshooting * [Error Handling](error_handling.md) diff --git a/use_cases/sms.md b/use_cases/sms.md index bd8655171..1e51f0a25 100644 --- a/use_cases/sms.md +++ b/use_cases/sms.md @@ -1,43 +1,12 @@ -Following are the steps to add Twilio SMS to your app: +First, follow the [Twilio Setup](twilio-setup.md) guide for creating a Twilio account and setting up environment variables with the proper credentials. -## 1. Obtain a Free Twilio Account - -Sign up for a free Twilio account [here](https://www.twilio.com/try-twilio?source=sendgrid-python). - -## 2. Update Your Environment Variables - -You can obtain your Account Sid and Auth Token from [twilio.com/console](https://twilio.com/console). - -### Mac +Then, install the Twilio Helper Library. ```bash -echo "export TWILIO_ACCOUNT_SID='YOUR_TWILIO_ACCOUNT_SID'" > twilio.env -echo "export TWILIO_AUTH_TOKEN='YOUR_TWILIO_AUTH_TOKEN'" >> twilio.env -echo "twilio.env" >> .gitignore -source ./twilio.env +pip install twilio ``` -### Windows - -Temporarily set the environment variable (accessible only during the current CLI session): - -```bash -set TWILIO_ACCOUNT_SID=YOUR_TWILIO_ACCOUNT_SID -set TWILIO_AUTH_TOKEN=YOUR_TWILIO_AUTH_TOKEN -``` - -Permanently set the environment variable (accessible in all subsequent CLI sessions): - -```bash -setx TWILIO_ACCOUNT_SID "YOUR_TWILIO_ACCOUNT_SID" -setx TWILIO_AUTH_TOKEN "YOUR_TWILIO_AUTH_TOKEN" -``` - -## 3. Install the Twilio Helper Library - -`pip install twilio` - -Then, you can execute the following code. +Finally, send a message. ```python import os @@ -50,12 +19,11 @@ to_number ='+15558675310' body = "Join Earth's mightiest heroes. Like Kevin Bacon." client = Client(account_sid, auth_token) -message = client.messages \ - .create( - body=body, - from_=from_number, - to=to_number - ) +message = client.messages.create( + body=body, + from_=from_number, + to=to_number +) print(message.sid) ``` diff --git a/use_cases/twilio-email.md b/use_cases/twilio-email.md new file mode 100644 index 000000000..c8f0373d8 --- /dev/null +++ b/use_cases/twilio-email.md @@ -0,0 +1,16 @@ +First, follow the [Twilio Setup](twilio-setup.md) guide for creating a Twilio account and setting up environment variables with the proper credentials. + +Then, initialize the Twilio Email Client. + +```python +import sendgrid +import os + +mail_client = sendgrid.TwilioEmailAPIClient(os.environ.get('TWILIO_API_KEY'), os.environ.get('TWILIO_API_SECRET')) + +# or + +mail_client = sendgrid.TwilioEmailAPIClient(os.environ.get('TWILIO_ACCOUNT_SID'), os.environ.get('TWILIO_AUTH_TOKEN')) +``` + +This client has the same interface as the `SendGridAPIClient` client. diff --git a/use_cases/twilio-setup.md b/use_cases/twilio-setup.md new file mode 100644 index 000000000..315cd3b32 --- /dev/null +++ b/use_cases/twilio-setup.md @@ -0,0 +1,54 @@ +## 1. Obtain a Free Twilio Account + +Sign up for a free Twilio account [here](https://www.twilio.com/try-twilio?source=sendgrid-nodejs). + +## 2. Set Up Your Environment Variables + +The Twilio API allows for authentication using with either an API key/secret or your Account SID/Auth Token. You can create an API key [here](https://twil.io/get-api-key) or obtain your Account SID and Auth Token [here](https://twil.io/console). + +Once you have those, follow the steps below based on your operating system. + +### Linux/Mac + +```bash +echo "export TWILIO_API_KEY='YOUR_TWILIO_API_KEY'" > twilio.env +echo "export TWILIO_API_SECRET='YOUR_TWILIO_API_SECRET'" >> twilio.env + +# or + +echo "export TWILIO_ACCOUNT_SID='YOUR_TWILIO_ACCOUNT_SID'" > twilio.env +echo "export TWILIO_AUTH_TOKEN='YOUR_TWILIO_AUTH_TOKEN'" >> twilio.env +``` + +Then: + +```bash +echo "twilio.env" >> .gitignore +source ./twilio.env +``` + +### Windows + +Temporarily set the environment variable (accessible only during the current CLI session): + +```bash +set TWILIO_API_KEY=YOUR_TWILIO_API_KEY +set TWILIO_API_SECRET=YOUR_TWILIO_API_SECRET + +: or + +set TWILIO_ACCOUNT_SID=YOUR_TWILIO_ACCOUNT_SID +set TWILIO_AUTH_TOKEN=YOUR_TWILIO_AUTH_TOKEN +``` + +Or permanently set the environment variable (accessible in all subsequent CLI sessions): + +```bash +setx TWILIO_API_KEY "YOUR_TWILIO_API_KEY" +setx TWILIO_API_SECRET "YOUR_TWILIO_API_SECRET" + +: or + +setx TWILIO_ACCOUNT_SID "YOUR_TWILIO_ACCOUNT_SID" +setx TWILIO_AUTH_TOKEN "YOUR_TWILIO_AUTH_TOKEN" +``` From 336999eba734fec74a2471b2e8ac620c15f3c8e5 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Wed, 22 Apr 2020 11:02:02 -0500 Subject: [PATCH 268/462] docs: correct 'personalization' spelling --- sendgrid/helpers/mail/mail.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index a938929be..5ac3ef800 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -3,24 +3,23 @@ from .cc_email import Cc from .content import Content from .custom_arg import CustomArg +from .dynamic_template_data import DynamicTemplateData from .email import Email from .from_email import From from .header import Header -from .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): """Creates the response body for v3/mail/send""" + def __init__( self, from_email=None, @@ -135,7 +134,7 @@ def _set_emails( :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 + :param is_multiple: Create a new personalization for each recipient :type is_multiple: bool :param p: p is the Personalization object or Personalization object index @@ -215,7 +214,7 @@ def to(self, to_emails, global_substitutions=None, is_multiple=False, p=0): :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 + :param is_multiple: Create a new personalization for each recipient :type is_multiple: bool :param p: p is the Personalization object or Personalization object index @@ -239,11 +238,11 @@ 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 to_email: A To object + :type to_email: 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 + :param is_multiple: Create a new personalization for each recipient :type is_multiple: bool :param p: p is the Personalization object or Personalization object index @@ -278,7 +277,7 @@ def cc(self, cc_emails, global_substitutions=None, is_multiple=False, p=0): :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 + :param is_multiple: Create a new personalization for each recipient :type is_multiple: bool :param p: p is the Personalization object or Personalization object index @@ -306,7 +305,7 @@ def add_cc( :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 + :param is_multiple: Create a new personalization for each recipient :type is_multiple: bool :param p: p is the Personalization object or Personalization object index @@ -338,7 +337,7 @@ def bcc( :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 + :param is_multiple: Create a new personalization for each recipient :type is_multiple: bool :param p: p is the Personalization object or Personalization object index @@ -370,7 +369,7 @@ def add_bcc( :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 + :param is_multiple: Create a new personalization for each recipient :type is_multiple: bool :param p: p is the Personalization object or Personalization object index @@ -476,7 +475,7 @@ def add_header(self, header): if isinstance(header, dict): (k, v) = list(header.items())[0] self._headers = self._ensure_append( - Header(k, v), self._headers) + Header(k, v), self._headers) else: self._headers = self._ensure_append(header, self._headers) @@ -951,7 +950,7 @@ def get(self): '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), + '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), From ca8bf29e68066bd89ef99b92207f3f4a9eed03c3 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 29 Apr 2020 19:27:53 +0000 Subject: [PATCH 269/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c45a1872..5536d7f0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-04-29] Version 6.3.0 +-------------------------- +**Library - Feature** +- [PR #882](https://github.com/sendgrid/sendgrid-python/pull/882): add support for Twilio Email. Thanks to [@childish-sambino](https://github.com/childish-sambino)! + + [2020-04-15] Version 6.2.2 -------------------------- **Library - Fix** From 11d526c699feccee218a09abe38d7fa3b6e3fc10 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 29 Apr 2020 19:35:12 +0000 Subject: [PATCH 270/462] Release 6.3.0 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index ac1df1f77..89d98bf88 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.2.2' +__version__ = '6.3.0' From ee02b3038e33e358076b7d3ee2b1fcba6fd57684 Mon Sep 17 00:00:00 2001 From: childish-sambino Date: Tue, 5 May 2020 11:37:16 -0500 Subject: [PATCH 271/462] fix: migrate to common prism setup (#888) --- .gitignore | 1 + .travis.yml | 26 +++++++++---------- Dockerfile | 12 +++++++++ Makefile | 14 +++++++---- test/prism.sh | 58 ------------------------------------------- test/test_sendgrid.py | 8 ++---- 6 files changed, 36 insertions(+), 83 deletions(-) create mode 100644 Dockerfile delete mode 100755 test/prism.sh diff --git a/.gitignore b/.gitignore index 2c374a9e9..53f5e85ed 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ __pycache__ example.pdf TODO.txt twilio.env +prism diff --git a/.travis.yml b/.travis.yml index 8101380a5..d44eb2352 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,27 +1,25 @@ dist: xenial # required for Python >= 3.7 language: python cache: pip -python: - - '2.7' - - '3.5' - - '3.6' - - '3.7' - - '3.8' +services: + - docker env: + matrix: + - version=2.7 + - version=3.5 + - version=3.6 + - version=3.7 + - version=3.8 global: - - CC_TEST_REPORTER_ID=$TRAVIS_CODE_CLIMATE_TOKEN SENDGRID_API_KEY=SGAPIKEY -install: - - make install - - make test-install + - CC_TEST_REPORTER_ID=$TRAVIS_CODE_CLIMATE_TOKEN before_script: - - "./test/prism.sh &" - - sleep 20 - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - ./cc-test-reporter before-build script: - - . venv/bin/activate; coverage run -m unittest discover + - make test-docker after_script: + - make test-install - . venv/bin/activate; codecov - ./cc-test-reporter after-build --exit-code $? deploy: @@ -32,7 +30,7 @@ deploy: distributions: sdist bdist_wheel on: tags: true - python: '3.6' + condition: $version=3.6 notifications: slack: diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..6f75cd98f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +ARG version=latest +FROM python:$version + +RUN pip install virtualenv + +COPY prism/prism/nginx/cert.crt /usr/local/share/ca-certificates/cert.crt +RUN update-ca-certificates + +WORKDIR /app +COPY . . + +RUN make install diff --git a/Makefile b/Makefile index 0e275b62b..27d2056c4 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: venv install test-install test clean nopyc +.PHONY: venv install test-install test test-integ test-docker clean nopyc venv: @python --version || (echo "Python is not installed, please install Python 2 or Python 3"; exit 1); @@ -8,13 +8,17 @@ install: venv . venv/bin/activate; python setup.py install . venv/bin/activate; pip install -r requirements.txt -test-install: +test-install: install . venv/bin/activate; pip install -r test/requirements.txt test: test-install - ./test/prism.sh & - sleep 2 - . venv/bin/activate; python -m unittest discover -v + +test-integ: test + . venv/bin/activate; coverage run -m unittest discover + +version ?= latest +test-docker: + curl -s https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/prism/prism.sh | version=$(version) bash clean: nopyc rm -rf venv diff --git a/test/prism.sh b/test/prism.sh deleted file mode 100755 index cc6a0bb15..000000000 --- a/test/prism.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/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 -} - -run () { - echo "Running prism..." - cd ../prism/bin - ./prism run --mock --spec https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/oai_stoplight.json -} - -if [ -f ../prism/bin/prism ]; then - echo "Prism is already installed." - run -else - echo "Prism is not installed." - install - run -fi \ No newline at end of file diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index 71b0ab6ff..eb9ebabb3 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -5,18 +5,15 @@ import sendgrid from sendgrid.helpers.endpoints.ip.unassigned import unassigned -host = "http://localhost:4010" - class UnitTests(unittest.TestCase): @classmethod def setUpClass(cls): - cls.host = host cls.path = '{}{}'.format( os.path.abspath( os.path.dirname(__file__)), '/..') - cls.sg = sendgrid.SendGridAPIClient(host=host) + cls.sg = sendgrid.SendGridAPIClient() cls.devnull = open(os.devnull, 'w') def test_api_key_init(self): @@ -34,7 +31,6 @@ def test_api_key_setter(self): def test_impersonate_subuser_init(self): temp_subuser = 'abcxyz@this.is.a.test.subuser' sg_impersonate = sendgrid.SendGridAPIClient( - host=host, impersonate_subuser=temp_subuser) self.assertEqual(sg_impersonate.impersonate_subuser, temp_subuser) @@ -43,7 +39,7 @@ def test_useragent(self): self.assertEqual(self.sg.useragent, useragent) def test_host(self): - self.assertEqual(self.sg.host, self.host) + self.assertEqual(self.sg.host, 'https://api.sendgrid.com') def test_get_default_headers(self): headers = self.sg._default_headers From e1e53496b7e1979e0c12b3bc80c84056b19d5b71 Mon Sep 17 00:00:00 2001 From: Paul McMillan Date: Tue, 12 May 2020 07:21:48 -0700 Subject: [PATCH 272/462] docs: Update readme supported versions (#893) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 277a1d0c6..09b0ce68c 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ We appreciate your continued support, thank you! ## Prerequisites -- Python version 2.6, 2.7, 3.4, 3.5 or 3.6 +- Python version 2.7, 3.5, 3.6, 3.7, or 3.8 - The SendGrid service, starting at the [free level](https://sendgrid.com/free?source=sendgrid-python) ## Setup Environment Variables From 5d5fb2779c2c4c468c0917c9979e26b86c00d843 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 13 May 2020 19:45:11 +0000 Subject: [PATCH 273/462] [Librarian] Version Bump --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5536d7f0b..890c0145b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-05-13] Version 6.3.1 +-------------------------- +**Library - Docs** +- [PR #893](https://github.com/sendgrid/sendgrid-python/pull/893): Update readme supported versions. Thanks to [@PaulMcMillan](https://github.com/PaulMcMillan)! + +**Library - Fix** +- [PR #888](https://github.com/sendgrid/sendgrid-python/pull/888): migrate to common prism setup. Thanks to [@childish-sambino](https://github.com/childish-sambino)! + + [2020-04-29] Version 6.3.0 -------------------------- **Library - Feature** From 9ff4e296545db27457424398ac205185749484f3 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 13 May 2020 20:08:41 +0000 Subject: [PATCH 274/462] Release 6.3.1 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 89d98bf88..ea089f2db 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.3.0' +__version__ = '6.3.1' From 2377c406f667f0da3bba3fb9fd5ffe79b10533ec Mon Sep 17 00:00:00 2001 From: childish-sambino Date: Fri, 15 May 2020 09:49:44 -0500 Subject: [PATCH 275/462] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d44eb2352..d01aedaea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ deploy: distributions: sdist bdist_wheel on: tags: true - condition: $version=3.6 + condition: env(version)=3.6 notifications: slack: From 09f787662e247f102494ce43091503daecc4f86a Mon Sep 17 00:00:00 2001 From: Dmitriy Krasilnikov Date: Mon, 18 May 2020 16:41:35 +0300 Subject: [PATCH 276/462] docs: Fixed Subject typo (#895) --- sendgrid/helpers/mail/subject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/helpers/mail/subject.py b/sendgrid/helpers/mail/subject.py index a7fecec8f..1637f4004 100644 --- a/sendgrid/helpers/mail/subject.py +++ b/sendgrid/helpers/mail/subject.py @@ -2,7 +2,7 @@ class Subject(object): """A subject for an email message.""" def __init__(self, subject, p=None): - """Create a Subjuct. + """Create a Subject. :param subject: The subject for an email :type subject: string From a2171d1273a3f30338fb473bd1cb763c9b1e8b7d Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 27 May 2020 18:52:50 +0000 Subject: [PATCH 277/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 890c0145b..b4d776082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-05-27] Version 6.3.2 +-------------------------- +**Library - Docs** +- [PR #895](https://github.com/sendgrid/sendgrid-python/pull/895): Fixed Subject typo. Thanks to [@dmitry-krasilnikov](https://github.com/dmitry-krasilnikov)! + + [2020-05-13] Version 6.3.1 -------------------------- **Library - Docs** From 257d9f514670c0ade1fbb60a520fd6692563ee9a Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 27 May 2020 18:58:15 +0000 Subject: [PATCH 278/462] Release 6.3.2 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index ea089f2db..144bf2f7f 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.3.1' +__version__ = '6.3.2' From 12ace42c1fd786df8818e5802af9239ba8008db3 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Fri, 29 May 2020 09:27:39 -0500 Subject: [PATCH 279/462] feat: allow interactive docker testing with 'command' env var Piping to the 'bash' command does not allow for interacting with the Docker container when running the 'test-docker' make target. By moving it to its own line, the interaction is now allowed. Example: `command=bash make test-docker` --- .gitignore | 2 +- Makefile | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 53f5e85ed..504640243 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,4 @@ __pycache__ example.pdf TODO.txt twilio.env -prism +prism* diff --git a/Makefile b/Makefile index 27d2056c4..fe7f18e97 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,8 @@ test-integ: test version ?= latest test-docker: - curl -s https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/prism/prism.sh | version=$(version) bash + curl -s https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/prism/prism.sh -o prism.sh + version=$(version) bash ./prism.sh clean: nopyc rm -rf venv From 10d6eb0152fbbfb49adaf99b1e7a6054ee6302be Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Fri, 29 May 2020 10:05:16 -0500 Subject: [PATCH 280/462] docs: shorten and correct the issue template link --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 63d07dc75..41a454683 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,7 +55,7 @@ Before you decide to create a new issue, please try the following: ### Please use our Bug Report Template -In order to make the process easier, we've included a [sample bug report template]((https://github.com/sendgrid/sendgrid-python/ISSUE_TEMPLATE.md)) (borrowed from [Ghost](https://github.com/TryGhost/Ghost/)). The template uses [GitHub flavored markdown](https://help.github.com/articles/github-flavored-markdown/) for formatting. +In order to make the process easier, we've included a [sample bug report template](ISSUE_TEMPLATE.md). ## Improvements to the Codebase From 6fe3d3c78e9a9aeb498d588529600af8018b63ab Mon Sep 17 00:00:00 2001 From: childish-sambino Date: Wed, 3 Jun 2020 10:47:41 -0500 Subject: [PATCH 281/462] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d01aedaea..489a4a788 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ deploy: distributions: sdist bdist_wheel on: tags: true - condition: env(version)=3.6 + condition: $version = 3.6 notifications: slack: From 75cc2d15feff643e6d17258d04cf5ece95fc01b9 Mon Sep 17 00:00:00 2001 From: childish-sambino Date: Fri, 12 Jun 2020 09:45:00 -0500 Subject: [PATCH 282/462] fix: revert "feat: Add equality to Email" (#904) This reverts commit b2ca1f4b9dfa32ad7ff829c52d0a5efd9128a2f3. --- sendgrid/helpers/mail/email.py | 21 --------------- test/test_email.py | 48 ---------------------------------- 2 files changed, 69 deletions(-) diff --git a/sendgrid/helpers/mail/email.py b/sendgrid/helpers/mail/email.py index 10a8718b1..3c2cb3bbb 100644 --- a/sendgrid/helpers/mail/email.py +++ b/sendgrid/helpers/mail/email.py @@ -73,27 +73,6 @@ def __init__(self, if p is not None: self.personalization = p - def __eq__(self, other): - """Email objects are equal if they have the same email and the same name - - :param other: object to compare with - :type other: any - """ - if isinstance(other, Email): - return self.email == other.email and self.name == other.name - return NotImplemented - - def __ne__(self, other): - """Inverse of __eq__ (required for Python 2) - - :param other: object to compare with - :type other: any - """ - x = self.__eq__(other) - if x is not NotImplemented: - return not x - return NotImplemented - @property def name(self): """Name associated with this email. diff --git a/test/test_email.py b/test/test_email.py index c8614fe17..eb50374aa 100644 --- a/test/test_email.py +++ b/test/test_email.py @@ -80,51 +80,3 @@ def test_add_unicode_name_with_comma(self): email.name = name self.assertEqual(email.name, u'"' + name + u'"') - - def test_equality_email_name(self): - address = "test@example.com" - name = "SomeName" - email1 = Email(address, name) - email2 = Email(address, name) - - self.assertEqual(email1, email2) - - def test_equality_email(self): - address = "test@example.com" - email1 = Email(address) - email2 = Email(address) - - self.assertEqual(email1, email2) - - def test_equality_name(self): - name = "SomeName" - email1 = Email() - email1.name = name - email2 = Email() - email2.name = name - - self.assertEqual(email1, email2) - - def test_equality_different_emails(self): - address1 = "test1@example.com" - email1 = Email(address1) - address2 = "test2@example.com" - email2 = Email(address2) - - self.assertNotEqual(email1, email2) - - def test_equality_different_name(self): - name1 = "SomeName1" - email1 = Email() - email1.name = name1 - name2 = "SomeName2" - email2 = Email() - email2.name = name2 - - self.assertNotEqual(email1, email2) - - def test_equality_non_email(self): - address = "test@example.com" - email = Email(address) - - self.assertNotEqual(email, address) From cb58667142e30642b9b60318b834c0713c71ac12 Mon Sep 17 00:00:00 2001 From: Elise Shanholtz Date: Mon, 15 Jun 2020 10:16:10 -0700 Subject: [PATCH 283/462] feat: verify signature from event webhook (#901) When enabling the "Signed Event Webhook Requests" feature in Mail Settings, Twilio SendGrid will generate a private and public key pair using the Elliptic Curve Digital Signature Algorithm (ECDSA). Once that is successfully enabled, all new event posts will have two new headers: X-Twilio-Email-Event-Webhook-Signature and X-Twilio-Email-Event-Webhook-Timestamp, which can be used to validate your events. This SDK update will make it easier to verify signatures from signed event webhook requests by using the VerifySignature method. Pass in the public key, event payload, signature, and timestamp to validate. Note: You will need to convert your public key string to an elliptic public key object in order to use the VerifySignature method. --- Makefile | 2 +- .../eventwebhook/eventwebhook_example.py | 14 ++++++ requirements.txt | 1 + sendgrid/__init__.py | 1 + sendgrid/helpers/eventwebhook/__init__.py | 50 +++++++++++++++++++ .../eventwebhook/eventwebhook_header.py | 10 ++++ sendgrid/helpers/inbound/config.py | 2 +- test/test_eventwebhook.py | 42 ++++++++++++++++ 8 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 examples/helpers/eventwebhook/eventwebhook_example.py create mode 100644 sendgrid/helpers/eventwebhook/__init__.py create mode 100644 sendgrid/helpers/eventwebhook/eventwebhook_header.py create mode 100644 test/test_eventwebhook.py diff --git a/Makefile b/Makefile index fe7f18e97..356d46682 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: venv install test-install test test-integ test-docker clean nopyc -venv: +venv: clean @python --version || (echo "Python is not installed, please install Python 2 or Python 3"; exit 1); virtualenv --python=python venv diff --git a/examples/helpers/eventwebhook/eventwebhook_example.py b/examples/helpers/eventwebhook/eventwebhook_example.py new file mode 100644 index 000000000..91ad8d64b --- /dev/null +++ b/examples/helpers/eventwebhook/eventwebhook_example.py @@ -0,0 +1,14 @@ +from sendgrid.helpers.eventwebhook import EventWebhook, EventWebhookHeader + +def is_valid_signature(request): + public_key = 'base64-encoded public key' + + event_webhook = EventWebhook() + ec_public_key = event_webhook.convert_public_key_to_ecdsa(public_key) + + return event_webhook.verify_signature( + request.text, + request.headers[EventWebhookHeader.SIGNATURE], + request.headers[EventWebhookHeader.TIMESTAMP], + ec_public_key + ) diff --git a/requirements.txt b/requirements.txt index ce29b7f3e..ff1ba3c35 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ PyYAML>=4.2b1 python-http-client>=3.2.1 six==1.11.0 pytest==3.8.2 +starkbank-ecdsa>=1.0.0 diff --git a/sendgrid/__init__.py b/sendgrid/__init__.py index eb6d58961..cd994dd2f 100644 --- a/sendgrid/__init__.py +++ b/sendgrid/__init__.py @@ -18,6 +18,7 @@ from .helpers.endpoints import * # noqa from .helpers.mail import * # noqa from .helpers.stats import * # noqa +from .helpers.eventwebhook import * # noqa from .sendgrid import SendGridAPIClient # noqa from .twilio_email import TwilioEmailAPIClient # noqa from .version import __version__ diff --git a/sendgrid/helpers/eventwebhook/__init__.py b/sendgrid/helpers/eventwebhook/__init__.py new file mode 100644 index 000000000..a44eb5b89 --- /dev/null +++ b/sendgrid/helpers/eventwebhook/__init__.py @@ -0,0 +1,50 @@ +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.publicKey import PublicKey +from ellipticcurve.signature import Signature + +from .eventwebhook_header import EventWebhookHeader + +class EventWebhook: + """ + This class allows you to use the Event Webhook feature. Read the docs for + more details: https://sendgrid.com/docs/for-developers/tracking-events/event + """ + + def __init__(self, public_key=None): + """ + Construct the Event Webhook verifier object + :param public_key: verification key under Mail Settings + :type public_key: string + """ + self.public_key = self.convert_public_key_to_ecdsa(public_key) if public_key else public_key + + def convert_public_key_to_ecdsa(self, public_key): + """ + Convert the public key string to a ECPublicKey. + + :param public_key: verification key under Mail Settings + :type public_key string + :return: public key using the ECDSA algorithm + :rtype PublicKey + """ + return PublicKey.fromPem(public_key) + + def verify_signature(self, payload, signature, timestamp, public_key=None): + """ + Verify signed event webhook requests. + + :param payload: event payload in the request body + :type payload: string + :param signature: value obtained from the 'X-Twilio-Email-Event-Webhook-Signature' header + :type signature: string + :param timestamp: value obtained from the 'X-Twilio-Email-Event-Webhook-Timestamp' header + :type timestamp: string + :param public_key: elliptic curve public key + :type public_key: PublicKey + :return: true or false if signature is valid + """ + timestamped_payload = timestamp + payload + decoded_signature = Signature.fromBase64(signature) + + key = public_key or self.public_key + return Ecdsa.verify(timestamped_payload, decoded_signature, key) diff --git a/sendgrid/helpers/eventwebhook/eventwebhook_header.py b/sendgrid/helpers/eventwebhook/eventwebhook_header.py new file mode 100644 index 000000000..a41a48524 --- /dev/null +++ b/sendgrid/helpers/eventwebhook/eventwebhook_header.py @@ -0,0 +1,10 @@ +class EventWebhookHeader: + """ + This class lists headers that get posted to the webhook. Read the docs for + more details: https://sendgrid.com/docs/for-developers/tracking-events/event + """ + SIGNATURE = 'X-Twilio-Email-Event-Webhook-Signature' + TIMESTAMP = 'X-Twilio-Email-Event-Webhook-Timestamp' + + def __init__(self): + pass diff --git a/sendgrid/helpers/inbound/config.py b/sendgrid/helpers/inbound/config.py index 32bec0793..06ca683cb 100644 --- a/sendgrid/helpers/inbound/config.py +++ b/sendgrid/helpers/inbound/config.py @@ -16,7 +16,7 @@ def __init__(self, **opts): 'path', os.path.abspath(os.path.dirname(__file__)) ) with open('{0}/config.yml'.format(self.path)) as stream: - config = yaml.load(stream) + config = yaml.load(stream, Loader=yaml.FullLoader) self._debug_mode = config['debug_mode'] self._endpoint = config['endpoint'] self._host = config['host'] diff --git a/test/test_eventwebhook.py b/test/test_eventwebhook.py new file mode 100644 index 000000000..28f9ad282 --- /dev/null +++ b/test/test_eventwebhook.py @@ -0,0 +1,42 @@ +import json +import unittest + +from sendgrid import EventWebhook + + +class UnitTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.PUBLIC_KEY = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEDr2LjtURuePQzplybdC+u4CwrqDqBaWjcMMsTbhdbcwHBcepxo7yAQGhHPTnlvFYPAZFceEu/1FwCM/QmGUhA==' + cls.SIGNATURE = 'MEUCIQCtIHJeH93Y+qpYeWrySphQgpNGNr/U+UyUlBkU6n7RAwIgJTz2C+8a8xonZGi6BpSzoQsbVRamr2nlxFDWYNH2j/0=' + cls.TIMESTAMP = '1588788367' + cls.PAYLOAD = json.dumps({ + 'event': 'test_event', + 'category': 'example_payload', + 'message_id': 'message_id', + }, sort_keys=True, separators=(',', ':')) + + def test_verify_valid_signature(self): + ew = EventWebhook() + key = ew.convert_public_key_to_ecdsa(self.PUBLIC_KEY) + self.assertTrue(ew.verify_signature(self.PAYLOAD, self.SIGNATURE, self.TIMESTAMP, key)) + + def test_verify_bad_key(self): + ew = EventWebhook('MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqTxd43gyp8IOEto2LdIfjRQrIbsd4SXZkLW6jDutdhXSJCWHw8REntlo7aNDthvj+y7GjUuFDb/R1NGe1OPzpA==') + self.assertFalse(ew.verify_signature(self.PAYLOAD, self.SIGNATURE, self.TIMESTAMP)) + + def test_verify_bad_payload(self): + ew = EventWebhook(self.PUBLIC_KEY) + self.assertFalse(ew.verify_signature('payload', self.SIGNATURE, self.TIMESTAMP)) + + def test_verify_bad_signature(self): + ew = EventWebhook(self.PUBLIC_KEY) + self.assertFalse(ew.verify_signature( + self.PAYLOAD, + 'MEUCIQCtIHJeH93Y+qpYeWrySphQgpNGNr/U+UyUlBkU6n7RAwIgJTz2C+8a8xonZGi6BpSzoQsbVRamr2nlxFDWYNH3j/0=', + self.TIMESTAMP + )) + + def test_verify_bad_timestamp(self): + ew = EventWebhook(self.PUBLIC_KEY) + self.assertFalse(ew.verify_signature(self.PAYLOAD, self.SIGNATURE, 'timestamp')) From 3415520105dca5973dfbe90b3655252a74d82335 Mon Sep 17 00:00:00 2001 From: childish-sambino Date: Wed, 17 Jun 2020 11:08:29 -0500 Subject: [PATCH 284/462] feat: add support for dynamic template data to Email class (#908) Fixes https://github.com/sendgrid/sendgrid-python/issues/899 This is helpful when sending multiple emails to multiple recipients. You can now include the dynamic template data with the recipient which will then be included in the personalization. --- .../helpers/mail/dynamic_template_data.py | 12 +-- sendgrid/helpers/mail/email.py | 49 +++++++--- sendgrid/helpers/mail/mail.py | 10 +- sendgrid/helpers/mail/personalization.py | 20 ++-- test/test_mail_helpers.py | 97 +++++++++++++++---- ..._multiple_emails_to_multiple_recipients.md | 23 ++--- 6 files changed, 148 insertions(+), 63 deletions(-) diff --git a/sendgrid/helpers/mail/dynamic_template_data.py b/sendgrid/helpers/mail/dynamic_template_data.py index d682dbf2d..e12967b70 100644 --- a/sendgrid/helpers/mail/dynamic_template_data.py +++ b/sendgrid/helpers/mail/dynamic_template_data.py @@ -5,10 +5,10 @@ class DynamicTemplateData(object): def __init__(self, dynamic_template_data=None, p=0): """Data for a transactional template. - Should be JSON-serializeable structure. + Should be JSON-serializable structure. :param dynamic_template_data: Data for a transactional template. - :type dynamic_template_data: A JSON-serializeable structure + :type dynamic_template_data: A JSON-serializable structure :param name: p is the Personalization object or Personalization object index :type name: Personalization, integer, optional @@ -25,7 +25,7 @@ def __init__(self, dynamic_template_data=None, p=0): def dynamic_template_data(self): """Data for a transactional template. - :rtype: A JSON-serializeable structure + :rtype: A JSON-serializable structure """ return self._dynamic_template_data @@ -34,7 +34,7 @@ def dynamic_template_data(self, value): """Data for a transactional template. :param value: Data for a transactional template. - :type value: A JSON-serializeable structure + :type value: A JSON-serializable structure """ self._dynamic_template_data = value @@ -59,7 +59,7 @@ def personalization(self, value): def __str__(self): """Get a JSON representation of this object. - :rtype: A JSON-serializeable structure + :rtype: A JSON-serializable structure """ return str(self.get()) @@ -68,6 +68,6 @@ def get(self): Get a JSON-ready representation of this DynamicTemplateData object. :returns: Data for a transactional template. - :rtype: A JSON-serializeable structure. + :rtype: A JSON-serializable structure. """ return self.dynamic_template_data diff --git a/sendgrid/helpers/mail/email.py b/sendgrid/helpers/mail/email.py index 3c2cb3bbb..ba3a98848 100644 --- a/sendgrid/helpers/mail/email.py +++ b/sendgrid/helpers/mail/email.py @@ -18,7 +18,7 @@ html_entity_decode = __html_parser__.unescape try: - basestring = basestring + basestring = basestring except NameError: # Define basestring when Python >= 3.0 basestring = str @@ -32,7 +32,8 @@ def __init__(self, name=None, substitutions=None, subject=None, - p=0): + p=0, + dynamic_template_data=None): """Create an Email with the given address and name. Either fill the separate name and email fields, or pass all information @@ -41,17 +42,19 @@ def __init__(self, :type email: string, optional :param name: Name for this sender or recipient. :type name: string, optional + :param substitutions: String substitutions to be applied to the email. + :type substitutions: list(Substitution), optional :param subject: Subject for this sender or recipient. :type subject: string, optional :param p: p is the Personalization object or Personalization object index :type p: Personalization, integer, optional + :param dynamic_template_data: Data for a dynamic transactional template. + :type dynamic_template_data: DynamicTemplateData, optional """ self._name = None self._email = None - self._substitutions = None - self._subject = None - self._personalization = None + self._personalization = p if email and not name: # allows passing emails as "Example Name " @@ -64,14 +67,11 @@ def __init__(self, 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 + # Note that these only apply to To Emails (see Personalization.add_to) + # and should be moved but have not been for compatibility. + self._substitutions = substitutions + self._dynamic_template_data = dynamic_template_data + self._subject = subject @property def name(self): @@ -129,7 +129,7 @@ def email(self, value): @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 + 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. @@ -141,13 +141,13 @@ def substitutions(self): @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 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 + 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. @@ -155,6 +155,23 @@ def substitutions(self, value): """ self._substitutions = value + @property + def dynamic_template_data(self): + """Data for a dynamic transactional template. + + :rtype: DynamicTemplateData + """ + return self._dynamic_template_data + + @dynamic_template_data.setter + def dynamic_template_data(self, value): + """Data for a dynamic transactional template. + + :param value: DynamicTemplateData + :type value: DynamicTemplateData + """ + self._dynamic_template_data = value + @property def subject(self): """Subject for this sender or recipient. diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 5ac3ef800..5d9490ba3 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -185,17 +185,17 @@ def _set_emails( @property def personalizations(self): - """A list of one or more Personaliztion objects + """A list of one or more Personalization objects :rtype: list(Personalization) """ return self._personalizations def add_personalization(self, personalization, index=0): - """Add a Personaliztion object + """Add a Personalization object - :param personalizations: Add a Personalization object - :type personalizations: Personalization + :param personalization: Add a Personalization object + :type personalization: Personalization :param index: The index where to add the Personalization :type index: int """ @@ -627,7 +627,7 @@ 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 + :type value: DynamicTemplateData, a JSON-serializable structure """ if not isinstance(value, DynamicTemplateData): value = DynamicTemplateData(value) diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py index 835933017..9239f9458 100644 --- a/sendgrid/helpers/mail/personalization.py +++ b/sendgrid/helpers/mail/personalization.py @@ -51,11 +51,16 @@ def add_to(self, email): self.add_substitution(substitution) else: self.add_substitution(email.substitutions) + + if email.dynamic_template_data: + self.dynamic_template_data = email.dynamic_template_data + if email.subject: if isinstance(email.subject, str): self.subject = email.subject else: self.subject = email.subject.get() + self._tos.append(email.get()) @property @@ -149,10 +154,10 @@ def add_substitution(self, substitution): :type substitution: Substitution """ - if isinstance(substitution, dict): - self._substitutions.append(substitution) - else: - self._substitutions.append(substitution.get()) + if not isinstance(substitution, dict): + substitution = substitution.get() + + self._substitutions.append(substitution) @property def custom_args(self): @@ -190,14 +195,17 @@ def send_at(self, value): @property def dynamic_template_data(self): """Data for dynamic transactional template. - Should be JSON-serializeable structure. + Should be JSON-serializable structure. - :rtype: JSON-serializeable structure + :rtype: JSON-serializable structure """ return self._dynamic_template_data @dynamic_template_data.setter def dynamic_template_data(self, value): + if not isinstance(value, dict): + value = value.get() + self._dynamic_template_data = value def get(self): diff --git a/test/test_mail_helpers.py b/test/test_mail_helpers.py index b29d8b8c2..fd4f3a68f 100644 --- a/test/test_mail_helpers.py +++ b/test/test_mail_helpers.py @@ -10,12 +10,11 @@ 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 + Asm, Attachment, + ClickTracking, Content, + DynamicTemplateData, Email, From, + Mail, Personalization, + Subject, Substitution, To, TrackingSettings ) @@ -210,18 +209,18 @@ def test_multiple_emails_to_multiple_recipients(self): 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')), + 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'), - ]) + 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( @@ -285,6 +284,70 @@ def test_multiple_emails_to_multiple_recipients(self): }''') ) + def test_dynamic_template_data(self): + self.maxDiff = None + + to_emails = [ + To(email='test+to+0@example.com', + name='Example To 0 Name', + dynamic_template_data=DynamicTemplateData({'name': 'Example 0 Name'})), + To(email='test+to+1@example.com', + name='Example To 1 Name', + dynamic_template_data={'name': 'Example 1 Name'}) + ] + message = Mail( + from_email=From('test@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Hi!'), + plain_text_content='Hello!', + html_content='Hello!', + is_multiple=True) + + self.assertEqual( + message.get(), + json.loads(r'''{ + "content": [ + { + "type": "text/plain", + "value": "Hello!" + }, + { + "type": "text/html", + "value": "Hello!" + } + ], + "from": { + "email": "test@example.com", + "name": "Example From Name" + }, + "personalizations": [ + { + "dynamic_template_data": { + "name": "Example 1 Name" + }, + "to": [ + { + "email": "test+to+1@example.com", + "name": "Example To 1 Name" + } + ] + }, + { + "dynamic_template_data": { + "name": "Example 0 Name" + }, + "to": [ + { + "email": "test+to+0@example.com", + "name": "Example To 0 Name" + } + ] + } + ], + "subject": "Hi!" + }''') + ) + def test_kitchen_sink(self): from sendgrid.helpers.mail import ( Mail, From, To, Cc, Bcc, Subject, Substitution, Header, diff --git a/use_cases/send_multiple_emails_to_multiple_recipients.md b/use_cases/send_multiple_emails_to_multiple_recipients.md index 7217d0a0b..e3085469d 100644 --- a/use_cases/send_multiple_emails_to_multiple_recipients.md +++ b/use_cases/send_multiple_emails_to_multiple_recipients.md @@ -1,33 +1,30 @@ ```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', + dynamic_template_data={ + 'name': 'Dynamic Name 0', + 'url': 'https://example.com/test0', }, subject='Override Global Subject'), To(email='test+to1@example.com', name='Example Name 1', - substitutions={ - '-name-': 'Example Name Substitution 1', - '-github-': 'https://example.com/test1', + dynamic_template_data={ + 'name': 'Dynamic Name 1', + 'url': '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, + subject='Global subject', is_multiple=True) +message.template_id = 'd-12345678901234567890123456789012' + try: sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) response = sendgrid_client.send(message) @@ -36,4 +33,4 @@ try: print(response.headers) except Exception as e: print(e.message) -``` \ No newline at end of file +``` From 3a001fcdfc17c69759a4c46b1a74ae8af629cda0 Mon Sep 17 00:00:00 2001 From: John Calhoun Date: Tue, 23 Jun 2020 12:02:13 -0700 Subject: [PATCH 285/462] docs: added docstrings to Stats classes (#912) --- sendgrid/helpers/stats/stats.py | 172 +++++++++++++++++++++++++++++++- 1 file changed, 167 insertions(+), 5 deletions(-) diff --git a/sendgrid/helpers/stats/stats.py b/sendgrid/helpers/stats/stats.py index 8fe1399a2..b866093b5 100644 --- a/sendgrid/helpers/stats/stats.py +++ b/sendgrid/helpers/stats/stats.py @@ -1,6 +1,14 @@ class Stats(object): + """ + Object for building query params for a global email statistics request + """ def __init__( self, start_date=None): + """Create a Stats object + + :param start_date: Date of when stats should begin in YYYY-MM-DD format, defaults to None + :type start_date: string, optional + """ self._start_date = None self._end_date = None self._aggregated_by = None @@ -14,11 +22,18 @@ def __init__( self.start_date = start_date def __str__(self): + """Get a JSON representation of this object. + + :rtype: string + """ return str(self.get()) def get(self): """ - :return: response stats dict + Get a JSON-ready representation of Stats + + :returns: This GlobalStats, ready for use in a request body. + :rtype: response stats dict """ stats = {} if self.start_date is not None: @@ -39,63 +54,136 @@ def get(self): @property def start_date(self): + """Date of when stats should begin in YYYY-MM-DD format + + :rtype: string + """ return self._start_date @start_date.setter def start_date(self, value): + """Date of when stats should begin in YYYY-MM-DD format + + :param value: Date representing when stats should begin + :type value: string + """ self._start_date = value @property def end_date(self): + """Date of when stats should end in YYYY-MM-DD format + + :rtype: string + """ return self._end_date @end_date.setter def end_date(self, value): + """Date of when stats should end in YYYY-MM-DD format + + :param value: Date representing when stats should end + :type value: string + """ self._end_date = value @property def aggregated_by(self): + """Chosen period (e.g. 'day', 'week', 'month') for how stats get grouped + + :rtype: string + """ return self._aggregated_by @aggregated_by.setter def aggregated_by(self, value): + """Chosen period (e.g. 'day', 'week', 'month') for how stats get grouped + + :param value: Period for how keys will get formatted + :type value: string + """ self._aggregated_by = value @property def sort_by_metric(self): + """Metric to sort stats by + + :rtype: string + """ return self._sort_by_metric @sort_by_metric.setter def sort_by_metric(self, value): + """Metric to sort stats by + + :param value: Chosen metric stats will by sorted by + :type value: string + """ self._sort_by_metric = value @property def sort_by_direction(self): + """Direction data will be sorted, either 'asc' or 'desc' + + :rtype: string + """ return self._sort_by_direction @sort_by_direction.setter def sort_by_direction(self, value): + """Direction data will be sorted, either 'asc' or 'desc' + + :param value: Direction of data, either 'asc' or 'desc' + :type value: string + """ self._sort_by_direction = value @property def limit(self): + """Max amount of results to be returned + + :rtype: int + """ return self._limit @limit.setter def limit(self, value): + """Max amount of results to be returned + + :param value: Max amount of results + :type value: int + """ self._limit = value @property def offset(self): + """Number of places a starting point of a data set will move + + :rtype: int + """ return self._offset @offset.setter def offset(self, value): + """Number of places a starting point of a data set will move + + :param value: Number of positions to move from starting point + :type value: int + """ self._offset = value class CategoryStats(Stats): + """ + object for building query params for a category statistics request + """ def __init__(self, start_date=None, categories=None): + """Create a CategoryStats object + + :param start_date: Date of when stats should begin in YYYY-MM-DD format, defaults to None + :type start_date: string, optional + :param categories: list of categories to get results of, defaults to None + :type categories: list(string), optional + """ self._categories = None super(CategoryStats, self).__init__() @@ -107,7 +195,9 @@ def __init__(self, start_date=None, categories=None): def get(self): """ - :return: response stats dict + Get a JSON-ready representation of this CategoryStats. + + :return: response category stats dict """ stats = {} if self.start_date is not None: @@ -131,16 +221,35 @@ def get(self): @property def categories(self): + """List of categories + + :rtype: list(Category) + """ return self._categories def add_category(self, category): + """Appends a category to this object's category list + + :param category: Category to append to CategoryStats + :type category: Category + """ if self._categories is None: self._categories = [] self._categories.append(category) class SubuserStats(Stats): + """ + object of building query params for a subuser statistics request + """ def __init__(self, start_date=None, subusers=None): + """Create a SubuserStats object + + :param start_date: Date of when stats should begin in YYYY-MM-DD format, defaults to None + :type start_date: string, optional + :param subusers: list of subusers to get results of, defaults to None + :type subusers: list(string), optional + """ self._subusers = None super(SubuserStats, self).__init__() @@ -152,7 +261,9 @@ def __init__(self, start_date=None, subusers=None): def get(self): """ - :return: response stats dict + Get a JSON-ready representation of this SubuserStats. + + :return: response subuser stats dict """ stats = {} if self.start_date is not None: @@ -176,47 +287,98 @@ def get(self): @property def subusers(self): + """List of subusers + + :rtype: list(Subuser) + """ return self._subusers def add_subuser(self, subuser): + """Appends a subuser to this object's subuser list + + :param subuser: Subuser to append to SubuserStats + :type subuser: Subuser + """ if self._subusers is None: self._subusers = [] self._subusers.append(subuser) class Category(object): - + """ + Represents a searchable statistics category to be used in a CategoryStats object + """ def __init__(self, name=None): + """Create a Category object + + :param name: name of category, defaults to None + :type name: string, optional + """ self._name = None if name is not None: self._name = name @property def name(self): + """Get name of category + + :rtype: string + """ return self._name @name.setter def name(self, value): + """Set name of category + + :param value: name of the statistical category + :type value: string + """ self._name = value def get(self): + """ + Get a string representation of Category. + + :return: string of the category's name + """ return self.name class Subuser(object): - + """ + Represents a searchable subuser to be used in a SubuserStats object + """ def __init__(self, name=None): + """Create a Subuser object + + :param name: name of subuser, defaults to None + :type name: string, optional + """ self._name = None if name is not None: self._name = name @property def name(self): + """Get name of the subuser + + :rtype: string + """ return self._name @name.setter def name(self, value): + """Set name of the subuser + + :param value: name of the subuser + :type value: string + """ self._name = value def get(self): + """ + Get a string representation of Subuser. + + :return: string of the subuser's name + """ return self.name From 3d11a2c78cce344024530043f1b2ce269e0970db Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 24 Jun 2020 19:13:36 +0000 Subject: [PATCH 286/462] [Librarian] Version Bump --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4d776082..632f4d24e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-06-24] Version 6.4.0 +-------------------------- +**Library - Docs** +- [PR #912](https://github.com/sendgrid/sendgrid-python/pull/912): added docstrings to Stats classes. Thanks to [@DougCal](https://github.com/DougCal)! + +**Library - Feature** +- [PR #908](https://github.com/sendgrid/sendgrid-python/pull/908): add support for dynamic template data to Email class. Thanks to [@childish-sambino](https://github.com/childish-sambino)! +- [PR #901](https://github.com/sendgrid/sendgrid-python/pull/901): verify signature from event webhook. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + +**Library - Fix** +- [PR #904](https://github.com/sendgrid/sendgrid-python/pull/904): revert "feat: Add equality to Email". Thanks to [@childish-sambino](https://github.com/childish-sambino)! + + [2020-05-27] Version 6.3.2 -------------------------- **Library - Docs** From c0d09c911a510054c61c97ebd4c9105fae478334 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 24 Jun 2020 19:28:18 +0000 Subject: [PATCH 287/462] Release 6.4.0 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 144bf2f7f..c1170678c 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.3.2' +__version__ = '6.4.0' From 443ee00d046ef59b7fdacc9493f5b5de6cbe1d84 Mon Sep 17 00:00:00 2001 From: Elise Shanholtz Date: Wed, 24 Jun 2020 15:09:16 -0700 Subject: [PATCH 288/462] fix: add dependency to install requires (#914) --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1c6c5a8c4..b3eeec82d 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,10 @@ exec(f.read()) def getRequires(): - deps = ['python_http_client>=3.2.1'] + deps = [ + 'python_http_client>=3.2.1', + 'starkbank-ecdsa>=1.0.0' + ] return deps From 53fe422a80563bf0d6d853564aded083adc0121b Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 25 Jun 2020 13:59:24 +0000 Subject: [PATCH 289/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 632f4d24e..52e22ddf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-06-25] Version 6.4.1 +-------------------------- +**Library - Fix** +- [PR #914](https://github.com/sendgrid/sendgrid-python/pull/914): add dependency to install requires. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + + [2020-06-24] Version 6.4.0 -------------------------- **Library - Docs** From 84c31931d0ffd26728aaa40df5d75b1e17aad6c0 Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 25 Jun 2020 14:03:50 +0000 Subject: [PATCH 290/462] Release 6.4.1 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index c1170678c..6594dc237 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.4.0' +__version__ = '6.4.1' From 67289467bd56073fba261faac347b5c96b1a0156 Mon Sep 17 00:00:00 2001 From: Honza Javorek Date: Thu, 25 Jun 2020 16:25:56 +0200 Subject: [PATCH 291/462] docs: document change in top-level dependencies (#915) This change adds information about top-level dependencies of the package to various places of its docs. Follow-up to https://github.com/sendgrid/sendgrid-python/pull/914 --- CONTRIBUTING.md | 1 + FIRST_TIMERS.md | 34 +++++++++++++++++----------------- README.md | 1 + README.rst | 2 ++ 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 41a454683..306443d08 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -75,6 +75,7 @@ You can use our Docker image to avoid setting up the development environment you - Python 2.7 and 3.4+ - [python_http_client](https://github.com/sendgrid/python-http-client) +- [ecdsa_python](https://github.com/starkbank/ecdsa-python) ##### Initial setup: ##### diff --git a/FIRST_TIMERS.md b/FIRST_TIMERS.md index 714d94bc1..f6d9e4192 100644 --- a/FIRST_TIMERS.md +++ b/FIRST_TIMERS.md @@ -6,7 +6,7 @@ - [Setting up the Development Environment](#setup) - [Proposing Change through a Pull Request](#pr) - [Be Patient & Wait for reviews](#reviews) - + ### Explore Twilio SendGrid Step 1: Get yourself Access to Twilio SendGrid API Service absolutely free from [here](https://sendgrid.com/free/?source=sendgrid-python)\ @@ -15,8 +15,8 @@ Step 2: Get familiar with Twilio SendGrid Service - Set up your [Twilio 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 Twilio 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 Twilio 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)\ @@ -27,16 +27,16 @@ Step 2: Get familiar with Twilio SendGrid Service 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 1: Install the Prerequistes: Any Version of Python (2.6 through 3.6) and both [python_http_client](https://github.com/sendgrid/python-http-client) and [ecdsa_python](https://github.com/starkbank/ecdsa-python)\ Step 2: Get a local copy of repository using `git clone https://github.com/sendgrid/sendgrid-python.git`\ Step 3: Set your [Twilio 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`\ @@ -46,28 +46,28 @@ Step 2: Get familiar with Twilio SendGrid Service - **/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. ## Be Patient & Wait for Reviews Kindly be patient & follow the suggestions as provided by the peer reviewers. Make required amendments & changes to the PR as asked. - + ## [Explore New Issues to work upon](https://github.com/sendgrid/sendgrid-python/labels/difficulty%3A%20easy) diff --git a/README.md b/README.md index 09b0ce68c..4e05596e4 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ pip install sendgrid ## Dependencies - [Python-HTTP-Client](https://github.com/sendgrid/python-http-client) +- [ECDSA-Python](https://github.com/starkbank/ecdsa-python) diff --git a/README.rst b/README.rst index 60be5d2af..b0c73aae2 100644 --- a/README.rst +++ b/README.rst @@ -93,6 +93,7 @@ Dependencies ------------ - `Python-HTTP-Client`_ +- `ECDSA-Python`_ Quick Start =========== @@ -272,6 +273,7 @@ License .. _Twilio account: https://www.twilio.com/try-twilio?source=sendgrid-python .. _SENDGRID_API_KEY: https://app.sendgrid.com/settings/api_keys .. _Python-HTTP-Client: https://github.com/sendgrid/python-http-client +.. _ECDSA-Python: https://github.com/starkbank/ecdsa-python .. _/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/ From 91f4cd89aef7e4856706791c99cde460d26011af Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Wed, 1 Jul 2020 11:15:12 -0500 Subject: [PATCH 292/462] docs: update the Docker test integration docs A top-level Dockerfile is now present which can be used to test this repo with prism. --- CONTRIBUTING.md | 110 +++++---------------- docker-test/Dockerfile | 20 ---- docker-test/README.md | 47 --------- docker-test/entrypoint.sh | 17 ---- docker-test/prism.sh | 50 ---------- docker/Dockerfile | 52 ---------- docker/Makefile | 20 ---- docker/README.md | 63 ------------ docker/USAGE.md | 134 -------------------------- docker/docker-compose.yml | 35 ------- docker/entrypoint.sh | 48 --------- docker/env/python-dev/sendgrid-python | 1 - docker/sendgrid.env | 8 -- 13 files changed, 23 insertions(+), 582 deletions(-) delete mode 100644 docker-test/Dockerfile delete mode 100644 docker-test/README.md delete mode 100755 docker-test/entrypoint.sh delete mode 100755 docker-test/prism.sh delete mode 100644 docker/Dockerfile delete mode 100644 docker/Makefile delete mode 100644 docker/README.md delete mode 100644 docker/USAGE.md delete mode 100644 docker/docker-compose.yml delete mode 100755 docker/entrypoint.sh delete mode 160000 docker/env/python-dev/sendgrid-python delete mode 100644 docker/sendgrid.env diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 306443d08..ed57a5722 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,22 +7,14 @@ All third party contributors acknowledge that any contributions they provide wil - [Please use our Bug Report Template](#please-use-our-bug-report-template) - [Improvements to the Codebase](#improvements-to-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) + - [Prerequisites](#prerequisites) + - [Initial setup](#initial-setup) + - [Environment Variables](#environment-variables) + - [Execute:](#execute) - [Understanding the Code Base](#understanding-the-code-base) - [Testing](#testing) - - [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) +- [Creating a Pull Request](#creating-a-pull-request) - [Code Reviews](#code-reviews) @@ -30,7 +22,6 @@ We use [Milestones](https://github.com/sendgrid/sendgrid-python/milestones) to h There are a few ways to contribute, which we'll enumerate below: - ## Feature Request If you'd like to make a feature request, please read this section. @@ -40,7 +31,6 @@ The GitHub issue tracker is the preferred channel for library feature requests, - Please **search for existing issues** in order to ensure we don't have duplicate bugs/feature requests. - Please be respectful and considerate of others when commenting on issues - ## Submit a Bug Report Note: DO NOT include your credentials in ANY code examples, descriptions, or media you make public. @@ -57,34 +47,28 @@ Before you decide to create a new issue, please try the following: In order to make the process easier, we've included a [sample bug report template](ISSUE_TEMPLATE.md). - ## Improvements to the Codebase 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. -You can use our Docker image to avoid setting up the development environment yourself. See [USAGE.md](https://github.com/sendgrid/sendgrid-python/blob/master/docker/USAGE.md). +### Development Environment -#### - OR - #### -#### 2. Install and Run Locally #### +#### Prerequisites -##### Prerequisites ##### - -- Python 2.7 and 3.4+ +- Python version 2.7, 3.5, 3.6, 3.7, or 3.8 - [python_http_client](https://github.com/sendgrid/python-http-client) - [ecdsa_python](https://github.com/starkbank/ecdsa-python) +- [pyenv](https://github.com/yyuu/pyenv) +- [tox](https://pypi.python.org/pypi/tox) -##### Initial setup: ##### +#### Initial setup ```bash git clone https://github.com/sendgrid/sendgrid-python.git cd sendgrid-python ``` -### Environment Variables +#### Environment Variables First, get your free Twilio SendGrid account [here](https://sendgrid.com/free?source=sendgrid-python). @@ -101,78 +85,31 @@ Then edit `.env` and insert your API key. source .env ``` -##### Execute: ##### +#### Execute See the [examples folder](https://github.com/sendgrid/sendgrid-python/tree/master/examples) to get started quickly. If testing from the root directory of this repo, create a new file (e.g. test.py) and replace `import sendgrid` with `from sendgrid import *` - ## Understanding the Code Base -**/examples** - -Working examples that demonstrate usage. - -**/tests** - -Currently, we have unit and profiling tests. +- **/examples** + - Working examples that demonstrate usage. +- **/tests** + - Currently, we have unit and profiling tests. +- **/sendgrid** + - The Web API v3 client is `sendgrid.py`, the other files are legacy code for our mail send v2 endpoint. -**/sendgrid** - -The Web API v3 client is `sendgrid.py`, the other files are legacy code for our mail send v2 endpoint. - - ## Testing The PR must pass all the tests before it is reviewed. -All test files are in the [`test`](https://github.com/sendgrid/sendgrid-python/test) directory. - -For the purposes of contributing to this repo, please update the [`test_sendgrid.py`](https://github.com/sendgrid/sendgrid-python/tree/master/test/test_sendgrid.py) file with unit tests as you modify the code. - -`python -m unittest discover -v` - -### Testing Multiple Versions of Python - -The PR must pass all the tests before it is reviewed. - -#### Prerequisites: #### - -The above local "Initial setup" is complete +All test files are in the [`test`](https://github.com/sendgrid/sendgrid-python/test) directory. For the purposes of contributing to this repo, please update the [`test_sendgrid.py`](https://github.com/sendgrid/sendgrid-python/tree/master/test/test_sendgrid.py) file with unit tests as you modify the code. -* [pyenv](https://github.com/yyuu/pyenv) -* [tox](https://pypi.python.org/pypi/tox) -* [prism](https://github.com/stoplightio/prism) v0.6 - It should be available in your PATH, but unittest script -will try to install it locally if not found. Apart from PATH env variable it will check in `~/bin` and `/usr/local/bin`. -You can install it by yourself in user dir by calling `source test/prism.sh`. +The integration tests require a Twilio SendGrid mock API in order to execute. We've simplified setting this up using Docker to run the tests. You will just need [Docker Desktop](https://docs.docker.com/get-docker/) and `make`. -#### Initial setup: #### - -Add ```eval "$(pyenv init -)"``` to your shell environment (.profile, .bashrc, etc) after installing tox, you only need to do this once. - -``` -pyenv install 2.7.11 -pyenv install 3.4.3 -pyenv install 3.5.0 -``` -Make sure to change the current working directory to your local version of the repo before running the following command: -``` -python setup.py install -``` -``` -pyenv local 3.5.0 3.4.3 2.7.11 -pyenv rehash -``` - -#### Execute: #### - -``` -source venv/bin/activate -tox -``` +Once these are available, simply execute the Docker test target to run all tests: `make test-docker`. This command can also be used to open an interactive shell into the container where this library is installed. To start a *bash* shell for example, use this command: `command=bash make test-docker`. - ## Style Guidelines & Naming Conventions Generally, we follow the style guidelines as suggested by the official language. However, we ask that you conform to the styles that already exist in the library. If you wish to deviate, please explain your reasoning. @@ -185,7 +122,7 @@ Please run your code through: - [pylint](https://www.pylint.org/) - [pep8](https://pypi.python.org/pypi/pep8) -## Creating a Pull Request +## Creating a Pull Request 1. [Fork](https://help.github.com/fork-a-repo/) the project, clone your fork, and configure the remotes: @@ -240,6 +177,5 @@ Please run your code through: 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) with a clear title and description against the `master` branch. All tests must be passing before we will review the PR. - ## 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/). diff --git a/docker-test/Dockerfile b/docker-test/Dockerfile deleted file mode 100644 index d53ac6512..000000000 --- a/docker-test/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -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 --update --no-cache bash curl - -# Install Prism -ADD prism.sh install.sh -RUN sync && bash install.sh - -# Set up default Twilio 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 deleted file mode 100644 index e3163b65d..000000000 --- a/docker-test/README.md +++ /dev/null @@ -1,47 +0,0 @@ -Use Docker to easily test the sendgrid-python library. - -This Docker image contains: - - Python 3.6 - - A running instance of [Stoplight.io's Prism](https://stoplight.io/platform/prism/), which lets you try out the SendGrid API without actually sending email - - A mirrored copy of sendgrid-php so that you may develop locally and then run the tests within the Docker container. - -# Table of Contents - -* [Quick Start](#quick-start) -* [Testing](#testing) -* [Contributing](#contributing) - - -# Quick Start - -1. Clone the sendgrid-python repo - - `git clone https://github.com/sendgrid/sendgrid-python.git` - - `cd sendgrid-python` - - `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 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 - - `sudo docker exec -it sendgrid_python3.6 /bin/bash -c 'cd sendgrid-python; python3.6 -m unittest discover -v; exec "${SHELL:-sh}"'` - -Now you can continue development locally, and run `python3.6 -m unittest discover -v` inside of the container to test. - -To clean up the container: `docker stop sendgrid_python3.6 && docker rm sendgrid_python3.6`. - -Happy Hacking! - - -# For Testing the Library (Kick the Tires) - -- After step 5 in the QuickStart, within the Docker container: - - `cd ../` - - `python sendmail.py` - - -# For Contributors - -- Develop per usual locally, but before pushing up to GitHub, you can run the tests locally in the Docker container per step 5 of the quickstart. -- To run all the tests: `python3.6 -m unittest discover -v` -- To run an individual test: `python3.6 -m unittest [Filename].[Class].[TestName]` diff --git a/docker-test/entrypoint.sh b/docker-test/entrypoint.sh deleted file mode 100755 index f41382281..000000000 --- a/docker-test/entrypoint.sh +++ /dev/null @@ -1,17 +0,0 @@ -#! /bin/bash -clear - -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/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/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 python-http-client pytest -exec $SHELL diff --git a/docker-test/prism.sh b/docker-test/prism.sh deleted file mode 100755 index 46acce8c0..000000000 --- a/docker-test/prism.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/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 deleted file mode 100644 index cf2d36b6b..000000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,52 +0,0 @@ -FROM ubuntu:xenial -ENV PYTHON_VERSIONS='python2.6 python2.7 python3.4 python3.5 python3.6' \ - OAI_SPEC_URL="https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/oai_stoplight.json" - -ARG SENDGRID-PYTHON_VERSION -ARG BRANCH_HTTP_CLIENT - -# install testing versions of python, including old versions, from deadsnakes -RUN set -x \ - && apt-get update \ - && apt-get install -y --no-install-recommends software-properties-common \ - && apt-add-repository -y ppa:fkrull/deadsnakes \ - && apt-get update \ - && apt-get install -y --no-install-recommends $PYTHON_VERSIONS \ - git \ - curl \ - && apt-get purge -y --auto-remove software-properties-common \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /root - -# install Prism -ADD https://raw.githubusercontent.com/stoplightio/prism/master/install.sh install.sh -RUN chmod +x ./install.sh && sync && \ - ./install.sh && \ - rm ./install.sh - -# install pip, tox -ADD https://bootstrap.pypa.io/get-pip.py get-pip.py -RUN python2.7 get-pip.py && \ - 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 - -# set up default sendgrid env -WORKDIR /root/sources -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 - -COPY entrypoint.sh entrypoint.sh -RUN chmod +x entrypoint.sh -ENTRYPOINT ["./entrypoint.sh"] -CMD ["--mock"] diff --git a/docker/Makefile b/docker/Makefile deleted file mode 100644 index 76ccb73af..000000000 --- a/docker/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100644 index d1a187d31..000000000 --- a/docker/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# Supported tags and respective `Dockerfile` links - - `v6.1.0`, `latest` [(Dockerfile)](https://github.com/sendgrid/sendgrid-python/blob/master/docker/Dockerfile) - - `v6.0.5` - - `v6.0.3` - - `v6.0.0` - - `v5.6.0` - - `v5.5.0` - - `v5.4.1` - - `v5.4.0` - - `v5.3.0` - - `v5.2.1` - - `v5.2.0` - - `v5.1.0` - - `v5.0.1` - - `v5.0.0` - - `v4.2.1` - - `v4.2.0` - - `v4.1.0` - - `v4.0.0` - - `v3.6.5` - - `v3.6.4` - - `v3.6.3` - - `v3.6.2` - - `v3.3.0` - - `v3.2.3` - - `v3.2.2` - - `v3.2.1` - - `v3.2.0` - -# Quick reference - - **Where to get help:** - [Contact Twilio SendGrid Support](https://support.sendgrid.com/hc/en-us) - - - **Where to file issues:** - https://github.com/sendgrid/sendgrid-python/issues - - - **Where to get more info:** - [USAGE.md](https://github.com/sendgrid/sendgrid-python/blob/master/docker/USAGE.md) - - - **Maintained by:** - [Twilio SendGrid Inc.](https://sendgrid.com) - -# Usage examples - - Most recent version: `docker run -it sendgrid/sendgrid-python`. - - Old version: `docker run -it sendgrid/sendgrid-python:v4.2.0` - - Old version predating this Docker image: - ```sh-session - $ git clone https://github.com/sendgrid/sendgrid-python.git --branch v3.6.1 - $ realpath sendgrid-python - /path/to/sendgrid-python - $ docker run -it -v /path/to/sendgrid-python:/mnt/sendgrid-python sendgrid/sendgrid-python - ``` - - Your own fork: - ```sh-session - $ git clone https://github.com/you/cool-sendgrid-python.git - $ realpath cool-sendgrid-python - /path/to/cool-sendgrid-python - $ docker run -it -v /path/to/cool-sendgrid-python:/mnt/sendgrid-python sendgrid/sendgrid-python - ``` - -For more detailed information, see [USAGE.md](https://github.com/sendgrid/sendgrid-python/blob/master/docker/USAGE.md). - -![Twilio SendGrid Logo](https://sendgrid.com/brand/sg-twilio/SG_Twilio_Lockup_RGBx1.png) diff --git a/docker/USAGE.md b/docker/USAGE.md deleted file mode 100644 index 77a106c9b..000000000 --- a/docker/USAGE.md +++ /dev/null @@ -1,134 +0,0 @@ -You can use Docker to easily try out or test sendgrid-python. - - -# Quickstart - -1. Install Docker on your machine. -2. Run `docker run -it sendgrid/sendgrid-python`. - - -# Info - -This Docker image contains - - `sendgrid-python` and `python-http-client` - - Stoplight's Prism, which lets you try out the API without actually sending email - - `tox` and all supported Python versions, set up to test `sendgrid-python` or your own fork - -Run it in interactive mode with `-it`. - -You can mount repositories in the `/mnt/sendgrid-python` and `/mnt/python-http-client` directories to use them instead of the default SendGrid libraries. Read on for more info. - - -# Options - -## Using an old version - -The easiest way to use an old version is to use an [old tag](https://github.com/sendgrid/sendgrid-python/releases). - -```sh-session -$ docker run -it sendgrid/sendgrid-python:v3.6.1 -``` - -Tags from before this Docker image was created might not exist yet. You may [manually download](#Versions) [old versions](https://github.com/sendgrid/sendgrid-python/releases) in order to use them. - - -## Specifying specific versions - -To use different versions of sendgrid-python or python-http-client - for instance, to replicate your production setup - mount them with the `-v :` option. When you put either repository under `/mnt`, the container will automatically detect it and make the proper symlinks. You can edit these files from the host machine while the container is running. - -For instance, to install sendgrid-python 3.6.1 and use the current python-http-client: - -```sh-session -$ git clone https://github.com/sendgrid/sendgrid-python.git --branch v3.6.1 -$ realpath sendgrid-python -/path/to/sendgrid-python -$ docker run -it -v /path/to/sendgrid-python:/mnt/sendgrid-python sendgrid/sendgrid-python -``` - -To install sendgrid-python v3.6.1 and use an older version of python-http-client: - -```sh-session -$ git clone https://github.com/sendgrid/sendgrid-python.git --branch v3.6.1 -$ realpath sendgrid-python -/path/to/sendgrid-python -$ git clone https://github.com/sendgrid/python-http-client.git --branch v1.2.4 -$ realpath python-http-client -/path/to/python-http-client -$ docker run -it -v /path/to/sendgrid-python:/mnt/sendgrid-python \ -> -v /path/to/python-http-client:/mnt/python-http-client \ -> sendgrid/sendgrid-python -``` - -## Specifying your own fork: - -```sh-session -$ git clone https://github.com/you/cool-sendgrid-python.git -$ realpath cool-sendgrid-python -/path/to/cool-sendgrid-python -$ docker run -it -v /path/to/cool-sendgrid-python:/mnt/sendgrid-python sendgrid/sendgrid-python -``` - -Note that the paths you specify in `-v` must be absolute. - -# 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`. - -![SendGrid Logo](https://uiux.s3.amazonaws.com/2016-logos/email-logo%402x.png) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index 2a435b39f..000000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,35 +0,0 @@ -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 deleted file mode 100755 index 560d80a35..000000000 --- a/docker/entrypoint.sh +++ /dev/null @@ -1,48 +0,0 @@ -#! /bin/bash -clear - -# check for + link mounted libraries: -if [ -d /mnt/sendgrid-python ] -then - rm /root/sendgrid - ln -s /mnt/sendgrid-python/sendgrid - echo "Linked mounted sendgrid-python's code to /root/sendgrid" -fi -if [ -d /mnt/python_http_client ] -then - rm /root/python_http_client - ln -s /mnt/python-http-client/python_http_client - echo "Linked mounted python-http-client's code to /root/python_http_client" -fi - -SENDGRID_PYTHON_VERSION=$(python2.7 -c 'import sendgrid; print(sendgrid.__version__)') -echo "Welcome to sendgrid-python docker v${SENDGRID_PYTHON_VERSION}." -echo - -if [ "$1" != "--no-mock" ] -then - echo "Starting Prism in mock mode. Calls made to Prism will not actually send emails." - echo "Disable this by running this container with --no-mock." - prism run --mock --spec $OAI_SPEC_URL 2> /dev/null & -else - echo "Starting Prism in live (--no-mock) mode. Calls made to Prism will send emails." - prism run --spec $OAI_SPEC_URL 2> /dev/null & -fi -echo "To use Prism, make API calls to localhost:4010. For example," -echo " sg = sendgrid.SendGridAPIClient(" -echo " host='http://localhost:4010/'," -echo " api_key=os.environ.get('SENDGRID_API_KEY_CAMPAIGNS'))" -echo "To stop Prism, run \"kill $!\" from the shell." - -echo -echo "Starting Python. Type \"import sendgrid\" to get started; return to shell with exit()." -echo - -python2.7 - -echo -echo "To get back into Python, run one of the installed versions:" -echo " $PYTHON_VERSIONS" -echo "To test sendgrid-python, \"cd sendgrid\" and run \"tox\"." -echo -exec $SHELL \ No newline at end of file diff --git a/docker/env/python-dev/sendgrid-python b/docker/env/python-dev/sendgrid-python deleted file mode 160000 index 28cf42f6d..000000000 --- a/docker/env/python-dev/sendgrid-python +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 28cf42f6d590695de7e7ecdedcb67e9d8d4729ac diff --git a/docker/sendgrid.env b/docker/sendgrid.env deleted file mode 100644 index ace58fafa..000000000 --- a/docker/sendgrid.env +++ /dev/null @@ -1,8 +0,0 @@ -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 From df75f6ecc43c79a0a50a1683b602e73f5047d477 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Wed, 1 Jul 2020 11:22:31 -0500 Subject: [PATCH 293/462] fix: drop the silly docker file tests --- test/test_project.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/test/test_project.py b/test/test_project.py index 27d0befb9..79f27972d 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -2,16 +2,7 @@ import unittest - class ProjectTests(unittest.TestCase): - # ./docker - def test_docker_dir(self): - 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/docker-compose.yml')) - # ./.env_sample def test_env(self): self.assertTrue(os.path.isfile('./.env_sample')) From c51b4296d5cdb972c7720713e55bf2b2564895ad Mon Sep 17 00:00:00 2001 From: John Calhoun Date: Tue, 7 Jul 2020 09:24:53 -0700 Subject: [PATCH 294/462] fix: type validation on to_emails parameter on mail object (#920) --- sendgrid/helpers/mail/mail.py | 11 +++-- test/test_mail_helpers.py | 89 +++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 5d9490ba3..ce8bb2f0c 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -37,7 +37,8 @@ def __init__( :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 + :type to_emails: To, str, tuple, list(str), list(tuple), + list(To), optional :param plain_text_content: The plain text body of the email :type plain_text_content: string, optional :param html_content: The html body of the email @@ -239,7 +240,7 @@ def add_to( """Adds a To object to the Personalization object :param to_email: A To object - :type to_email: To, str, tuple + :type to_email: To, str, tuple, list(str), list(tuple), list(To) :param global_substitutions: A dict of substitutions for all recipients :type global_substitutions: dict :param is_multiple: Create a new personalization for each recipient @@ -253,8 +254,12 @@ def add_to( for email in to_email: if isinstance(email, str): email = To(email, None) - if isinstance(email, tuple): + elif isinstance(email, tuple): email = To(email[0], email[1]) + elif not isinstance(email, To): + raise ValueError( + 'Please use a tuple, To, or a str for a to_email list.' + ) self._set_emails(email, global_substitutions, is_multiple, p) else: if isinstance(to_email, str): diff --git a/test/test_mail_helpers.py b/test/test_mail_helpers.py index fd4f3a68f..dff3de5b2 100644 --- a/test/test_mail_helpers.py +++ b/test/test_mail_helpers.py @@ -284,6 +284,95 @@ def test_multiple_emails_to_multiple_recipients(self): }''') ) + def test_value_error_is_raised_on_to_emails_set_to_list_of_lists(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = [ + ['test+to0@example.com', 'Example To Name 0'], + ['test+to1@example.com', 'Example To Name 1'] + ] + + with self.assertRaises(ValueError): + Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + + def test_error_is_not_raised_on_to_emails_set_to_list_of_tuples(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = [ + ('test+to0@example.com', 'Example To Name 0'), + ('test+to1@example.com', 'Example To Name 1') + ] + + try: + 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')) + except: + self.fail('Mail() raised an error on list of tuples') + + def test_error_is_not_raised_on_to_emails_set_to_list_of_strs(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = ['test+to0@example.com', 'test+to1@example.com'] + + try: + 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')) + except: + self.fail('Mail() raised an error on list of strings') + + def test_error_is_not_raised_on_to_emails_set_to_a_str(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = 'test+to0@example.com' + + try: + 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')) + except: + self.fail('Mail() raised an error on a string') + + def test_error_is_not_raised_on_to_emails_set_to_a_tuple(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = ('test+to0@example.com', 'Example To Name 0') + + try: + 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')) + except: + self.fail('Mail() raised an error on a tuple of strings') + def test_dynamic_template_data(self): self.maxDiff = None From 3a69a52d5b35b2c66794b99e3648814bc58c1df0 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 8 Jul 2020 18:32:26 +0000 Subject: [PATCH 295/462] [Librarian] Version Bump --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52e22ddf6..65cf3e0ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-07-08] Version 6.4.2 +-------------------------- +**Library - Fix** +- [PR #920](https://github.com/sendgrid/sendgrid-python/pull/920): type validation on to_emails parameter on mail object. Thanks to [@DougCal](https://github.com/DougCal)! + +**Library - Docs** +- [PR #915](https://github.com/sendgrid/sendgrid-python/pull/915): document change in top-level dependencies. Thanks to [@honzajavorek](https://github.com/honzajavorek)! + + [2020-06-25] Version 6.4.1 -------------------------- **Library - Fix** From f12689c8d2186faef0d8a9a17ccea3419d9e7114 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 8 Jul 2020 18:52:57 +0000 Subject: [PATCH 296/462] Release 6.4.2 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 6594dc237..1c98065b4 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.4.1' +__version__ = '6.4.2' From 50d50d240719a4abb392b8d758ac606f7a1b9aa0 Mon Sep 17 00:00:00 2001 From: Elise Shanholtz Date: Fri, 10 Jul 2020 09:22:39 -0700 Subject: [PATCH 297/462] fix: allow general Email type for to_emails (#921) --- sendgrid/helpers/mail/mail.py | 4 +- test/test_mail_helpers.py | 96 +++++++++++++++++++---------------- 2 files changed, 53 insertions(+), 47 deletions(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index ce8bb2f0c..db2399310 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -256,9 +256,9 @@ def add_to( email = To(email, None) elif isinstance(email, tuple): email = To(email[0], email[1]) - elif not isinstance(email, To): + elif not isinstance(email, Email): raise ValueError( - 'Please use a tuple, To, or a str for a to_email list.' + 'Please use a To/Cc/Bcc, tuple, or a str for a to_email list.' ) self._set_emails(email, global_substitutions, is_multiple, p) else: diff --git a/test/test_mail_helpers.py b/test/test_mail_helpers.py index dff3de5b2..202d3948b 100644 --- a/test/test_mail_helpers.py +++ b/test/test_mail_helpers.py @@ -14,7 +14,7 @@ ClickTracking, Content, DynamicTemplateData, Email, From, Mail, Personalization, - Subject, Substitution, To, TrackingSettings + Subject, Substitution, To, Cc, Bcc, TrackingSettings ) @@ -310,68 +310,74 @@ def test_error_is_not_raised_on_to_emails_set_to_list_of_tuples(self): ('test+to1@example.com', 'Example To Name 1') ] - try: - 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')) - except: - self.fail('Mail() raised an error on list of tuples') + Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) def test_error_is_not_raised_on_to_emails_set_to_list_of_strs(self): from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) self.maxDiff = None to_emails = ['test+to0@example.com', 'test+to1@example.com'] - try: - 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')) - except: - self.fail('Mail() raised an error on list of strings') + Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) def test_error_is_not_raised_on_to_emails_set_to_a_str(self): from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) self.maxDiff = None to_emails = 'test+to0@example.com' - try: - 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')) - except: - self.fail('Mail() raised an error on a string') + Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) def test_error_is_not_raised_on_to_emails_set_to_a_tuple(self): from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) self.maxDiff = None to_emails = ('test+to0@example.com', 'Example To Name 0') - try: - 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')) - except: - self.fail('Mail() raised an error on a tuple of strings') + Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + + def test_error_is_not_raised_on_to_emails_includes_bcc_cc(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = [ + To('test+to0@example.com', 'Example To Name 0'), + Bcc('test+bcc@example.com', 'Example Bcc Name 1'), + Cc('test+cc@example.com', 'Example Cc Name 2') + ] + + Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) def test_dynamic_template_data(self): self.maxDiff = None From ab96abe67c1a389d204f6fe4843c4dcccb192648 Mon Sep 17 00:00:00 2001 From: Twilio Date: Fri, 10 Jul 2020 22:50:30 +0000 Subject: [PATCH 298/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65cf3e0ed..18b4ddc8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-07-10] Version 6.4.3 +-------------------------- +**Library - Fix** +- [PR #921](https://github.com/sendgrid/sendgrid-python/pull/921): allow general email type for to_emails. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + + [2020-07-08] Version 6.4.2 -------------------------- **Library - Fix** From e0a95aeade4e9624bc04bf0fbba4a34fa4ac8388 Mon Sep 17 00:00:00 2001 From: Twilio Date: Fri, 10 Jul 2020 22:54:40 +0000 Subject: [PATCH 299/462] Release 6.4.3 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 1c98065b4..b4818768c 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.4.2' +__version__ = '6.4.3' From 95890f9ccb58e1163d950d87e4204ccba15e55e3 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Mon, 13 Jul 2020 10:24:47 -0500 Subject: [PATCH 300/462] chore: drop the CLA links --- README.md | 1 - README.rst | 2 -- 2 files changed, 3 deletions(-) diff --git a/README.md b/README.md index 4e05596e4..6e453a543 100644 --- a/README.md +++ b/README.md @@ -213,7 +213,6 @@ Quick links: - [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 diff --git a/README.rst b/README.rst index b0c73aae2..8d3b4330b 100644 --- a/README.rst +++ b/README.rst @@ -243,7 +243,6 @@ Quick links: - `Bug Reports`_ - `Improvements to the Codebase`_ - `Review Pull Requests`_ -- `Sign the CLA to Create a Pull Request`_ Troubleshooting =============== @@ -294,7 +293,6 @@ License .. _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 .. _The MIT License (MIT): https://github.com/sendgrid/sendgrid-python/blob/master/LICENSE.md From 27ad7692d2c88cf2316574247728afab2306658e Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Mon, 13 Jul 2020 10:58:42 -0500 Subject: [PATCH 301/462] chore: drop the local Docker setup link --- FIRST_TIMERS.md | 104 +++++++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 54 deletions(-) diff --git a/FIRST_TIMERS.md b/FIRST_TIMERS.md index f6d9e4192..b0636fb81 100644 --- a/FIRST_TIMERS.md +++ b/FIRST_TIMERS.md @@ -1,73 +1,69 @@ ## Welcome to the Twilio 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 Twilio 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 Twilio SendGrid -Step 1: Get yourself Access to Twilio SendGrid API Service absolutely free from [here](https://sendgrid.com/free/?source=sendgrid-python)\ +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 Twilio 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 Twilio SendGrid +Step 1: Get yourself Access to Twilio SendGrid API Service absolutely free from [here](https://sendgrid.com/free/?source=sendgrid-python) \ Step 2: Get familiar with Twilio SendGrid Service - - Prerequisites are Python version 2.6, 2.7, 3.4, 3.5 or 3.6 - - Set up your [Twilio 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 Twilio 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 - Twilio 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\ +- Prerequisites are Python version 2.6, 2.7, 3.4, 3.5 or 3.6 +- Set up your [Twilio 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 Twilio 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 +Twilio 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\ +- 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 + - 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. + 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) and both [python_http_client](https://github.com/sendgrid/python-http-client) and [ecdsa_python](https://github.com/starkbank/ecdsa-python)\ - Step 2: Get a local copy of repository using `git clone https://github.com/sendgrid/sendgrid-python.git`\ - Step 3: Set your [Twilio SendGrid API Key](https://app.sendgrid.com/settings/api_keys) to your local workspace using\ + +### Setting up the Development Environment +- **Setting up Locally** + - Step 1: Install the Prerequistes: Any Version of Python (2.6 through 3.6) and both [python_http_client](https://github.com/sendgrid/python-http-client) and [ecdsa_python](https://github.com/starkbank/ecdsa-python) + - Step 2: Get a local copy of repository using `git clone https://github.com/sendgrid/sendgrid-python.git` + - Step 3: Set your [Twilio 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*. - + `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` + +## 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 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 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 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 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 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 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. +**Step 8:** Open a Pull Request with clear title and description against the master branch. - - ## Be Patient & Wait for Reviews - Kindly be patient & follow the suggestions as provided by the peer reviewers. Make required amendments & changes to the PR as asked. + +## Be Patient & Wait for Reviews +Kindly be patient & follow the suggestions as provided by the peer reviewers. Make required amendments & changes to the PR as asked. ## [Explore New Issues to work upon](https://github.com/sendgrid/sendgrid-python/labels/difficulty%3A%20easy) From ab706ce95e84151f4ad966cc1c8876a105381012 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Mon, 13 Jul 2020 11:01:32 -0500 Subject: [PATCH 302/462] chore: drop the local Docker setup links --- README.md | 5 ----- README.rst | 2 -- 2 files changed, 7 deletions(-) diff --git a/README.md b/README.md index 6e453a543..9bc604525 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,6 @@ [![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). diff --git a/README.rst b/README.rst index 8d3b4330b..5a2ef9898 100644 --- a/README.rst +++ b/README.rst @@ -11,7 +11,6 @@ - Subscribe to email `notifications`_ for releases and breaking changes. - Version 6.X release is a BREAKING CHANGE from version 5.X, please see the `release notes`_ for details. -- Quickly get started with `Docker`_. - Send SMS messages with `Twilio`_. This library provides full support for all Twilio SendGrid `Web API v3`_ endpoints, including `v3 /mail/send`_. @@ -261,7 +260,6 @@ License `The MIT License (MIT)`_ .. _notifications: https://dx.sendgrid.com/newsletter/python -.. _Docker: https://github.com/sendgrid/sendgrid-python/tree/master/docker .. _Twilio: https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/sms.md .. _release notes: https://github.com/sendgrid/sendgrid-python/releases/tag/v6.0.0 .. _Web API v3: https://sendgrid.com/docs/API_Reference/Web_API_v3/index.html From 03996081ae0a152801a47ad5a452b679622323d8 Mon Sep 17 00:00:00 2001 From: Elise Shanholtz Date: Mon, 20 Jul 2020 15:33:37 -0700 Subject: [PATCH 303/462] chore: migrate to new default sendgrid-oai branch (#925) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 356d46682..b176be0a1 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ test-integ: test version ?= latest test-docker: - curl -s https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/prism/prism.sh -o prism.sh + curl -s https://raw.githubusercontent.com/sendgrid/sendgrid-oai/main/prism/prism.sh -o prism.sh version=$(version) bash ./prism.sh clean: nopyc From b3b88d7b409f67a120d5700beade2c09c66d276a Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 22 Jul 2020 19:00:03 +0000 Subject: [PATCH 304/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18b4ddc8d..94ea6ef9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-07-22] Version 6.4.4 +-------------------------- +**Library - Chore** +- [PR #925](https://github.com/sendgrid/sendgrid-python/pull/925): migrate to new default sendgrid-oai branch. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + + [2020-07-10] Version 6.4.3 -------------------------- **Library - Fix** From 2e6f33fd1d731c6f91755f3cf18deab9fccc263e Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 22 Jul 2020 19:19:33 +0000 Subject: [PATCH 305/462] Release 6.4.4 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index b4818768c..e55bc1c67 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.4.3' +__version__ = '6.4.4' From f010ca8d52e697d7f74274438062e7cd8778b7ca Mon Sep 17 00:00:00 2001 From: Elise Shanholtz Date: Wed, 22 Jul 2020 14:33:44 -0700 Subject: [PATCH 306/462] chore: use HEAD to refer to default branch name --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b176be0a1..620a25993 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ test-integ: test version ?= latest test-docker: - curl -s https://raw.githubusercontent.com/sendgrid/sendgrid-oai/main/prism/prism.sh -o prism.sh + curl -s https://raw.githubusercontent.com/sendgrid/sendgrid-oai/HEAD/prism/prism.sh -o prism.sh version=$(version) bash ./prism.sh clean: nopyc From d7ee47b56b171e82c20e4dfd9ae5a3f79eb71a85 Mon Sep 17 00:00:00 2001 From: childish-sambino Date: Fri, 24 Jul 2020 11:54:56 -0500 Subject: [PATCH 307/462] docs: remove last references of "whitelabel" (#926) * Whitelabel = Sender Authentication * Domain Whitelabel = Domain Authentication * IP Whitelabel = Reverse DNS * Link Whitelabel = Link branding --- USAGE.md | 2073 +++++++++-------- .../senderauthentication.py | 54 +- use_cases/domain_authentication.md | 2 +- 3 files changed, 1066 insertions(+), 1063 deletions(-) diff --git a/USAGE.md b/USAGE.md index 560daea71..b6d044676 100644 --- a/USAGE.md +++ b/USAGE.md @@ -32,14 +32,13 @@ sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) * [PARTNER SETTINGS](#partner-settings) * [SCOPES](#scopes) * [SENDERS](#senders) +* [SENDER AUTHENTICATION](#sender-authentication) * [STATS](#stats) * [SUBUSERS](#subusers) * [SUPPRESSION](#suppression) * [TEMPLATES](#templates) * [TRACKING SETTINGS](#tracking-settings) * [USER](#user) -* [WHITELABEL](#whitelabel) - # ACCESS SETTINGS @@ -1785,7 +1784,7 @@ print(response.headers) **This endpoint allows you to retrieve a list of all assigned and unassigned IPs.** -The response includes warm-up status, pools, assigned subusers, and whitelabel info. The start_date field corresponds to when warmup started for that IP. +The response includes warm-up status, pools, assigned subusers, and authentication info. The start_date field corresponds to when warmup started for that IP. A single IP address or a range of IP addresses may be dedicated to an account in order to send email for multiple domains. The reputation of this IP is based on the aggregate performance of all the senders who use it. @@ -2854,2184 +2853,2188 @@ print(response.status_code) print(response.body) print(response.headers) ``` - -# STATS - -## Retrieve global email statistics - -**This endpoint allows you to retrieve all of your global email statistics between a given date range.** - -Parent accounts will see aggregated stats for their account and all subuser accounts. Subuser accounts will only see their own stats. - -### GET /stats + +# SENDER AUTHENTICATION -```python -params = {'aggregated_by': 'day', 'limit': 1, 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 1} -response = sg.client.stats.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) -``` - -# SUBUSERS +## Create an authenticated domain. -## Create Subuser +**This endpoint allows you to create a domain authentication for one of your domains.** -This endpoint allows you to retrieve a list of all of your subusers. You can choose to retrieve specific subusers as well as limit the results that come back from the API. +If you are creating a domain authentication that you would like a subuser to use, you have two options: +1. Use the "username" parameter. This allows you to create a domain authentication on behalf of your subuser. This means the subuser is able to see and modify the created authentication. +2. Use the Association workflow (see Associate Domain section). This allows you to assign a domain authentication created by the parent to a subuser. This means the subuser will default to the assigned domain authentication, but will not be able to see or modify that authentication. However, if the subuser creates their own domain authentication it will overwrite the assigned domain authentication. -For more information about Subusers: +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) -* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -### POST /subusers +### POST /whitelabel/domains ```python data = { - "email": "John@example.com", + "automatic_security": False, + "custom_spf": True, + "default": True, + "domain": "example.com", "ips": [ - "1.1.1.1", - "2.2.2.2" + "192.168.1.1", + "192.168.1.2" ], - "password": "johns_password", - "username": "John@example.com" + "subdomain": "news", + "username": "john@example.com" } -response = sg.client.subusers.post(request_body=data) +response = sg.client.whitelabel.domains.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## List all Subusers - -This endpoint allows you to retrieve a list of all of your subusers. You can choose to retrieve specific subusers as well as limit the results that come back from the API. - -For more information about Subusers: - -* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) -* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) - -### GET /subusers +## List all domain authentications. +**This endpoint allows you to retrieve a list of all domain authentications you have created.** -```python -params = {'username': 'test_string', 'limit': 1, 'offset': 1} -response = sg.client.subusers.get(query_params=params) -print(response.status_code) -print(response.body) -print(response.headers) -``` -## Retrieve Subuser Reputations +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -Subuser sender reputations give a good idea how well a sender is doing with regards to how recipients and recipient servers react to the mail that is being received. When a bounce, spam report, or other negative action happens on a sent email, it will effect your sender rating. +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -This endpoint allows you to request the reputations for your subusers. -### GET /subusers/reputations +### GET /whitelabel/domains ```python -params = {'usernames': 'test_string'} -response = sg.client.subusers.reputations.get(query_params=params) +params = {'username': 'test_string', 'domain': 'test_string', 'exclude_subusers': 'true', 'limit': 1, 'offset': 1} +response = sg.client.whitelabel.domains.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve email statistics for your subusers. +## Get the default domain authentication. -**This endpoint allows you to retrieve the email statistics for the given subusers.** +**This endpoint allows you to retrieve the default default authentication for a domain.** -You may retrieve statistics for up to 10 different subusers by including an additional _subusers_ parameter for each additional subuser. +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| domain | string |The domain to find a default domain authentication for. | -### GET /subusers/stats +### GET /whitelabel/domains/default ```python -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'offset': 1, 'start_date': '2016-01-01', 'subusers': 'test_string'} -response = sg.client.subusers.stats.get(query_params=params) +response = sg.client.whitelabel.domains.default.get() print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve monthly stats for all subusers +## List the domain authentication associated with the given user. -**This endpoint allows you to retrieve the monthly email statistics for all subusers over the given date range.** +**This endpoint allows you to retrieve all of the domain authentications that have been assigned to a specific subuser.** -While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats for your subusers. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -When using the `sort_by_metric` to sort your stats by a specific metric, you can not sort by the following metrics: -`bounce_drops`, `deferred`, `invalid_emails`, `processed`, `spam_report_drops`, `spam_reports`, or `unsubscribe_drops`. +Domain authentications can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's authenticated domains. To associate a domain authentication with a subuser, the parent account must first create the default authentication and validate it. The parent may then associate the default authentication via the subuser management tools. -For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -### GET /subusers/stats/monthly +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| username | string | Username of the subuser to find associated domain authentications for. | + +### GET /whitelabel/domains/subuser ```python -params = {'subuser': 'test_string', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'date': 'test_string', 'sort_by_direction': 'asc'} -response = sg.client.subusers.stats.monthly.get(query_params=params) +response = sg.client.whitelabel.domains.subuser.get() print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve the totals for each email statistic metric for all subusers. +## Disassociate a domain authentication from a given user. -**This endpoint allows you to retrieve the total sums of each email statistic metric for all subusers over the given date range.** +**This endpoint allows you to disassociate a specific default authentication from a subuser.** +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. +Domain authentications can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's authenticated domains. To associate a domain authentication with a subuser, the parent account must first create the default authentication and validate it. The parent may then associate the default authentication via the subuser management tools. -For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -### GET /subusers/stats/sums +## URI Parameters +| URI Parameter | Type | Required? | Description | +|---|---|---|---| +| username | string | required | Username for the subuser to find associated domain authentications for. | + +### DELETE /whitelabel/domains/subuser ```python -params = {'end_date': '2016-04-01', 'aggregated_by': 'day', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1, 'start_date': '2016-01-01', 'sort_by_direction': 'asc'} -response = sg.client.subusers.stats.sums.get(query_params=params) +response = sg.client.whitelabel.domains.subuser.delete() print(response.status_code) print(response.body) print(response.headers) ``` -## Enable/disable a subuser +## Update a domain authentication. -This endpoint allows you to enable or disable a subuser. +**This endpoint allows you to update the settings for a domain authentication.** -For more information about Subusers: +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) -* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -### PATCH /subusers/{subuser_name} +### PATCH /whitelabel/domains/{domain_id} ```python data = { - "disabled": False + "custom_spf": True, + "default": False } -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).patch(request_body=data) +domain_id = "test_url_param" +response = sg.client.whitelabel.domains._(domain_id).patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Delete a subuser +## Retrieve a domain authentication. -This endpoint allows you to delete a subuser. This is a permanent action, once you delete a subuser it cannot be retrieved. +**This endpoint allows you to retrieve a specific domain authentication.** -For more information about Subusers: +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) -* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -### DELETE /subusers/{subuser_name} + +### GET /whitelabel/domains/{domain_id} ```python -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).delete() +domain_id = "test_url_param" +response = sg.client.whitelabel.domains._(domain_id).get() print(response.status_code) print(response.body) print(response.headers) ``` -## Update IPs assigned to a subuser +## Delete a domain authentication. -Each subuser should be assigned to an IP address, from which all of this subuser's mail will be sent. Often, this is the same IP as the parent account, but each subuser can have their own, or multiple, IP addresses as well. +**This endpoint allows you to delete a domain authentication.** -More information: +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. -* [How to request more IPs](https://sendgrid.com/docs/Classroom/Basics/Account/adding_an_additional_dedicated_ip_to_your_account.html) -* [IPs can be whitelabeled](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/ips.html) +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) -### PUT /subusers/{subuser_name}/ips +### DELETE /whitelabel/domains/{domain_id} ```python -data = [ - "127.0.0.1" -] -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).ips.put(request_body=data) +domain_id = "test_url_param" +response = sg.client.whitelabel.domains._(domain_id).delete() print(response.status_code) print(response.body) print(response.headers) ``` -## Update Monitor Settings for a subuser +## Associate a domain authentication with a given user. -Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. +**This endpoint allows you to associate a specific domain authentication with a subuser.** -### PUT /subusers/{subuser_name}/monitor +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +Domain authentications can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's authenticated domains. To associate a domain authentication with a subuser, the parent account must first create the default authentication and validate it. The parent may then associate the default authentication via the subuser management tools. + +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) + +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| domain_id | integer | ID of the domain authentication to associate with the subuser. | + +### POST /whitelabel/domains/{domain_id}/subuser ```python data = { - "email": "example@example.com", - "frequency": 500 + "username": "jane@example.com" } -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.put(request_body=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) ``` -## Create monitor settings +## Add an IP to a domain authentication. -Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. +**This endpoint allows you to add an IP address to a domain authentication.** -### POST /subusers/{subuser_name}/monitor +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) + +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| id | integer | ID of the domain to which you are adding an IP | + +### POST /whitelabel/domains/{id}/ips ```python data = { - "email": "example@example.com", - "frequency": 50000 + "ip": "192.168.0.1" } -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.post(request_body=data) +id = "test_url_param" +response = sg.client.whitelabel.domains._(id).ips.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve monitor settings for a subuser +## Remove an IP from a domain authentication. -Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. +**This endpoint allows you to remove a domain's IP address from that domain's authentication.** -### GET /subusers/{subuser_name}/monitor +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) + +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| id | integer | ID of the domain authentication to delete the IP from. | +| ip | string | IP to remove from the domain authentication. | + +### DELETE /whitelabel/domains/{id}/ips/{ip} ```python -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.get() +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) ``` -## Delete monitor settings +## Validate a domain authentication. -Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. +**This endpoint allows you to validate a domain authentication. If it fails, it will return an error message describing why the default authentication could not be validated.** -### DELETE /subusers/{subuser_name}/monitor +A domain authentication allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Authenticating a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. + +For more information on domain authentication, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) + +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| id | integer |ID of the domain authentication to validate. | + +### POST /whitelabel/domains/{id}/validate ```python -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).monitor.delete() +id = "test_url_param" +response = sg.client.whitelabel.domains._(id).validate.post() print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve the monthly email statistics for a single subuser -**This endpoint allows you to retrieve the monthly email statistics for a specific subuser.** +## Create reverse DNS record -While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats for your subusers. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. +**This endpoint allows you to create a reverse DNS record.** -When using the `sort_by_metric` to sort your stats by a specific metric, you can not sort by the following metrics: -`bounce_drops`, `deferred`, `invalid_emails`, `processed`, `spam_report_drops`, `spam_reports`, or `unsubscribe_drops`. +When creating a reverse DNS record, you should use the same subdomain that you used when you created a domain authentication. -For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). +Reverse DNS consists of a subdomain and domain that will be used to generate a record for a given IP. Once Twilio SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. -### GET /subusers/{subuser_name}/stats/monthly +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-reverse-dns/). + +### POST /whitelabel/ips ```python -params = {'date': 'test_string', 'sort_by_direction': 'asc', 'limit': 1, 'sort_by_metric': 'test_string', 'offset': 1} -subuser_name = "test_url_param" -response = sg.client.subusers._(subuser_name).stats.monthly.get(query_params=params) +data = { + "domain": "example.com", + "ip": "192.168.1.1", + "subdomain": "email" +} +response = sg.client.whitelabel.ips.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` - -# SUPPRESSION -## Retrieve all blocks +## Retrieve all reverse DNS records -**This endpoint allows you to retrieve a list of all email addresses that are currently on your blocks list.** +**This endpoint allows you to retrieve all of the reverse DNS records that have been created by this account.** -[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. +You may include a search key by using the "ip" parameter. This enables you to perform a prefix search for a given IP segment (e.g. "192."). -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). +Reverse DNS consists of a subdomain and domain that will be used to generate a record for a given IP. Once Twilio SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. -### GET /suppression/blocks +For more information, please see our [User Guide](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-reverse-dns/). + +### GET /whitelabel/ips ```python -params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} -response = sg.client.suppression.blocks.get(query_params=params) +params = {'ip': 'test_string', 'limit': 1, 'offset': 1} +response = sg.client.whitelabel.ips.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) ``` -## Delete blocks - -**This endpoint allows you to delete all email addresses on your blocks list.** +## Retrieve a reverse DNS record -There are two options for deleting blocked emails: +**This endpoint allows you to retrieve a reverse DNS record.** -1. You can delete all blocked emails by setting `delete_all` to true in the request body. -2. You can delete some blocked emails by specifying the email addresses in an array in the request body. +A reverse DNS record consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once Twilio SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. -[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). - -### DELETE /suppression/blocks +### GET /whitelabel/ips/{id} ```python -data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] -} -response = sg.client.suppression.blocks.delete(request_body=data) +id = "test_url_param" +response = sg.client.whitelabel.ips._(id).get() print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve a specific block +## Delete a reverse DNS record -**This endpoint allows you to retrieve a specific email address from your blocks list.** +**This endpoint allows you to delete a reverse DNS record.** -[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. +A reverse DNS record consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once Twilio SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). -### GET /suppression/blocks/{email} +### DELETE /whitelabel/ips/{id} ```python -email = "test_url_param" -response = sg.client.suppression.blocks._(email).get() +id = "test_url_param" +response = sg.client.whitelabel.ips._(id).delete() print(response.status_code) print(response.body) print(response.headers) ``` -## Delete a specific block +## Validate a reverse DNS record -**This endpoint allows you to delete a specific email address from your blocks list.** +**This endpoint allows you to validate a reverse DNS record.** -[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. +A reverse DNS record consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once Twilio SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). -### DELETE /suppression/blocks/{email} +### POST /whitelabel/ips/{id}/validate ```python -email = "test_url_param" -response = sg.client.suppression.blocks._(email).delete() +id = "test_url_param" +response = sg.client.whitelabel.ips._(id).validate.post() print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve all bounces - -**This endpoint allows you to retrieve all of your bounces.** +## Create a Link Branding -Bounces are messages that are returned to the server that sent it. +**This endpoint allows you to create a new link branding.** -For more information see: +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information -* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### GET /suppression/bounces +### POST /whitelabel/links ```python -params = {'start_time': 1, 'end_time': 1} -response = sg.client.suppression.bounces.get(query_params=params) +data = { + "default": True, + "domain": "example.com", + "subdomain": "mail" +} +params = {'limit': 1, 'offset': 1} +response = sg.client.whitelabel.links.post(request_body=data, query_params=params) print(response.status_code) print(response.body) print(response.headers) ``` -## Delete bounces - -**This endpoint allows you to delete all of your bounces. You can also use this endpoint to remove a specific email address from your bounce list.** - -Bounces are messages that are returned to the server that sent it. +## Retrieve all link brandings -For more information see: +**This endpoint allows you to retrieve all link brandings.** -* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information -* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) -* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -Note: the `delete_all` and `emails` parameters should be used independently of each other as they have different purposes. +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### DELETE /suppression/bounces +### GET /whitelabel/links ```python -data = { - "delete_all": True, - "emails": [ - "example@example.com", - "example2@example.com" - ] -} -response = sg.client.suppression.bounces.delete(request_body=data) +params = {'limit': 1} +response = sg.client.whitelabel.links.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve a Bounce +## Retrieve a Default Link Branding -**This endpoint allows you to retrieve a specific bounce for a given email address.** +**This endpoint allows you to retrieve the default link branding.** -Bounces are messages that are returned to the server that sent it. +Default link branding is the actual link branding to be used when sending messages. If there are multiple link brandings, the default is determined by the following order: +
    +
  • Validated link brandings marked as "default"
  • +
  • Legacy link brands (migrated from the whitelabel wizard)
  • +
  • Default Twilio SendGrid link branding (i.e. 100.ct.sendgrid.net)
  • +
-For more information see: +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information -* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) -* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### GET /suppression/bounces/{email} +### GET /whitelabel/links/default ```python -email = "test_url_param" -response = sg.client.suppression.bounces._(email).get() +params = {'domain': 'test_string'} +response = sg.client.whitelabel.links.default.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) ``` -## Delete a bounce +## Retrieve Associated Link Branding -**This endpoint allows you to remove an email address from your bounce list.** +**This endpoint allows you to retrieve the associated link branding for a subuser.** -Bounces are messages that are returned to the server that sent it. This endpoint allows you to delete a single email address from your bounce list. +Link whitelables can be associated with subusers from the parent account. This functionality allows +subusers to send mail using their parent's link brandings. To associate a link branding, the parent account +must first create a domain authentication and validate it. The parent may then associate that branded link with a subuser via the API or the Subuser Management page in the user interface. -For more information see: +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information -* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) -* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### DELETE /suppression/bounces/{email} +### GET /whitelabel/links/subuser ```python -params = {'email_address': 'example@example.com'} -email = "test_url_param" -response = sg.client.suppression.bounces._(email).delete(query_params=params) +params = {'username': 'test_string'} +response = sg.client.whitelabel.links.subuser.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve all invalid emails +## Disassociate a Link Branding -**This endpoint allows you to retrieve a list of all invalid email addresses.** +**This endpoint allows you to disassociate a link branding from a subuser.** -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. +Link whitelables can be associated with subusers from the parent account. This functionality allows +subusers to send mail using their parent's link brandings. To associate a link branding, the parent account +must first create a domain authentication and validate it. The parent may then associate that branded link with a subuser via the API or the Subuser Management page in the user interface. -Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### GET /suppression/invalid_emails +### DELETE /whitelabel/links/subuser ```python -params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} -response = sg.client.suppression.invalid_emails.get(query_params=params) +params = {'username': 'test_string'} +response = sg.client.whitelabel.links.subuser.delete(query_params=params) print(response.status_code) print(response.body) print(response.headers) ``` -## Delete invalid emails - -**This endpoint allows you to remove email addresses from your invalid email address list.** - -There are two options for deleting invalid email addresses: - -1) You can delete all invalid email addresses by setting `delete_all` to true in the request body. -2) You can delete some invalid email addresses by specifying certain addresses in an array in the request body. +## Update a Link Branding -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. +**This endpoint allows you to update a specific link branding. You can use this endpoint to change a link branding's default status.** -Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### DELETE /suppression/invalid_emails +### PATCH /whitelabel/links/{id} ```python data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] + "default": True } -response = sg.client.suppression.invalid_emails.delete(request_body=data) +id = "test_url_param" +response = sg.client.whitelabel.links._(id).patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve a specific invalid email +## Retrieve a Link Branding -**This endpoint allows you to retrieve a specific invalid email addresses.** - -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. +**This endpoint allows you to retrieve a specific link branding.** -Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### GET /suppression/invalid_emails/{email} +### GET /whitelabel/links/{id} ```python -email = "test_url_param" -response = sg.client.suppression.invalid_emails._(email).get() +id = "test_url_param" +response = sg.client.whitelabel.links._(id).get() print(response.status_code) print(response.body) print(response.headers) ``` -## Delete a specific invalid email - -**This endpoint allows you to remove a specific email address from the invalid email address list.** +## Delete a Link Branding -An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. +**This endpoint allows you to delete a link branding.** -Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### DELETE /suppression/invalid_emails/{email} +### DELETE /whitelabel/links/{id} ```python -email = "test_url_param" -response = sg.client.suppression.invalid_emails._(email).delete() +id = "test_url_param" +response = sg.client.whitelabel.links._(id).delete() print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve a specific spam report +## Validate a Link Branding -**This endpoint allows you to retrieve a specific spam report.** +**This endpoint allows you to validate a link branding.** -[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to Twilio SendGrid. +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### GET /suppression/spam_report/{email} +### POST /whitelabel/links/{id}/validate ```python -email = "test_url_param" -response = sg.client.suppression.spam_report._(email).get() +id = "test_url_param" +response = sg.client.whitelabel.links._(id).validate.post() print(response.status_code) print(response.body) print(response.headers) ``` -## Delete a specific spam report +## Associate a Link Branding -**This endpoint allows you to delete a specific spam report.** +**This endpoint allows you to associate a link branding with a subuser account.** -[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to Twilio SendGrid. +Link whitelables can be associated with subusers from the parent account. This functionality allows +subusers to send mail using their parent's link brandings. To associate a link branding, the parent account +must first create a domain authentication and validate it. The parent may then associate that branded link with a subuser via the API or the Subuser Management page in the user interface. -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). +Email link brandings allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. -### DELETE /suppression/spam_report/{email} +For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). + +### POST /whitelabel/links/{link_id}/subuser ```python -email = "test_url_param" -response = sg.client.suppression.spam_report._(email).delete() +data = { + "username": "jane@example.com" +} +link_id = "test_url_param" +response = sg.client.whitelabel.links._(link_id).subuser.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve all spam reports -**This endpoint allows you to retrieve all spam reports.** + +# STATS -[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to Twilio SendGrid. +## Retrieve global email statistics -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). +**This endpoint allows you to retrieve all of your global email statistics between a given date range.** -### GET /suppression/spam_reports +Parent accounts will see aggregated stats for their account and all subuser accounts. Subuser accounts will only see their own stats. + +### GET /stats ```python -params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} -response = sg.client.suppression.spam_reports.get(query_params=params) +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) ``` -## Delete spam reports - -**This endpoint allows you to delete your spam reports.** + +# SUBUSERS -There are two options for deleting spam reports: +## Create Subuser -1) You can delete all spam reports by setting "delete_all" to true in the request body. -2) You can delete some spam reports by specifying the email addresses in an array in the request body. +This endpoint allows you to retrieve a list of all of your subusers. You can choose to retrieve specific subusers as well as limit the results that come back from the API. -[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to Twilio SendGrid. +For more information about Subusers: -For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). +* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) +* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) -### DELETE /suppression/spam_reports +### POST /subusers ```python data = { - "delete_all": False, - "emails": [ - "example1@example.com", - "example2@example.com" - ] + "email": "John@example.com", + "ips": [ + "1.1.1.1", + "2.2.2.2" + ], + "password": "johns_password", + "username": "John@example.com" } -response = sg.client.suppression.spam_reports.delete(request_body=data) +response = sg.client.subusers.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve all global suppressions +## List all Subusers -**This endpoint allows you to retrieve a list of all email address that are globally suppressed.** +This endpoint allows you to retrieve a list of all of your subusers. You can choose to retrieve specific subusers as well as limit the results that come back from the API. -A global suppression (or global unsubscribe) is an email address of a recipient who does not want to receive any of your messages. A globally suppressed recipient will be removed from any email you send. For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/global_unsubscribes.html). +For more information about Subusers: -### GET /suppression/unsubscribes +* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) +* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) + +### GET /subusers ```python -params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} -response = sg.client.suppression.unsubscribes.get(query_params=params) +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) ``` - -# TEMPLATES - -## Create a transactional template. - -**This endpoint allows you to create a transactional template.** +## Retrieve Subuser Reputations -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. +Subuser sender reputations give a good idea how well a sender is doing with regards to how recipients and recipient servers react to the mail that is being received. When a bounce, spam report, or other negative action happens on a sent email, it will effect your sender rating. -Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +This endpoint allows you to request the reputations for your subusers. -### POST /templates +### GET /subusers/reputations ```python -data = { - "name": "example_name" -} -response = sg.client.templates.post(request_body=data) +params = {'usernames': 'test_string'} +response = sg.client.subusers.reputations.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve all transactional templates. +## Retrieve email statistics for your subusers. -**This endpoint allows you to retrieve all transactional templates.** +**This endpoint allows you to retrieve the email statistics for the given subusers.** -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. +You may retrieve statistics for up to 10 different subusers by including an additional _subusers_ parameter for each additional subuser. -Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. -### GET /templates +For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). + +### GET /subusers/stats ```python -response = sg.client.templates.get() +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) ``` -## Edit a transactional template. +## Retrieve monthly stats for all subusers -**This endpoint allows you to edit a transactional template.** +**This endpoint allows you to retrieve the monthly email statistics for all subusers over the given date range.** -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. +While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats for your subusers. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. -Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +When using the `sort_by_metric` to sort your stats by a specific metric, you can not sort by the following metrics: +`bounce_drops`, `deferred`, `invalid_emails`, `processed`, `spam_report_drops`, `spam_reports`, or `unsubscribe_drops`. +For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). -### PATCH /templates/{template_id} +### GET /subusers/stats/monthly ```python -data = { - "name": "new_example_name" -} -template_id = "test_url_param" -response = sg.client.templates._(template_id).patch(request_body=data) +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) ``` -## Retrieve a single transactional template. +## Retrieve the totals for each email statistic metric for all subusers. -**This endpoint allows you to retrieve a single transactional template.** +**This endpoint allows you to retrieve the total sums of each email statistic metric for all subusers over the given date range.** -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. -Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. +For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). -### GET /templates/{template_id} +### GET /subusers/stats/sums ```python -template_id = "test_url_param" -response = sg.client.templates._(template_id).get() +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) ``` -## Delete a template. - -**This endpoint allows you to delete a transactional template.** +## Enable/disable a subuser -Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. +This endpoint allows you to enable or disable a subuser. -Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +For more information about Subusers: +* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) +* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) -### DELETE /templates/{template_id} +### PATCH /subusers/{subuser_name} ```python -template_id = "test_url_param" -response = sg.client.templates._(template_id).delete() +data = { + "disabled": False +} +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Create a new transactional template version. +## Delete a subuser -**This endpoint allows you to create a new version of a template.** +This endpoint allows you to delete a subuser. This is a permanent action, once you delete a subuser it cannot be retrieved. -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +For more information about Subusers: -For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +* [User Guide > Subusers](https://sendgrid.com/docs/User_Guide/Settings/Subusers/index.html) +* [Classroom > How do I add more subusers to my account?](https://sendgrid.com/docs/Classroom/Basics/Account/how_do_i_add_more_subusers_to_my_account.html) +### DELETE /subusers/{subuser_name} -### POST /templates/{template_id}/versions + +```python +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).delete() +print(response.status_code) +print(response.body) +print(response.headers) +``` +## Update IPs assigned to a subuser + +Each subuser should be assigned to an IP address, from which all of this subuser's mail will be sent. Often, this is the same IP as the parent account, but each subuser can have their own, or multiple, IP addresses as well. + +More information: + +* [How to request more IPs](https://sendgrid.com/docs/Classroom/Basics/Account/adding_an_additional_dedicated_ip_to_your_account.html) +* [How to set up reverse DNS](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-reverse-dns/) + +### PUT /subusers/{subuser_name}/ips ```python -data = { - "active": 1, - "html_content": "<%body%>", - "name": "example_version_name", - "plain_content": "<%body%>", - "subject": "<%subject%>", - "template_id": "ddb96bbc-9b92-425e-8979-99464621b543" -} -template_id = "test_url_param" -response = sg.client.templates._(template_id).versions.post(request_body=data) +data = [ + "127.0.0.1" +] +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).ips.put(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Edit a transactional template version. +## Update Monitor Settings for a subuser -**This endpoint allows you to edit a version of one of your transactional templates.** +Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +### PUT /subusers/{subuser_name}/monitor -For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| template_id | string | The ID of the original template | -| version_id | string | The ID of the template version | +```python +data = { + "email": "example@example.com", + "frequency": 500 +} +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.put(request_body=data) +print(response.status_code) +print(response.body) +print(response.headers) +``` +## Create monitor settings -### PATCH /templates/{template_id}/versions/{version_id} +Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. + +### POST /subusers/{subuser_name}/monitor ```python data = { - "active": 1, - "html_content": "<%body%>", - "name": "updated_example_name", - "plain_content": "<%body%>", - "subject": "<%subject%>" + "email": "example@example.com", + "frequency": 50000 } -template_id = "test_url_param" -version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).patch(request_body=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) ``` -## Retrieve a specific transactional template version. +## Retrieve monitor settings for a subuser -**This endpoint allows you to retrieve a specific version of a template.** +Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +### GET /subusers/{subuser_name}/monitor -For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| template_id | string | The ID of the original template | -| version_id | string | The ID of the template version | +```python +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.get() +print(response.status_code) +print(response.body) +print(response.headers) +``` +## Delete monitor settings -### GET /templates/{template_id}/versions/{version_id} +Subuser monitor settings allow you to receive a sample of an outgoing message by a specific customer at a specific frequency of emails. + +### DELETE /subusers/{subuser_name}/monitor ```python -template_id = "test_url_param" -version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).get() +subuser_name = "test_url_param" +response = sg.client.subusers._(subuser_name).monitor.delete() print(response.status_code) print(response.body) print(response.headers) ``` -## Delete a transactional template version. +## Retrieve the monthly email statistics for a single subuser -**This endpoint allows you to delete one of your transactional template versions.** +**This endpoint allows you to retrieve the monthly email statistics for a specific subuser.** -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +While you can always view the statistics for all email activity on your account, subuser statistics enable you to view specific segments of your stats for your subusers. Emails sent, bounces, and spam reports are always tracked for subusers. Unsubscribes, clicks, and opens are tracked if you have enabled the required settings. -For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +When using the `sort_by_metric` to sort your stats by a specific metric, you can not sort by the following metrics: +`bounce_drops`, `deferred`, `invalid_emails`, `processed`, `spam_report_drops`, `spam_reports`, or `unsubscribe_drops`. -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| template_id | string | The ID of the original template | -| version_id | string | The ID of the template version | +For more information, see our [User Guide](https://sendgrid.com/docs/User_Guide/Statistics/subuser.html). -### DELETE /templates/{template_id}/versions/{version_id} +### GET /subusers/{subuser_name}/stats/monthly ```python -template_id = "test_url_param" -version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).delete() +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) ``` -## Activate a transactional template version. - -**This endpoint allows you to activate a version of one of your templates.** + +# SUPPRESSION -Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. +## Retrieve all blocks +**This endpoint allows you to retrieve a list of all email addresses that are currently on your blocks list.** -For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). +[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| template_id | string | The ID of the original template | -| version_id | string | The ID of the template version | +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). -### POST /templates/{template_id}/versions/{version_id}/activate +### GET /suppression/blocks ```python -template_id = "test_url_param" -version_id = "test_url_param" -response = sg.client.templates._(template_id).versions._(version_id).activate.post() +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) ``` - -# TRACKING SETTINGS +## Delete blocks -## Retrieve Tracking Settings +**This endpoint allows you to delete all email addresses on your blocks list.** -**This endpoint allows you to retrieve a list of all tracking settings that you can enable on your account.** +There are two options for deleting blocked emails: -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +1. You can delete all blocked emails by setting `delete_all` to true in the request body. +2. You can delete some blocked emails by specifying the email addresses in an array in the request body. -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. -### GET /tracking_settings +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). + +### DELETE /suppression/blocks ```python -params = {'limit': 1, 'offset': 1} -response = sg.client.tracking_settings.get(query_params=params) +data = { + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] +} +response = sg.client.suppression.blocks.delete(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Update Click Tracking Settings +## Retrieve a specific block -**This endpoint allows you to change your current click tracking setting. You can enable, or disable, click tracking using this endpoint.** +**This endpoint allows you to retrieve a specific email address from your blocks list.** -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). -### PATCH /tracking_settings/click +### GET /suppression/blocks/{email} ```python -data = { - "enabled": True -} -response = sg.client.tracking_settings.click.patch(request_body=data) +email = "test_url_param" +response = sg.client.suppression.blocks._(email).get() print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve Click Track Settings +## Delete a specific block -**This endpoint allows you to retrieve your current click tracking setting.** +**This endpoint allows you to delete a specific email address from your blocks list.** -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +[Blocks](https://sendgrid.com/docs/Glossary/blocks.html) happen when your message was rejected for a reason related to the message, not the recipient address. This can happen when your mail server IP address has been added to a blacklist or blocked by an ISP, or if the message content is flagged by a filter on the receiving server. -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/blocks.html). -### GET /tracking_settings/click +### DELETE /suppression/blocks/{email} ```python -response = sg.client.tracking_settings.click.get() +email = "test_url_param" +response = sg.client.suppression.blocks._(email).delete() print(response.status_code) print(response.body) print(response.headers) ``` -## Update Google Analytics Settings - -**This endpoint allows you to update your current setting for Google Analytics.** +## Retrieve all bounces -For more information about using Google Analytics, please refer to [Googles URL Builder](https://support.google.com/analytics/answer/1033867?hl=en) and their article on ["Best Practices for Campaign Building"](https://support.google.com/analytics/answer/1037445). +**This endpoint allows you to retrieve all of your bounces.** -We default the settings to Googles recommendations. For more information, see [Google Analytics Demystified](https://sendgrid.com/docs/Classroom/Track/Collecting_Data/google_analytics_demystified_ga_statistics_vs_sg_statistics.html). +Bounces are messages that are returned to the server that sent it. -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +For more information see: -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information +* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) -### PATCH /tracking_settings/google_analytics +### GET /suppression/bounces ```python -data = { - "enabled": True, - "utm_campaign": "website", - "utm_content": "", - "utm_medium": "email", - "utm_source": "sendgrid.com", - "utm_term": "" -} -response = sg.client.tracking_settings.google_analytics.patch(request_body=data) +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) ``` -## Retrieve Google Analytics Settings +## Delete bounces -**This endpoint allows you to retrieve your current setting for Google Analytics.** +**This endpoint allows you to delete all of your bounces. You can also use this endpoint to remove a specific email address from your bounce list.** -For more information about using Google Analytics, please refer to [Googles URL Builder](https://support.google.com/analytics/answer/1033867?hl=en) and their article on ["Best Practices for Campaign Building"](https://support.google.com/analytics/answer/1037445). +Bounces are messages that are returned to the server that sent it. -We default the settings to Googles recommendations. For more information, see [Google Analytics Demystified](https://sendgrid.com/docs/Classroom/Track/Collecting_Data/google_analytics_demystified_ga_statistics_vs_sg_statistics.html). +For more information see: -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information +* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) +* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +Note: the `delete_all` and `emails` parameters should be used independently of each other as they have different purposes. -### GET /tracking_settings/google_analytics +### DELETE /suppression/bounces ```python -response = sg.client.tracking_settings.google_analytics.get() +data = { + "delete_all": True, + "emails": [ + "example@example.com", + "example2@example.com" + ] +} +response = sg.client.suppression.bounces.delete(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Update Open Tracking Settings +## Retrieve a Bounce -**This endpoint allows you to update your current settings for open tracking.** +**This endpoint allows you to retrieve a specific bounce for a given email address.** -Open Tracking adds an invisible image at the end of the email which can track email opens. If the email recipient has images enabled on their email client, a request to SendGrids server for the invisible image is executed and an open event is logged. These events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook. +Bounces are messages that are returned to the server that sent it. -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +For more information see: -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information +* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) +* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) -### PATCH /tracking_settings/open +### GET /suppression/bounces/{email} ```python -data = { - "enabled": True -} -response = sg.client.tracking_settings.open.patch(request_body=data) +email = "test_url_param" +response = sg.client.suppression.bounces._(email).get() print(response.status_code) print(response.body) print(response.headers) ``` -## Get Open Tracking Settings +## Delete a bounce -**This endpoint allows you to retrieve your current settings for open tracking.** +**This endpoint allows you to remove an email address from your bounce list.** -Open Tracking adds an invisible image at the end of the email which can track email opens. If the email recipient has images enabled on their email client, a request to SendGrids server for the invisible image is executed and an open event is logged. These events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook. +Bounces are messages that are returned to the server that sent it. This endpoint allows you to delete a single email address from your bounce list. -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +For more information see: -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +* [User Guide > Bounces](https://sendgrid.com/docs/User_Guide/Suppressions/bounces.html) for more information +* [Glossary > Bounces](https://sendgrid.com/docs/Glossary/Bounces.html) +* [Classroom > List Scrubbing Guide](https://sendgrid.com/docs/Classroom/Deliver/list_scrubbing.html) -### GET /tracking_settings/open +### DELETE /suppression/bounces/{email} ```python -response = sg.client.tracking_settings.open.get() +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) ``` -## Update Subscription Tracking Settings +## Retrieve all invalid emails -**This endpoint allows you to update your current settings for subscription tracking.** +**This endpoint allows you to retrieve a list of all invalid email addresses.** -Subscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails. +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). -### PATCH /tracking_settings/subscription +### GET /suppression/invalid_emails ```python -data = { - "enabled": True, - "html_content": "html content", - "landing": "landing page html", - "plain_content": "text content", - "replace": "replacement tag", - "url": "url" -} -response = sg.client.tracking_settings.subscription.patch(request_body=data) +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) ``` -## Retrieve Subscription Tracking Settings +## Delete invalid emails -**This endpoint allows you to retrieve your current settings for subscription tracking.** +**This endpoint allows you to remove email addresses from your invalid email address list.** -Subscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails. +There are two options for deleting invalid email addresses: -You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +1) You can delete all invalid email addresses by setting `delete_all` to true in the request body. +2) You can delete some invalid email addresses by specifying certain addresses in an array in the request body. -For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. -### GET /tracking_settings/subscription +Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. + +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). + +### DELETE /suppression/invalid_emails ```python -response = sg.client.tracking_settings.subscription.get() +data = { + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] +} +response = sg.client.suppression.invalid_emails.delete(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` - -# USER - -## Get a user's account information. - -**This endpoint allows you to retrieve your user account details.** +## Retrieve a specific invalid email -Your user's account information includes the user's account type and reputation. +**This endpoint allows you to retrieve a specific invalid email addresses.** -Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. -For more information about your user profile: +Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. -* [Twilio SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). -### GET /user/account +### GET /suppression/invalid_emails/{email} ```python -response = sg.client.user.account.get() +email = "test_url_param" +response = sg.client.suppression.invalid_emails._(email).get() print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve your credit balance +## Delete a specific invalid email -**This endpoint allows you to retrieve the current credit balance for your account.** +**This endpoint allows you to remove a specific email address from the invalid email address list.** -Your monthly credit allotment limits the number of emails you may send before incurring overage charges. For more information about credits and billing, please visit our [Classroom](https://sendgrid.com/docs/Classroom/Basics/Billing/billing_info_and_faqs.html). +An invalid email occurs when you attempt to send email to an address that is formatted in a manner that does not meet internet email format standards or the email does not exist at the recipients mail server. -### GET /user/credits +Examples include addresses without the @ sign or addresses that include certain special characters and/or spaces. This response can come from our own server or the recipient mail server. + +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/invalid_emails.html). + +### DELETE /suppression/invalid_emails/{email} ```python -response = sg.client.user.credits.get() +email = "test_url_param" +response = sg.client.suppression.invalid_emails._(email).delete() print(response.status_code) print(response.body) print(response.headers) ``` -## Update your account email address - -**This endpoint allows you to update the email address currently on file for your account.** +## Retrieve a specific spam report -Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. +**This endpoint allows you to retrieve a specific spam report.** -For more information about your user profile: +[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to Twilio SendGrid. -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). -### PUT /user/email +### GET /suppression/spam_report/{email} ```python -data = { - "email": "example@example.com" -} -response = sg.client.user.email.put(request_body=data) +email = "test_url_param" +response = sg.client.suppression.spam_report._(email).get() print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve your account email address - -**This endpoint allows you to retrieve the email address currently on file for your account.** +## Delete a specific spam report -Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. +**This endpoint allows you to delete a specific spam report.** -For more information about your user profile: +[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to Twilio SendGrid. -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). -### GET /user/email +### DELETE /suppression/spam_report/{email} ```python -response = sg.client.user.email.get() +email = "test_url_param" +response = sg.client.suppression.spam_report._(email).delete() print(response.status_code) print(response.body) print(response.headers) ``` -## Update your password - -**This endpoint allows you to update your password.** +## Retrieve all spam reports -Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. +**This endpoint allows you to retrieve all spam reports.** -For more information about your user profile: +[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to Twilio SendGrid. -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). -### PUT /user/password +### GET /suppression/spam_reports ```python -data = { - "new_password": "new_password", - "old_password": "old_password" -} -response = sg.client.user.password.put(request_body=data) +params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} +response = sg.client.suppression.spam_reports.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) ``` -## Update a user's profile +## Delete spam reports -**This endpoint allows you to update your current profile details.** +**This endpoint allows you to delete your spam reports.** -Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. +There are two options for deleting spam reports: -For more information about your user profile: +1) You can delete all spam reports by setting "delete_all" to true in the request body. +2) You can delete some spam reports by specifying the email addresses in an array in the request body. -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +[Spam reports](https://sendgrid.com/docs/Glossary/spam_reports.html) happen when a recipient indicates that they think your email is [spam](https://sendgrid.com/docs/Glossary/spam.html) and then their email provider reports this to Twilio SendGrid. -It should be noted that any one or more of the parameters can be updated via the PATCH /user/profile endpoint. The only requirement is that you include at least one when you PATCH. +For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/spam_reports.html). -### PATCH /user/profile +### DELETE /suppression/spam_reports ```python data = { - "city": "Orange", - "first_name": "Example", - "last_name": "User" + "delete_all": False, + "emails": [ + "example1@example.com", + "example2@example.com" + ] } -response = sg.client.user.profile.patch(request_body=data) +response = sg.client.suppression.spam_reports.delete(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Get a user's profile - -Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. +## Retrieve all global suppressions -For more information about your user profile: +**This endpoint allows you to retrieve a list of all email address that are globally suppressed.** -* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +A global suppression (or global unsubscribe) is an email address of a recipient who does not want to receive any of your messages. A globally suppressed recipient will be removed from any email you send. For more information, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Suppressions/global_unsubscribes.html). -### GET /user/profile +### GET /suppression/unsubscribes ```python -response = sg.client.user.profile.get() +params = {'start_time': 1, 'limit': 1, 'end_time': 1, 'offset': 1} +response = sg.client.suppression.unsubscribes.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) ``` -## Cancel or pause a scheduled send + +# TEMPLATES -**This endpoint allows you to cancel or pause an email that has been scheduled to be sent.** +## Create a transactional template. -If the maximum number of cancellations/pauses are added, HTTP 400 will -be returned. +**This endpoint allows you to create a transactional template.** -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. -### POST /user/scheduled_sends +Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). + +### POST /templates ```python data = { - "batch_id": "YOUR_BATCH_ID", - "status": "pause" + "name": "example_name" } -response = sg.client.user.scheduled_sends.post(request_body=data) +response = sg.client.templates.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve all scheduled sends +## Retrieve all transactional templates. -**This endpoint allows you to retrieve all cancel/paused scheduled send information.** +**This endpoint allows you to retrieve all transactional templates.** -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. -### GET /user/scheduled_sends +Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). + +### GET /templates ```python -response = sg.client.user.scheduled_sends.get() +response = sg.client.templates.get() print(response.status_code) print(response.body) print(response.headers) ``` -## Update user scheduled send information +## Edit a transactional template. -**This endpoint allows you to update the status of a scheduled send for the given `batch_id`.** +**This endpoint allows you to edit a transactional template.** -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. -### PATCH /user/scheduled_sends/{batch_id} +Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). + + +### PATCH /templates/{template_id} ```python data = { - "status": "pause" + "name": "new_example_name" } -batch_id = "test_url_param" -response = sg.client.user.scheduled_sends._(batch_id).patch(request_body=data) +template_id = "test_url_param" +response = sg.client.templates._(template_id).patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve scheduled send - -**This endpoint allows you to retrieve the cancel/paused scheduled send information for a specific `batch_id`.** - -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. - -### GET /user/scheduled_sends/{batch_id} +## Retrieve a single transactional template. +**This endpoint allows you to retrieve a single transactional template.** -```python -batch_id = "test_url_param" -response = sg.client.user.scheduled_sends._(batch_id).get() -print(response.status_code) -print(response.body) -print(response.headers) -``` -## Delete a cancellation or pause of a scheduled send +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. -**This endpoint allows you to delete the cancellation/pause of a scheduled send.** +Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### DELETE /user/scheduled_sends/{batch_id} +### GET /templates/{template_id} ```python -batch_id = "test_url_param" -response = sg.client.user.scheduled_sends._(batch_id).delete() +template_id = "test_url_param" +response = sg.client.templates._(template_id).get() print(response.status_code) print(response.body) print(response.headers) ``` -## Update Enforced TLS settings +## Delete a template. -**This endpoint allows you to update your current Enforced TLS settings.** +**This endpoint allows you to delete a transactional template.** -The Enforced TLS settings specify whether or not the recipient is required to support TLS or have a valid certificate. See the [SMTP Ports User Guide](https://sendgrid.com/docs/Classroom/Basics/Email_Infrastructure/smtp_ports.html) for more information on opportunistic TLS. +Each user can create up to 300 different transactional templates. Transactional templates are specific to accounts and subusers. Templates created on a parent account will not be accessible from the subuser accounts. -**Note:** If either setting is enabled and the recipient does not support TLS or have a valid certificate, we drop the message and send a block event with TLS required but not supported as the description. +Transactional templates are templates created specifically for transactional email and are not to be confused with [Marketing Campaigns templates](https://sendgrid.com/docs/User_Guide/Marketing_Campaigns/templates.html). For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -### PATCH /user/settings/enforced_tls + +### DELETE /templates/{template_id} ```python -data = { - "require_tls": True, - "require_valid_cert": False -} -response = sg.client.user.settings.enforced_tls.patch(request_body=data) +template_id = "test_url_param" +response = sg.client.templates._(template_id).delete() print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve current Enforced TLS settings. +## Create a new transactional template version. -**This endpoint allows you to retrieve your current Enforced TLS settings.** +**This endpoint allows you to create a new version of a template.** -The Enforced TLS settings specify whether or not the recipient is required to support TLS or have a valid certificate. See the [SMTP Ports User Guide](https://sendgrid.com/docs/Classroom/Basics/Email_Infrastructure/smtp_ports.html) for more information on opportunistic TLS. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. -**Note:** If either setting is enabled and the recipient does not support TLS or have a valid certificate, we drop the message and send a block event with TLS required but not supported as the description. +For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -### GET /user/settings/enforced_tls + +### POST /templates/{template_id}/versions ```python -response = sg.client.user.settings.enforced_tls.get() +data = { + "active": 1, + "html_content": "<%body%>", + "name": "example_version_name", + "plain_content": "<%body%>", + "subject": "<%subject%>", + "template_id": "ddb96bbc-9b92-425e-8979-99464621b543" +} +template_id = "test_url_param" +response = sg.client.templates._(template_id).versions.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Update your username +## Edit a transactional template version. -**This endpoint allows you to update the username for your account.** +**This endpoint allows you to edit a version of one of your transactional templates.** -Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. -For more information about your user profile: +For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -* [Twilio SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| template_id | string | The ID of the original template | +| version_id | string | The ID of the template version | -### PUT /user/username +### PATCH /templates/{template_id}/versions/{version_id} ```python data = { - "username": "test_username" + "active": 1, + "html_content": "<%body%>", + "name": "updated_example_name", + "plain_content": "<%body%>", + "subject": "<%subject%>" } -response = sg.client.user.username.put(request_body=data) +template_id = "test_url_param" +version_id = "test_url_param" +response = sg.client.templates._(template_id).versions._(version_id).patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve your username +## Retrieve a specific transactional template version. -**This endpoint allows you to retrieve your current account username.** +**This endpoint allows you to retrieve a specific version of a template.** -Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. -For more information about your user profile: +For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -* [Twilio SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| template_id | string | The ID of the original template | +| version_id | string | The ID of the template version | -### GET /user/username +### GET /templates/{template_id}/versions/{version_id} ```python -response = sg.client.user.username.get() +template_id = "test_url_param" +version_id = "test_url_param" +response = sg.client.templates._(template_id).versions._(version_id).get() print(response.status_code) print(response.body) print(response.headers) ``` -## Update Event Notification Settings +## Delete a transactional template version. -**This endpoint allows you to update your current event webhook settings.** +**This endpoint allows you to delete one of your transactional template versions.** -If an event type is marked as `true`, then the event webhook will include information about that event. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. -Twilio SendGrid's Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as Twilio SendGrid processes your email. +For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| template_id | string | The ID of the original template | +| version_id | string | The ID of the template version | -### PATCH /user/webhooks/event/settings +### DELETE /templates/{template_id}/versions/{version_id} ```python -data = { - "bounce": True, - "click": True, - "deferred": True, - "delivered": True, - "dropped": True, - "enabled": True, - "group_resubscribe": True, - "group_unsubscribe": True, - "open": True, - "processed": True, - "spam_report": True, - "unsubscribe": True, - "url": "url" -} -response = sg.client.user.webhooks.event.settings.patch(request_body=data) +template_id = "test_url_param" +version_id = "test_url_param" +response = sg.client.templates._(template_id).versions._(version_id).delete() print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve Event Webhook settings +## Activate a transactional template version. -**This endpoint allows you to retrieve your current event webhook settings.** +**This endpoint allows you to activate a version of one of your templates.** -If an event type is marked as `true`, then the event webhook will include information about that event. +Each transactional template can have multiple versions, each version with its own subject and content. Each user can have up to 300 versions across across all templates. -Twilio SendGrid's Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as Twilio SendGrid processes your email. -Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. +For more information about transactional templates, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). -### GET /user/webhooks/event/settings +## URI Parameters +| URI Parameter | Type | Description | +|---|---|---| +| template_id | string | The ID of the original template | +| version_id | string | The ID of the template version | + +### POST /templates/{template_id}/versions/{version_id}/activate ```python -response = sg.client.user.webhooks.event.settings.get() +template_id = "test_url_param" +version_id = "test_url_param" +response = sg.client.templates._(template_id).versions._(version_id).activate.post() print(response.status_code) print(response.body) print(response.headers) ``` -## Test Event Notification Settings + +# TRACKING SETTINGS -**This endpoint allows you to test your event webhook by sending a fake event notification post to the provided URL.** +## Retrieve Tracking Settings -Twilio SendGrid's Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as Twilio SendGrid processes your email. +**This endpoint allows you to retrieve a list of all tracking settings that you can enable on your account.** -Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -### POST /user/webhooks/event/test +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). + +### GET /tracking_settings ```python -data = { - "url": "url" -} -response = sg.client.user.webhooks.event.test.post(request_body=data) +params = {'limit': 1, 'offset': 1} +response = sg.client.tracking_settings.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) ``` -## Create a parse setting +## Update Click Tracking Settings + +**This endpoint allows you to change your current click tracking setting. You can enable, or disable, click tracking using this endpoint.** -**This endpoint allows you to create a new inbound parse setting.** +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by Twilio SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). -### POST /user/webhooks/parse/settings +### PATCH /tracking_settings/click ```python data = { - "hostname": "myhostname.com", - "send_raw": False, - "spam_check": True, - "url": "http://email.myhosthame.com" + "enabled": True } -response = sg.client.user.webhooks.parse.settings.post(request_body=data) +response = sg.client.tracking_settings.click.patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve all parse settings +## Retrieve Click Track Settings -**This endpoint allows you to retrieve all of your current inbound parse settings.** +**This endpoint allows you to retrieve your current click tracking setting.** -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by Twilio SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -### GET /user/webhooks/parse/settings +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). + +### GET /tracking_settings/click ```python -response = sg.client.user.webhooks.parse.settings.get() +response = sg.client.tracking_settings.click.get() print(response.status_code) print(response.body) print(response.headers) ``` -## Update a parse setting +## Update Google Analytics Settings -**This endpoint allows you to update a specific inbound parse setting.** +**This endpoint allows you to update your current setting for Google Analytics.** -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by Twilio SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). +For more information about using Google Analytics, please refer to [Googles URL Builder](https://support.google.com/analytics/answer/1033867?hl=en) and their article on ["Best Practices for Campaign Building"](https://support.google.com/analytics/answer/1037445). -### PATCH /user/webhooks/parse/settings/{hostname} +We default the settings to Googles recommendations. For more information, see [Google Analytics Demystified](https://sendgrid.com/docs/Classroom/Track/Collecting_Data/google_analytics_demystified_ga_statistics_vs_sg_statistics.html). + +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. + +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). + +### PATCH /tracking_settings/google_analytics ```python data = { - "send_raw": True, - "spam_check": False, - "url": "http://newdomain.com/parse" + "enabled": True, + "utm_campaign": "website", + "utm_content": "", + "utm_medium": "email", + "utm_source": "sendgrid.com", + "utm_term": "" } -hostname = "test_url_param" -response = sg.client.user.webhooks.parse.settings._(hostname).patch(request_body=data) +response = sg.client.tracking_settings.google_analytics.patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve a specific parse setting - -**This endpoint allows you to retrieve a specific inbound parse setting.** - -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by Twilio SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). +## Retrieve Google Analytics Settings -### GET /user/webhooks/parse/settings/{hostname} +**This endpoint allows you to retrieve your current setting for Google Analytics.** +For more information about using Google Analytics, please refer to [Googles URL Builder](https://support.google.com/analytics/answer/1033867?hl=en) and their article on ["Best Practices for Campaign Building"](https://support.google.com/analytics/answer/1037445). -```python -hostname = "test_url_param" -response = sg.client.user.webhooks.parse.settings._(hostname).get() -print(response.status_code) -print(response.body) -print(response.headers) -``` -## Delete a parse setting +We default the settings to Googles recommendations. For more information, see [Google Analytics Demystified](https://sendgrid.com/docs/Classroom/Track/Collecting_Data/google_analytics_demystified_ga_statistics_vs_sg_statistics.html). -**This endpoint allows you to delete a specific inbound parse setting.** +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by Twilio SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). -### DELETE /user/webhooks/parse/settings/{hostname} +### GET /tracking_settings/google_analytics ```python -hostname = "test_url_param" -response = sg.client.user.webhooks.parse.settings._(hostname).delete() +response = sg.client.tracking_settings.google_analytics.get() print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieves Inbound Parse Webhook statistics. +## Update Open Tracking Settings -**This endpoint allows you to retrieve the statistics for your Parse Webhook usage.** +**This endpoint allows you to update your current settings for open tracking.** -Twilio SendGrid's Inbound Parse Webhook allows you to parse the contents and attachments of incoming emails. The Parse API can then POST the parsed emails to a URL that you specify. The Inbound Parse Webhook cannot parse messages greater than 20MB in size, including all attachments. +Open Tracking adds an invisible image at the end of the email which can track email opens. If the email recipient has images enabled on their email client, a request to SendGrids server for the invisible image is executed and an open event is logged. These events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook. -There are a number of pre-made integrations for the Twilio SendGrid Parse Webhook which make processing events easy. You can find these integrations in the [Library Index](https://sendgrid.com/docs/Integrate/libraries.html#-Webhook-Libraries). +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -### GET /user/webhooks/parse/stats +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). + +### PATCH /tracking_settings/open ```python -params = {'aggregated_by': 'day', 'limit': 'test_string', 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 'test_string'} -response = sg.client.user.webhooks.parse.stats.get(query_params=params) +data = { + "enabled": True +} +response = sg.client.tracking_settings.open.patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` - -# WHITELABEL - -## Create a domain whitelabel. +## Get Open Tracking Settings -**This endpoint allows you to create a whitelabel for one of your domains.** +**This endpoint allows you to retrieve your current settings for open tracking.** -If you are creating a domain whitelabel that you would like a subuser to use, you have two options: -1. Use the "username" parameter. This allows you to create a whitelabel on behalf of your subuser. This means the subuser is able to see and modify the created whitelabel. -2. Use the Association workflow (see Associate Domain section). This allows you to assign a whitelabel created by the parent to a subuser. This means the subuser will default to the assigned whitelabel, but will not be able to see or modify that whitelabel. However, if the subuser creates their own whitelabel it will overwrite the assigned whitelabel. +Open Tracking adds an invisible image at the end of the email which can track email opens. If the email recipient has images enabled on their email client, a request to SendGrids server for the invisible image is executed and an open event is logged. These events are logged in the Statistics portal, Email Activity interface, and are reported by the Event Webhook. -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). -### POST /whitelabel/domains +### GET /tracking_settings/open ```python -data = { - "automatic_security": False, - "custom_spf": True, - "default": True, - "domain": "example.com", - "ips": [ - "192.168.1.1", - "192.168.1.2" - ], - "subdomain": "news", - "username": "john@example.com" -} -response = sg.client.whitelabel.domains.post(request_body=data) +response = sg.client.tracking_settings.open.get() print(response.status_code) print(response.body) print(response.headers) ``` -## List all domain whitelabels. +## Update Subscription Tracking Settings -**This endpoint allows you to retrieve a list of all domain whitelabels you have created.** +**This endpoint allows you to update your current settings for subscription tracking.** -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Subscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). -### GET /whitelabel/domains +### PATCH /tracking_settings/subscription ```python -params = {'username': 'test_string', 'domain': 'test_string', 'exclude_subusers': 'true', 'limit': 1, 'offset': 1} -response = sg.client.whitelabel.domains.get(query_params=params) +data = { + "enabled": True, + "html_content": "html content", + "landing": "landing page html", + "plain_content": "text content", + "replace": "replacement tag", + "url": "url" +} +response = sg.client.tracking_settings.subscription.patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Get the default domain whitelabel. +## Retrieve Subscription Tracking Settings -**This endpoint allows you to retrieve the default whitelabel for a domain.** +**This endpoint allows you to retrieve your current settings for subscription tracking.** -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Subscription tracking adds links to the bottom of your emails that allows your recipients to subscribe to, or unsubscribe from, your emails. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +You can track a variety of the actions your recipients may take when interacting with your emails including opening your emails, clicking on links in your emails, and subscribing to (or unsubscribing from) your emails. -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| domain | string |The domain to find a default domain whitelabel for. | +For more information about tracking, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/tracking.html). -### GET /whitelabel/domains/default +### GET /tracking_settings/subscription ```python -response = sg.client.whitelabel.domains.default.get() +response = sg.client.tracking_settings.subscription.get() print(response.status_code) print(response.body) print(response.headers) ``` -## List the domain whitelabel associated with the given user. + +# USER -**This endpoint allows you to retrieve all of the whitelabels that have been assigned to a specific subuser.** +## Get a user's account information. -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +**This endpoint allows you to retrieve your user account details.** -Domain whitelabels can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's whitelabels. To associate a whitelabel with a subuser, the parent account must first create the whitelabel and validate it. The parent may then associate the whitelabel via the subuser management tools. +Your user's account information includes the user's account type and reputation. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| username | string | Username of the subuser to find associated whitelabels for. | +For more information about your user profile: -### GET /whitelabel/domains/subuser +* [Twilio SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) + +### GET /user/account ```python -response = sg.client.whitelabel.domains.subuser.get() +response = sg.client.user.account.get() print(response.status_code) print(response.body) print(response.headers) ``` -## Disassociate a domain whitelabel from a given user. - -**This endpoint allows you to disassociate a specific whitelabel from a subuser.** - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. - -Domain whitelabels can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's whitelabels. To associate a whitelabel with a subuser, the parent account must first create the whitelabel and validate it. The parent may then associate the whitelabel via the subuser management tools. +## Retrieve your credit balance -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +**This endpoint allows you to retrieve the current credit balance for your account.** -## URI Parameters -| URI Parameter | Type | Required? | Description | -|---|---|---|---| -| username | string | required | Username for the subuser to find associated whitelabels for. | +Your monthly credit allotment limits the number of emails you may send before incurring overage charges. For more information about credits and billing, please visit our [Classroom](https://sendgrid.com/docs/Classroom/Basics/Billing/billing_info_and_faqs.html). -### DELETE /whitelabel/domains/subuser +### GET /user/credits ```python -response = sg.client.whitelabel.domains.subuser.delete() +response = sg.client.user.credits.get() print(response.status_code) print(response.body) print(response.headers) ``` -## Update a domain whitelabel. +## Update your account email address -**This endpoint allows you to update the settings for a domain whitelabel.** +**This endpoint allows you to update the email address currently on file for your account.** -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +For more information about your user profile: -### PATCH /whitelabel/domains/{domain_id} +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) + +### PUT /user/email ```python data = { - "custom_spf": True, - "default": False + "email": "example@example.com" } -domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).patch(request_body=data) +response = sg.client.user.email.put(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve a domain whitelabel. +## Retrieve your account email address -**This endpoint allows you to retrieve a specific domain whitelabel.** +**This endpoint allows you to retrieve the email address currently on file for your account.** -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +For more information about your user profile: +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) -### GET /whitelabel/domains/{domain_id} +### GET /user/email ```python -domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).get() +response = sg.client.user.email.get() print(response.status_code) print(response.body) print(response.headers) ``` -## Delete a domain whitelabel. +## Update your password -**This endpoint allows you to delete a domain whitelabel.** +**This endpoint allows you to update your password.** -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +For more information about your user profile: -### DELETE /whitelabel/domains/{domain_id} +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) + +### PUT /user/password ```python -domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).delete() +data = { + "new_password": "new_password", + "old_password": "old_password" +} +response = sg.client.user.password.put(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Associate a domain whitelabel with a given user. +## Update a user's profile -**This endpoint allows you to associate a specific domain whitelabel with a subuser.** +**This endpoint allows you to update your current profile details.** -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. -Domain whitelabels can be associated with (i.e. assigned to) subusers from a parent account. This functionality allows subusers to send mail using their parent's whitelabels. To associate a whitelabel with a subuser, the parent account must first create the whitelabel and validate it. The parent may then associate the whitelabel via the subuser management tools. +For more information about your user profile: -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| domain_id | integer | ID of the domain whitelabel to associate with the subuser. | +It should be noted that any one or more of the parameters can be updated via the PATCH /user/profile endpoint. The only requirement is that you include at least one when you PATCH. -### POST /whitelabel/domains/{domain_id}/subuser +### PATCH /user/profile ```python data = { - "username": "jane@example.com" + "city": "Orange", + "first_name": "Example", + "last_name": "User" } -domain_id = "test_url_param" -response = sg.client.whitelabel.domains._(domain_id).subuser.post(request_body=data) +response = sg.client.user.profile.patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Add an IP to a domain whitelabel. - -**This endpoint allows you to add an IP address to a domain whitelabel.** +## Get a user's profile -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +For more information about your user profile: -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| id | integer | ID of the domain to which you are adding an IP | +* [SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) -### POST /whitelabel/domains/{id}/ips +### GET /user/profile ```python -data = { - "ip": "192.168.0.1" -} -id = "test_url_param" -response = sg.client.whitelabel.domains._(id).ips.post(request_body=data) +response = sg.client.user.profile.get() print(response.status_code) print(response.body) print(response.headers) ``` -## Remove an IP from a domain whitelabel. - -**This endpoint allows you to remove a domain's IP address from that domain's whitelabel.** +## Cancel or pause a scheduled send -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +**This endpoint allows you to cancel or pause an email that has been scheduled to be sent.** -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +If the maximum number of cancellations/pauses are added, HTTP 400 will +be returned. -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| id | integer | ID of the domain whitelabel to delete the IP from. | -| ip | string | IP to remove from the domain whitelabel. | +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### DELETE /whitelabel/domains/{id}/ips/{ip} +### POST /user/scheduled_sends ```python -id = "test_url_param" -ip = "test_url_param" -response = sg.client.whitelabel.domains._(id).ips._(ip).delete() +data = { + "batch_id": "YOUR_BATCH_ID", + "status": "pause" +} +response = sg.client.user.scheduled_sends.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Validate a domain whitelabel. - -**This endpoint allows you to validate a domain whitelabel. If it fails, it will return an error message describing why the whitelabel could not be validated.** - -A domain whitelabel allows you to remove the via or sent on behalf of message that your recipients see when they read your emails. Whitelabeling a domain allows you to replace sendgrid.net with your personal sending domain. You will be required to create a subdomain so that Twilio SendGrid can generate the DNS records which you must give to your host provider. If you choose to use Automated Security, Twilio SendGrid will provide you with 3 CNAME records. If you turn Automated Security off, you will be given 2 TXT records and 1 MX record. +## Retrieve all scheduled sends -For more information on whitelabeling, please see our [User Guide](https://sendgrid.com/docs/User_Guide/Settings/Whitelabel/index.html) +**This endpoint allows you to retrieve all cancel/paused scheduled send information.** -## URI Parameters -| URI Parameter | Type | Description | -|---|---|---| -| id | integer |ID of the domain whitelabel to validate. | +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### POST /whitelabel/domains/{id}/validate +### GET /user/scheduled_sends ```python -id = "test_url_param" -response = sg.client.whitelabel.domains._(id).validate.post() +response = sg.client.user.scheduled_sends.get() print(response.status_code) print(response.body) print(response.headers) ``` -## Create an IP whitelabel - -**This endpoint allows you to create an IP whitelabel.** - -When creating an IP whitelable, you should use the same subdomain that you used when you created a domain whitelabel. +## Update user scheduled send information -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once Twilio SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. +**This endpoint allows you to update the status of a scheduled send for the given `batch_id`.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### POST /whitelabel/ips +### PATCH /user/scheduled_sends/{batch_id} ```python data = { - "domain": "example.com", - "ip": "192.168.1.1", - "subdomain": "email" + "status": "pause" } -response = sg.client.whitelabel.ips.post(request_body=data) +batch_id = "test_url_param" +response = sg.client.user.scheduled_sends._(batch_id).patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve all IP whitelabels - -**This endpoint allows you to retrieve all of the IP whitelabels that have been created by this account.** - -You may include a search key by using the "ip" parameter. This enables you to perform a prefix search for a given IP segment (e.g. "192."). +## Retrieve scheduled send -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once Twilio SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. +**This endpoint allows you to retrieve the cancel/paused scheduled send information for a specific `batch_id`.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### GET /whitelabel/ips +### GET /user/scheduled_sends/{batch_id} ```python -params = {'ip': 'test_string', 'limit': 1, 'offset': 1} -response = sg.client.whitelabel.ips.get(query_params=params) +batch_id = "test_url_param" +response = sg.client.user.scheduled_sends._(batch_id).get() print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve an IP whitelabel - -**This endpoint allows you to retrieve an IP whitelabel.** +## Delete a cancellation or pause of a scheduled send -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once Twilio SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. +**This endpoint allows you to delete the cancellation/pause of a scheduled send.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). +The Cancel Scheduled Sends feature allows the customer to cancel a scheduled send based on a Batch ID included in the SMTPAPI header.Scheduled sends cancelled less than 10 minutes before the scheduled time are not guaranteed to be cancelled. -### GET /whitelabel/ips/{id} +### DELETE /user/scheduled_sends/{batch_id} ```python -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).get() +batch_id = "test_url_param" +response = sg.client.user.scheduled_sends._(batch_id).delete() print(response.status_code) print(response.body) print(response.headers) ``` -## Delete an IP whitelabel +## Update Enforced TLS settings -**This endpoint allows you to delete an IP whitelabel.** +**This endpoint allows you to update your current Enforced TLS settings.** -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once Twilio SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. +The Enforced TLS settings specify whether or not the recipient is required to support TLS or have a valid certificate. See the [SMTP Ports User Guide](https://sendgrid.com/docs/Classroom/Basics/Email_Infrastructure/smtp_ports.html) for more information on opportunistic TLS. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). +**Note:** If either setting is enabled and the recipient does not support TLS or have a valid certificate, we drop the message and send a block event with TLS required but not supported as the description. -### DELETE /whitelabel/ips/{id} +### PATCH /user/settings/enforced_tls ```python -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).delete() +data = { + "require_tls": True, + "require_valid_cert": False +} +response = sg.client.user.settings.enforced_tls.patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Validate an IP whitelabel +## Retrieve current Enforced TLS settings. -**This endpoint allows you to validate an IP whitelabel.** +**This endpoint allows you to retrieve your current Enforced TLS settings.** -A IP whitelabel consists of a subdomain and domain that will be used to generate a reverse DNS record for a given IP. Once Twilio SendGrid has verified that the appropriate A record for the IP has been created, the appropriate reverse DNS record for the IP is generated. +The Enforced TLS settings specify whether or not the recipient is required to support TLS or have a valid certificate. See the [SMTP Ports User Guide](https://sendgrid.com/docs/Classroom/Basics/Email_Infrastructure/smtp_ports.html) for more information on opportunistic TLS. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/ips.html). +**Note:** If either setting is enabled and the recipient does not support TLS or have a valid certificate, we drop the message and send a block event with TLS required but not supported as the description. -### POST /whitelabel/ips/{id}/validate +### GET /user/settings/enforced_tls ```python -id = "test_url_param" -response = sg.client.whitelabel.ips._(id).validate.post() +response = sg.client.user.settings.enforced_tls.get() print(response.status_code) print(response.body) print(response.headers) ``` -## Create a Link Whitelabel +## Update your username -**This endpoint allows you to create a new link whitelabel.** +**This endpoint allows you to update the username for your account.** -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +For more information about your user profile: -### POST /whitelabel/links +* [Twilio SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) + +### PUT /user/username ```python data = { - "default": True, - "domain": "example.com", - "subdomain": "mail" + "username": "test_username" } -params = {'limit': 1, 'offset': 1} -response = sg.client.whitelabel.links.post(request_body=data, query_params=params) +response = sg.client.user.username.put(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve all link whitelabels +## Retrieve your username -**This endpoint allows you to retrieve all link whitelabels.** +**This endpoint allows you to retrieve your current account username.** -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +Keeping your user profile up to date is important. This will help Twilio SendGrid to verify who you are as well as contact you should we need to. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +For more information about your user profile: -### GET /whitelabel/links +* [Twilio SendGrid Account Settings](https://sendgrid.com/docs/User_Guide/Settings/account.html) + +### GET /user/username ```python -params = {'limit': 1} -response = sg.client.whitelabel.links.get(query_params=params) +response = sg.client.user.username.get() print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve a Default Link Whitelabel +## Update Event Notification Settings -**This endpoint allows you to retrieve the default link whitelabel.** +**This endpoint allows you to update your current event webhook settings.** -Default link whitelabel is the actual link whitelabel to be used when sending messages. If there are multiple link whitelabels, the default is determined by the following order: -
    -
  • Validated link whitelabels marked as "default"
  • -
  • Legacy link whitelabels (migrated from the whitelabel wizard)
  • -
  • Default Twilio SendGrid link whitelabel (i.e. 100.ct.sendgrid.net)
  • -
+If an event type is marked as `true`, then the event webhook will include information about that event. -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +Twilio SendGrid's Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as Twilio SendGrid processes your email. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. -### GET /whitelabel/links/default +### PATCH /user/webhooks/event/settings ```python -params = {'domain': 'test_string'} -response = sg.client.whitelabel.links.default.get(query_params=params) +data = { + "bounce": True, + "click": True, + "deferred": True, + "delivered": True, + "dropped": True, + "enabled": True, + "group_resubscribe": True, + "group_unsubscribe": True, + "open": True, + "processed": True, + "spam_report": True, + "unsubscribe": True, + "url": "url" +} +response = sg.client.user.webhooks.event.settings.patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve Associated Link Whitelabel +## Retrieve Event Webhook settings -**This endpoint allows you to retrieve the associated link whitelabel for a subuser.** +**This endpoint allows you to retrieve your current event webhook settings.** -Link whitelables can be associated with subusers from the parent account. This functionality allows -subusers to send mail using their parent's link whitelabels. To associate a link whitelabel, the parent account -must first create a whitelabel and validate it. The parent may then associate that whitelabel with a subuser via the API or the Subuser Management page in the user interface. +If an event type is marked as `true`, then the event webhook will include information about that event. -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +Twilio SendGrid's Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as Twilio SendGrid processes your email. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. -### GET /whitelabel/links/subuser +### GET /user/webhooks/event/settings ```python -params = {'username': 'test_string'} -response = sg.client.whitelabel.links.subuser.get(query_params=params) +response = sg.client.user.webhooks.event.settings.get() print(response.status_code) print(response.body) print(response.headers) ``` -## Disassociate a Link Whitelabel - -**This endpoint allows you to disassociate a link whitelabel from a subuser.** +## Test Event Notification Settings -Link whitelables can be associated with subusers from the parent account. This functionality allows -subusers to send mail using their parent's link whitelabels. To associate a link whitelabel, the parent account -must first create a whitelabel and validate it. The parent may then associate that whitelabel with a subuser via the API or the Subuser Management page in the user interface. +**This endpoint allows you to test your event webhook by sending a fake event notification post to the provided URL.** -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +Twilio SendGrid's Event Webhook will notify a URL of your choice via HTTP POST with information about events that occur as Twilio SendGrid processes your email. -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +Common uses of this data are to remove unsubscribes, react to spam reports, determine unengaged recipients, identify bounced email addresses, or create advanced analytics of your email program. -### DELETE /whitelabel/links/subuser +### POST /user/webhooks/event/test ```python -params = {'username': 'test_string'} -response = sg.client.whitelabel.links.subuser.delete(query_params=params) +data = { + "url": "url" +} +response = sg.client.user.webhooks.event.test.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Update a Link Whitelabel - -**This endpoint allows you to update a specific link whitelabel. You can use this endpoint to change a link whitelabel's default status.** +## Create a parse setting -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +**This endpoint allows you to create a new inbound parse setting.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by Twilio SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). -### PATCH /whitelabel/links/{id} +### POST /user/webhooks/parse/settings ```python data = { - "default": True + "hostname": "myhostname.com", + "send_raw": False, + "spam_check": True, + "url": "http://email.myhosthame.com" } -id = "test_url_param" -response = sg.client.whitelabel.links._(id).patch(request_body=data) +response = sg.client.user.webhooks.parse.settings.post(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Retrieve a Link Whitelabel - -**This endpoint allows you to retrieve a specific link whitelabel.** +## Retrieve all parse settings -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +**This endpoint allows you to retrieve all of your current inbound parse settings.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by Twilio SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). -### GET /whitelabel/links/{id} +### GET /user/webhooks/parse/settings ```python -id = "test_url_param" -response = sg.client.whitelabel.links._(id).get() +response = sg.client.user.webhooks.parse.settings.get() print(response.status_code) print(response.body) print(response.headers) ``` -## Delete a Link Whitelabel - -**This endpoint allows you to delete a link whitelabel.** +## Update a parse setting -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +**This endpoint allows you to update a specific inbound parse setting.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by Twilio SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). -### DELETE /whitelabel/links/{id} +### PATCH /user/webhooks/parse/settings/{hostname} ```python -id = "test_url_param" -response = sg.client.whitelabel.links._(id).delete() +data = { + "send_raw": True, + "spam_check": False, + "url": "http://newdomain.com/parse" +} +hostname = "test_url_param" +response = sg.client.user.webhooks.parse.settings._(hostname).patch(request_body=data) print(response.status_code) print(response.body) print(response.headers) ``` -## Validate a Link Whitelabel - -**This endpoint allows you to validate a link whitelabel.** +## Retrieve a specific parse setting -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +**This endpoint allows you to retrieve a specific inbound parse setting.** -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by Twilio SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). -### POST /whitelabel/links/{id}/validate +### GET /user/webhooks/parse/settings/{hostname} ```python -id = "test_url_param" -response = sg.client.whitelabel.links._(id).validate.post() +hostname = "test_url_param" +response = sg.client.user.webhooks.parse.settings._(hostname).get() print(response.status_code) print(response.body) print(response.headers) ``` -## Associate a Link Whitelabel +## Delete a parse setting -**This endpoint allows you to associate a link whitelabel with a subuser account.** +**This endpoint allows you to delete a specific inbound parse setting.** -Link whitelables can be associated with subusers from the parent account. This functionality allows -subusers to send mail using their parent's link whitelabels. To associate a link whitelabel, the parent account -must first create a whitelabel and validate it. The parent may then associate that whitelabel with a subuser via the API or the Subuser Management page in the user interface. +The inbound parse webhook allows you to have incoming emails parsed, extracting some or all of the content, and then have that content POSTed by Twilio SendGrid to a URL of your choosing. For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Webhooks/parse.html). -Email link whitelabels allow all of the click-tracked links you send in your emails to include the URL of your domain instead of sendgrid.net. +### DELETE /user/webhooks/parse/settings/{hostname} -For more information, please see our [User Guide](https://sendgrid.com/docs/API_Reference/Web_API_v3/Whitelabel/links.html). -### POST /whitelabel/links/{link_id}/subuser +```python +hostname = "test_url_param" +response = sg.client.user.webhooks.parse.settings._(hostname).delete() +print(response.status_code) +print(response.body) +print(response.headers) +``` +## Retrieves Inbound Parse Webhook statistics. + +**This endpoint allows you to retrieve the statistics for your Parse Webhook usage.** + +Twilio SendGrid's Inbound Parse Webhook allows you to parse the contents and attachments of incoming emails. The Parse API can then POST the parsed emails to a URL that you specify. The Inbound Parse Webhook cannot parse messages greater than 20MB in size, including all attachments. + +There are a number of pre-made integrations for the Twilio SendGrid Parse Webhook which make processing events easy. You can find these integrations in the [Library Index](https://sendgrid.com/docs/Integrate/libraries.html#-Webhook-Libraries). + +### GET /user/webhooks/parse/stats ```python -data = { - "username": "jane@example.com" -} -link_id = "test_url_param" -response = sg.client.whitelabel.links._(link_id).subuser.post(request_body=data) +params = {'aggregated_by': 'day', 'limit': 'test_string', 'start_date': '2016-01-01', 'end_date': '2016-04-01', 'offset': 'test_string'} +response = sg.client.user.webhooks.parse.stats.get(query_params=params) print(response.status_code) print(response.body) print(response.headers) diff --git a/examples/senderauthentication/senderauthentication.py b/examples/senderauthentication/senderauthentication.py index e799b2a1b..f842d9302 100644 --- a/examples/senderauthentication/senderauthentication.py +++ b/examples/senderauthentication/senderauthentication.py @@ -6,7 +6,7 @@ sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) ################################################## -# Create a domain whitelabel. # +# Create a domain authentication. # # POST /whitelabel/domains # data = { @@ -27,7 +27,7 @@ print(response.headers) ################################################## -# List all domain whitelabels. # +# List all domain authentications. # # GET /whitelabel/domains # params = {'username': 'test_string', 'domain': 'test_string', @@ -38,7 +38,7 @@ print(response.headers) ################################################## -# Get the default domain whitelabel. # +# Get the default domain authentication. # # GET /whitelabel/domains/default # response = sg.client.whitelabel.domains.default.get() @@ -47,7 +47,7 @@ print(response.headers) ################################################## -# List the domain whitelabel associated with the given user. # +# List the domain authentication associated with the given user. # # GET /whitelabel/domains/subuser # response = sg.client.whitelabel.domains.subuser.get() @@ -56,7 +56,7 @@ print(response.headers) ################################################## -# Disassociate a domain whitelabel from a given user. # +# Disassociate a domain authentication from a given user. # # DELETE /whitelabel/domains/subuser # response = sg.client.whitelabel.domains.subuser.delete() @@ -65,7 +65,7 @@ print(response.headers) ################################################## -# Update a domain whitelabel. # +# Update a domain authentication. # # PATCH /whitelabel/domains/{domain_id} # data = { @@ -79,7 +79,7 @@ print(response.headers) ################################################## -# Retrieve a domain whitelabel. # +# Retrieve a domain authentication. # # GET /whitelabel/domains/{domain_id} # domain_id = "test_url_param" @@ -89,7 +89,7 @@ print(response.headers) ################################################## -# Delete a domain whitelabel. # +# Delete a domain authentication. # # DELETE /whitelabel/domains/{domain_id} # domain_id = "test_url_param" @@ -99,7 +99,7 @@ print(response.headers) ################################################## -# Associate a domain whitelabel with a given user. # +# Associate a domain authentication with a given user. # # POST /whitelabel/domains/{domain_id}/subuser # data = { @@ -113,7 +113,7 @@ print(response.headers) ################################################## -# Add an IP to a domain whitelabel. # +# Add an IP to a domain authentication. # # POST /whitelabel/domains/{id}/ips # data = { @@ -126,7 +126,7 @@ print(response.headers) ################################################## -# Remove an IP from a domain whitelabel. # +# Remove an IP from a domain authentication. # # DELETE /whitelabel/domains/{id}/ips/{ip} # id_ = "test_url_param" @@ -137,7 +137,7 @@ print(response.headers) ################################################## -# Validate a domain whitelabel. # +# Validate a domain authentication. # # POST /whitelabel/domains/{id}/validate # id_ = "test_url_param" @@ -147,7 +147,7 @@ print(response.headers) ################################################## -# Create an IP whitelabel # +# Create a reverse DNS record # # POST /whitelabel/ips # data = { @@ -161,7 +161,7 @@ print(response.headers) ################################################## -# Retrieve all IP whitelabels # +# Create a reverse DNS record # # GET /whitelabel/ips # params = {'ip': 'test_string', 'limit': 1, 'offset': 1} @@ -171,7 +171,7 @@ print(response.headers) ################################################## -# Retrieve an IP whitelabel # +# Retrieve a reverse DNS record # # GET /whitelabel/ips/{id} # id_ = "test_url_param" @@ -181,7 +181,7 @@ print(response.headers) ################################################## -# Delete an IP whitelabel # +# Delete a reverse DNS record # # DELETE /whitelabel/ips/{id} # id_ = "test_url_param" @@ -191,7 +191,7 @@ print(response.headers) ################################################## -# Validate an IP whitelabel # +# Validate a reverse DNS record # # POST /whitelabel/ips/{id}/validate # id_ = "test_url_param" @@ -201,7 +201,7 @@ print(response.headers) ################################################## -# Create a Link Whitelabel # +# Create a Link Branding # # POST /whitelabel/links # data = { @@ -217,7 +217,7 @@ print(response.headers) ################################################## -# Retrieve all link whitelabels # +# Retrieve all link brandings # # GET /whitelabel/links # params = {'limit': 1} @@ -227,7 +227,7 @@ print(response.headers) ################################################## -# Retrieve a Default Link Whitelabel # +# Retrieve a Default Link Branding # # GET /whitelabel/links/default # params = {'domain': 'test_string'} @@ -237,7 +237,7 @@ print(response.headers) ################################################## -# Retrieve Associated Link Whitelabel # +# Retrieve Associated Link Branding # # GET /whitelabel/links/subuser # params = {'username': 'test_string'} @@ -247,7 +247,7 @@ print(response.headers) ################################################## -# Disassociate a Link Whitelabel # +# Disassociate a Link Branding # # DELETE /whitelabel/links/subuser # params = {'username': 'test_string'} @@ -257,7 +257,7 @@ print(response.headers) ################################################## -# Update a Link Whitelabel # +# Update a Link Branding # # PATCH /whitelabel/links/{id} # data = { @@ -270,7 +270,7 @@ print(response.headers) ################################################## -# Retrieve a Link Whitelabel # +# Retrieve a Link Branding # # GET /whitelabel/links/{id} # id_ = "test_url_param" @@ -280,7 +280,7 @@ print(response.headers) ################################################## -# Delete a Link Whitelabel # +# Delete a Link Branding # # DELETE /whitelabel/links/{id} # id_ = "test_url_param" @@ -290,7 +290,7 @@ print(response.headers) ################################################## -# Validate a Link Whitelabel # +# Validate a Link Branding # # POST /whitelabel/links/{id}/validate # id_ = "test_url_param" @@ -300,7 +300,7 @@ print(response.headers) ################################################## -# Associate a Link Whitelabel # +# Associate a Link Branding # # POST /whitelabel/links/{link_id}/subuser # data = { diff --git a/use_cases/domain_authentication.md b/use_cases/domain_authentication.md index 7ae7f2e22..d34470b39 100644 --- a/use_cases/domain_authentication.md +++ b/use_cases/domain_authentication.md @@ -1,4 +1,4 @@ -# How to Setup a Domain Whitelabel +# How to Setup a Domain Authentication You can find documentation for how to setup a domain authentication via the UI [here](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication) and via API [here](https://github.com/sendgrid/sendgrid-python/blob/master/USAGE.md#sender-authentication). From 6882972add406ac0434008e6a2672564edfd8150 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Mon, 27 Jul 2020 17:11:48 -0500 Subject: [PATCH 308/462] chore: update README to reflect default branch rename --- README.md | 2 ++ README.rst | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 9bc604525..9ac2fbc3b 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ [![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) +**The default branch name for this repository has been changed to `main` as of 07/27/2020.** + **This library allows you to quickly and easily use the 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). diff --git a/README.rst b/README.rst index 5a2ef9898..3ef04e638 100644 --- a/README.rst +++ b/README.rst @@ -9,6 +9,8 @@ **NEW:** +**The default branch name for this repository has been changed to `main` as of 07/27/2020.** + - Subscribe to email `notifications`_ for releases and breaking changes. - Version 6.X release is a BREAKING CHANGE from version 5.X, please see the `release notes`_ for details. - Send SMS messages with `Twilio`_. From 1192e939a745fa1e427d13c46d842836576558f5 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Tue, 28 Jul 2020 09:56:35 -0500 Subject: [PATCH 309/462] chore: update CI config to use new default branch name --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 489a4a788..8c158b2b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,7 @@ deploy: notifications: slack: - if: branch = master + if: branch = main on_pull_requests: false on_success: never on_failure: change From 182ad7a8d627a1482239c921c8157c6994e4d9dc Mon Sep 17 00:00:00 2001 From: Twilio Date: Mon, 3 Aug 2020 22:32:00 +0000 Subject: [PATCH 310/462] docs: Update templated markdown docs to use new default branch name --- ISSUE_TEMPLATE.md | 6 +++++- PULL_REQUEST_TEMPLATE.md | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index a4b1cc240..fb2e15cef 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,5 +1,9 @@ ### Issue Summary @@ -21,6 +25,6 @@ A summary of the issue and the environment in which it occurs. If suitable, incl ``` ### Technical details: -* sendgrid-python version: +* sendgrid-python version: * python version: diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 215059a96..a86818029 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -23,7 +23,7 @@ A short description of what this PR does. - [ ] I have made a material change to the repo (functionality, testing, spelling, grammar) - [ ] I have read the [Contribution Guidelines](CONTRIBUTING.md) and my PR follows them - [ ] I have titled the PR appropriately -- [ ] I have updated my branch with the master branch +- [ ] I have updated my branch with the main branch - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added necessary documentation about the functionality in the appropriate .md file - [ ] I have added inline documentation to the code I modified From ba6dec464666d1bf98435d9f0bc277ed2cba6534 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 5 Aug 2020 21:24:36 +0000 Subject: [PATCH 311/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94ea6ef9c..af7fc752d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-08-05] Version 6.4.5 +-------------------------- +**Library - Docs** +- [PR #926](https://github.com/sendgrid/sendgrid-python/pull/926): remove last references of "whitelabel". Thanks to [@childish-sambino](https://github.com/childish-sambino)! + + [2020-07-22] Version 6.4.4 -------------------------- **Library - Chore** From 23c33b05ecbbdbd16246bc50bd607e5dd1534789 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 5 Aug 2020 21:28:56 +0000 Subject: [PATCH 312/462] Release 6.4.5 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index e55bc1c67..9bf999671 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.4.4' +__version__ = '6.4.5' From 3c3277b5f10da343e0a399a915425dd5f98e41f7 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 11 Aug 2020 10:29:40 -0700 Subject: [PATCH 313/462] chore: update GitHub branch references to use HEAD (#929) --- CHANGELOG.md | 14 +++--- CONTRIBUTING.md | 8 ++-- FIRST_TIMERS.md | 6 +-- README.md | 40 ++++++++-------- README.rst | 48 +++++++++---------- TROUBLESHOOTING.md | 6 +-- USAGE.md | 2 +- app.json | 2 +- examples/helpers/README.md | 10 ++-- examples/helpers/mail_example.py | 6 +-- examples/helpers/stats/stats_example.py | 2 +- examples/mail/mail.py | 2 +- proposals/mail-helper-refactor.md | 12 ++--- sendgrid/helpers/inbound/README.md | 14 +++--- sendgrid/helpers/inbound/templates/index.html | 2 +- sendgrid/helpers/mail/README.md | 2 +- sendgrid/helpers/stats/README.md | 4 +- use_cases/django.md | 2 +- use_cases/domain_authentication.md | 2 +- use_cases/email_stats.md | 2 +- use_cases/error_handling.md | 6 +-- 21 files changed, 96 insertions(+), 96 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af7fc752d..c367217e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -152,7 +152,7 @@ All notable changes to this project will be documented in this file. ### Added - Twilio SendGrid branding -- [Twilio SMS example](https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/sms.md) +- [Twilio SMS example](use_cases/sms.md) - Updated CLA process ## [6.0.0] - 2019-04-02 ## @@ -162,7 +162,7 @@ All notable changes to this project will be documented in this file. - The `Mail` helper signature has changed. - Setting up a `SendGridAPIClient` has changed. -Please see the [use cases documentation](https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/README.md) for implemenation details. +Please see the [use cases documentation](use_cases/README.md) for implemenation details. This refactor was based on [this issue](https://github.com/sendgrid/sendgrid-python/issues/347). BIG thanks to all of those who [participated](https://github.com/sendgrid/sendgrid-python/issues/323) in shaping this release. @@ -198,7 +198,7 @@ In particular, BIG THANKS to: - [PR #488](https://github.com/sendgrid/sendgrid-python/pull/488): Fix similar code issue in mail.py helper (BIG thanks to [@adiman9](https://github.com/adiman9)) - [PR #496](https://github.com/sendgrid/sendgrid-python/pull/496): Fix issues in sendgrid/helpers/mail/mail.py (BIG thanks to [@galihmelon](https://github.com/galihmelon)) - [PR #510](https://github.com/sendgrid/sendgrid-python/pull/510): Fix similar code issue in sendgrid/helpers/mail/mail.py (BIG thanks to [@nanspro](https://github.com/nanspro)) -- [PR #524](https://github.com/sendgrid/sendgrid-python/pull/524): Fix master failure on travis (relating to ASM raise-assertion). (BIG thanks to [@extemporalgenome](https://github.com/extemporalgenome)) +- [PR #524](https://github.com/sendgrid/sendgrid-python/pull/524): Fix main failure on travis (relating to ASM raise-assertion). (BIG thanks to [@extemporalgenome](https://github.com/extemporalgenome)) ### Added - [PR #666](https://github.com/sendgrid/sendgrid-python/pull/666): Created First-timers.md File (BIG thanks to [@jaykay12](https://github.com/jaykay12)) @@ -316,7 +316,7 @@ Removed the trailing white spaces. Big thanks to [Siddaram Halli](https://github ## [5.0.0] - 2017-08-11 ### BREAKING CHANGE - The breaking change actually happened in [version 4.2.1](https://github.com/sendgrid/sendgrid-python/releases/tag/v4.2.1), where I mistakenly applied a patch version bump. See issues #328 and #321 for details. -- This version (5.0.0) replaces error handling via HTTPError from urllib in favor of custom error handling via the [HTTPError class](https://github.com/sendgrid/python-http-client/blob/master/python_http_client/exceptions.py) as was the case in version 4.2.0. +- This version (5.0.0) replaces error handling via HTTPError from urllib in favor of custom error handling via the [HTTPError class](https://github.com/sendgrid/python-http-client/blob/HEAD/python_http_client/exceptions.py) as was the case in version 4.2.0. ## [4.2.1] - 2017-08-03 ## ### Fixed @@ -337,7 +337,7 @@ Removed the trailing white spaces. Big thanks to [Siddaram Halli](https://github ### BREAKING CHANGE - Pull #244 [refactor helpers using property getter/setter](https://github.com/sendgrid/sendgrid-python/pull/244/files) - Big thanks to [Denis Vlasov](https://github.com/denis90) for the pull request! -- The changes break the implementation of the [Mail Helper](https://github.com/sendgrid/sendgrid-python/tree/master/sendgrid/helpers/mail) `Mail()` class +- The changes break the implementation of the [Mail Helper](sendgrid/helpers/mail) `Mail()` class - `set_from()` is now the property `from_email` - `set_subject()` is now the property `subject` - `set_template_id()` is now the property `template_id` @@ -426,7 +426,7 @@ Removed the trailing white spaces. Big thanks to [Siddaram Halli](https://github ## [3.2.2] - 2016-08-23 ## ### Added - Table of Contents in the README -- Added a [USE_CASES.md](https://github.com/sendgrid/sendgrid-python/blob/master/USE_CASES.md) section, with the first use case example for transactional templates +- Added a [USE_CASES.md](USE_CASES.md) section, with the first use case example for transactional templates ## [3.2.1] - 2016-08-17 ## ### Fixed @@ -448,7 +448,7 @@ Removed the trailing white spaces. Big thanks to [Siddaram Halli](https://github ## [3.1.8] - 2016-07-25 ## ### Added -- [Troubleshooting](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md) section +- [Troubleshooting](TROUBLESHOOTING.md) section ## [3.1.7] - 2016-07-25 ## ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ed57a5722..92d72897e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -87,7 +87,7 @@ source .env #### Execute -See the [examples folder](https://github.com/sendgrid/sendgrid-python/tree/master/examples) to get started quickly. +See the [examples folder](examples) to get started quickly. If testing from the root directory of this repo, create a new file (e.g. test.py) and replace `import sendgrid` with `from sendgrid import *` @@ -104,7 +104,7 @@ If testing from the root directory of this repo, create a new file (e.g. test.py The PR must pass all the tests before it is reviewed. -All test files are in the [`test`](https://github.com/sendgrid/sendgrid-python/test) directory. For the purposes of contributing to this repo, please update the [`test_sendgrid.py`](https://github.com/sendgrid/sendgrid-python/tree/master/test/test_sendgrid.py) file with unit tests as you modify the code. +All test files are in the [`test`](test) directory. For the purposes of contributing to this repo, please update the [`test_sendgrid.py`](test/test_sendgrid.py) file with unit tests as you modify the code. The integration tests require a Twilio SendGrid mock API in order to execute. We've simplified setting this up using Docker to run the tests. You will just need [Docker Desktop](https://docs.docker.com/get-docker/) and `make`. @@ -165,7 +165,7 @@ Please run your code through: 5. Locally merge (or rebase) the upstream development branch into your topic branch: ```bash - git pull [--rebase] upstream master + git pull [--rebase] upstream main ``` 6. Push your topic branch up to your fork: @@ -175,7 +175,7 @@ Please run your code through: ``` 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) - with a clear title and description against the `master` branch. All tests must be passing before we will review the PR. + with a clear title and description against the `main` branch. All tests must be passing before we will review the PR. ## Code Reviews If you can, please look at open PRs and review them. Give feedback and help us merge these PRs much faster! If you don't know how, GitHub has some great [information on how to review a Pull Request](https://help.github.com/articles/about-pull-request-reviews/). diff --git a/FIRST_TIMERS.md b/FIRST_TIMERS.md index b0636fb81..4bf1b26e3 100644 --- a/FIRST_TIMERS.md +++ b/FIRST_TIMERS.md @@ -54,13 +54,13 @@ Kindly make sure, to check for any duplicate issues raised by fellow contributor **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 5:** Run all test locally, [for more info](CONTRIBUTING.md#testing) -**Step 6:** Locally merge your the upstream development branch into your topic-branch using `git pull [--rebase] upstream master` +**Step 6:** Locally merge your the upstream development branch into your topic-branch using `git pull [--rebase] upstream main` **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. +**Step 8:** Open a Pull Request with clear title and description against the main branch. ## Be Patient & Wait for Reviews diff --git a/README.md b/README.md index 9ac2fbc3b..d8049ca23 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -![SendGrid Logo](https://uiux.s3.amazonaws.com/2016-logos/email-logo%402x.png) +![SendGrid Logo](twilio_sendgrid_logo.png) -[![Travis Badge](https://travis-ci.org/sendgrid/sendgrid-python.svg?branch=master)](https://travis-ci.org/sendgrid/sendgrid-python) -[![codecov](https://img.shields.io/codecov/c/github/sendgrid/sendgrid-python/master.svg?style=flat-square&label=Codecov+Coverage)](https://codecov.io/gh/sendgrid/sendgrid-python) +[![Travis Badge](https://travis-ci.org/sendgrid/sendgrid-python.svg?branch=main)](https://travis-ci.org/sendgrid/sendgrid-python) +[![codecov](https://img.shields.io/codecov/c/github/sendgrid/sendgrid-python/main.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.md) @@ -15,7 +15,7 @@ Version 3.X.X+ of this library provides full support for all SendGrid [Web API v3](https://sendgrid.com/docs/API_Reference/Web_API_v3/index.html) endpoints, including the new [v3 /mail/send](https://sendgrid.com/blog/introducing-v3mailsend-sendgrids-new-mail-endpoint). -This library represents the beginning of a new path for SendGrid. We want this library to be community driven and SendGrid led. We need your help to realize this goal. To help make sure we are building the right things in the right order, we ask that you create [issues](https://github.com/sendgrid/sendgrid-python/issues) and [pull requests](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md) or simply upvote or comment on existing issues or pull requests. +This library represents the beginning of a new path for SendGrid. We want this library to be community driven and SendGrid led. We need your help to realize this goal. To help make sure we are building the right things in the right order, we ask that you create [issues](https://github.com/sendgrid/sendgrid-python/issues) and [pull requests](CONTRIBUTING.md) or simply upvote or comment on existing issues or pull requests. Please browse the rest of this README for further detail. @@ -82,7 +82,7 @@ pip install sendgrid ## 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_example.py#L9) is a full example): +The following is the minimum needed code to send an email with the [/mail/send Helper](sendgrid/helpers/mail) ([here](examples/helpers/mail_example.py#L9) is a full example): ### With Mail Helper Class @@ -103,11 +103,11 @@ 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_example.py#L16) is an example of how to add it. +The `Mail` constructor creates a [personalization object](https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/personalizations.html) for you. [Here](examples/helpers/mail_example.py#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): +The following is the minimum needed code to send an email without the /mail/send Helper ([here](examples/mail/mail.py#L27) is a full example): ```python import sendgrid @@ -170,29 +170,29 @@ print(response.headers) # Processing Inbound Email -Please see [our helper](https://github.com/sendgrid/sendgrid-python/tree/master/sendgrid/helpers/inbound) for utilizing our Inbound Parse webhook. +Please see [our helper](sendgrid/helpers/inbound) for utilizing our Inbound Parse webhook. # Usage - [SendGrid Documentation](https://sendgrid.com/docs/API_Reference/index.html) -- [Library Usage Documentation](https://github.com/sendgrid/sendgrid-python/tree/master/USAGE.md) -- [Example Code](https://github.com/sendgrid/sendgrid-python/tree/master/examples) +- [Library Usage Documentation](USAGE.md) +- [Example Code](examples) - [How-to: Migration from v2 to v3](https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/how_to_migrate_from_v2_to_v3_mail_send.html) -- [v3 Web API Mail Send Helper](https://github.com/sendgrid/sendgrid-python/tree/master/sendgrid/helpers/mail) - build a request object payload for a v3 /mail/send API call. -- [Processing Inbound Email](https://github.com/sendgrid/sendgrid-python/tree/master/sendgrid/helpers/inbound) +- [v3 Web API Mail Send Helper](sendgrid/helpers/mail) - build a request object payload for a v3 /mail/send API call. +- [Processing Inbound Email](sendgrid/helpers/inbound) # Use Cases -[Examples of common API use cases](https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/README.md), such as how to send an email with a transactional template. +[Examples of common API use cases](use_cases/README.md), such as how to send an email with a transactional template. # Announcements 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. +All updates to this library are documented in our [CHANGELOG](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 @@ -202,19 +202,19 @@ If you are interested in the future direction of this project, please take a loo # How to Contribute -We encourage contribution to our libraries (you might even score some nifty swag), please see our [CONTRIBUTING](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md) guide for details. +We encourage contribution to our libraries (you might even score some nifty swag), please see our [CONTRIBUTING](CONTRIBUTING.md) guide for details. Quick links: -- [Feature Request](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md#feature-request) -- [Bug Reports](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md#submit-a-bug-report) -- [Improvements to the Codebase](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md#improvements-to-the-codebase) -- [Review Pull Requests](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md#code-reviews) +- [Feature Request](CONTRIBUTING.md#feature-request) +- [Bug Reports](CONTRIBUTING.md#submit-a-bug-report) +- [Improvements to the Codebase](CONTRIBUTING.md#improvements-to-the-codebase) +- [Review Pull Requests](CONTRIBUTING.md#code-reviews) # Troubleshooting -Please see our [troubleshooting guide](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md) for common library issues. +Please see our [troubleshooting guide](TROUBLESHOOTING.md) for common library issues. # About diff --git a/README.rst b/README.rst index 3ef04e638..88d0fb280 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -.. image:: https://github.com/sendgrid/sendgrid-python/raw/master/twilio_sendgrid_logo.png +.. image:: https://github.com/sendgrid/sendgrid-python/raw/HEAD/twilio_sendgrid_logo.png :target: https://www.sendgrid.com @@ -103,7 +103,7 @@ Hello Email ----------- The following is the minimum needed code to send an email with the `/mail/send Helper`_ -(`here `__ is a full example): +(`here `__ is a full example): With Mail Helper Class ~~~~~~~~~~~~~~~~~~~~~~ @@ -129,13 +129,13 @@ With Mail Helper Class print(str(e)) The ``Mail`` constructor creates a `personalization object`_ for you. -`Here `__ is an example of how to add it. +`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): +(`here `__ is a full example): .. code:: python @@ -262,47 +262,47 @@ License `The MIT License (MIT)`_ .. _notifications: https://dx.sendgrid.com/newsletter/python -.. _Twilio: https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/sms.md +.. _Twilio: https://github.com/sendgrid/sendgrid-python/blob/HEAD/use_cases/sms.md .. _release notes: https://github.com/sendgrid/sendgrid-python/releases/tag/v6.0.0 .. _Web API v3: https://sendgrid.com/docs/API_Reference/Web_API_v3/index.html .. _v3 /mail/send: https://sendgrid.com/blog/introducing-v3mailsend-sendgrids-new-mail-endpoint .. _issues: https://github.com/sendgrid/sendgrid-python/issues -.. _pull requests: https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md +.. _pull requests: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md .. _free level: https://sendgrid.com/free?source=sendgrid-python .. _Twilio account: https://www.twilio.com/try-twilio?source=sendgrid-python .. _SENDGRID_API_KEY: https://app.sendgrid.com/settings/api_keys .. _Python-HTTP-Client: https://github.com/sendgrid/python-http-client .. _ECDSA-Python: https://github.com/starkbank/ecdsa-python -.. _/mail/send Helper: https://github.com/sendgrid/sendgrid-python/tree/master/sendgrid/helpers/mail +.. _/mail/send Helper: https://github.com/sendgrid/sendgrid-python/tree/HEAD/sendgrid/helpers/mail .. _personalization object: https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/personalizations.html .. _Fluent Interface: https://sendgrid.com/blog/using-python-to-implement-a-fluent-interface-to-any-rest-api/ -.. _our helper: https://github.com/sendgrid/sendgrid-python/tree/master/sendgrid/helpers/inbound +.. _our helper: https://github.com/sendgrid/sendgrid-python/tree/HEAD/sendgrid/helpers/inbound .. _Twilio SendGrid Documentation: https://sendgrid.com/docs/API_Reference/index.html -.. _Library Usage Documentation: https://github.com/sendgrid/sendgrid-python/tree/master/USAGE.md -.. _Example Code: https://github.com/sendgrid/sendgrid-python/tree/master/examples +.. _Library Usage Documentation: https://github.com/sendgrid/sendgrid-python/tree/HEAD/USAGE.md +.. _Example Code: https://github.com/sendgrid/sendgrid-python/tree/HEAD/examples .. _`How-to: Migration from v2 to v3`: https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/how_to_migrate_from_v2_to_v3_mail_send.html -.. _v3 Web API Mail Send Helper: https://github.com/sendgrid/sendgrid-python/tree/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 +.. _v3 Web API Mail Send Helper: https://github.com/sendgrid/sendgrid-python/tree/HEAD/sendgrid/helpers/mail +.. _Processing Inbound Email: https://github.com/sendgrid/sendgrid-python/tree/HEAD/sendgrid/helpers/inbound +.. _Examples of common API use cases: https://github.com/sendgrid/sendgrid-python/blob/HEAD/use_cases/README.md .. _breaking changes: https://github.com/sendgrid/sendgrid-python/issues/217 -.. _CHANGELOG: https://github.com/sendgrid/sendgrid-python/blob/master/CHANGELOG.md +.. _CHANGELOG: https://github.com/sendgrid/sendgrid-python/blob/HEAD/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 -.. _troubleshooting guide: https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md -.. _The MIT License (MIT): https://github.com/sendgrid/sendgrid-python/blob/master/LICENSE.md - -.. |Travis Badge| image:: https://travis-ci.org/sendgrid/sendgrid-python.svg?branch=master +.. _CONTRIBUTING: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md +.. _Feature Request: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md#feature-request +.. _Bug Reports: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md#submit-a-bug-report +.. _Improvements to the Codebase: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md#improvements-to-the-codebase +.. _Review Pull Requests: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md#code-reviews +.. _troubleshooting guide: https://github.com/sendgrid/sendgrid-python/blob/HEAD/TROUBLESHOOTING.md +.. _The MIT License (MIT): https://github.com/sendgrid/sendgrid-python/blob/HEAD/LICENSE.md + +.. |Travis Badge| image:: https://travis-ci.org/sendgrid/sendgrid-python.svg?branch=main :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 +.. |codecov| image:: https://img.shields.io/codecov/c/github/sendgrid/sendgrid-python/main.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/ diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index f3a64ec3c..589aa2e6a 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -34,7 +34,7 @@ In the first case, SENDGRID_API_KEY is in reference to the name of the environme ## Error Messages -HTTP exceptions are defined in the [`python_http_client` package](https://github.com/sendgrid/python-http-client/blob/master/python_http_client/exceptions.py). +HTTP exceptions are defined in the [`python_http_client` package](https://github.com/sendgrid/python-http-client/blob/HEAD/python_http_client/exceptions.py). To read the error message returned by SendGrid's API in Python 2.X: @@ -100,7 +100,7 @@ If you are using a [requirements file](https://pip.readthedocs.io/en/1.1/require ## Versioning Convention -We follow the MAJOR.MINOR.PATCH versioning scheme as described by [SemVer.org](http://semver.org). Therefore, we recommend that you always pin (or vendor) the particular version you are working with to your code and never auto-update to the latest version. Especially when there is a MAJOR point release, since that is guaranteed to be a breaking change. Changes are documented in the [CHANGELOG](https://github.com/sendgrid/sendgrid-python/blob/master/CHANGELOG.md) and [releases](https://github.com/sendgrid/sendgrid-python/releases) section. +We follow the MAJOR.MINOR.PATCH versioning scheme as described by [SemVer.org](http://semver.org). Therefore, we recommend that you always pin (or vendor) the particular version you are working with to your code and never auto-update to the latest version. Especially when there is a MAJOR point release, since that is guaranteed to be a breaking change. Changes are documented in the [CHANGELOG](CHANGELOG.md) and [releases](https://github.com/sendgrid/sendgrid-python/releases) section. ## Viewing the Request Body @@ -116,4 +116,4 @@ You can do this right before you call `response = sg.client.mail.send.post(reque # Error Handling -Please review [our use_cases](https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/README.md) for examples of error handling. +Please review [our use_cases](use_cases/README.md) for examples of error handling. diff --git a/USAGE.md b/USAGE.md index b6d044676..978b675ab 100644 --- a/USAGE.md +++ b/USAGE.md @@ -2105,7 +2105,7 @@ For more detailed information about how to use the v3 Mail Send endpoint, please ### POST /mail/send -This endpoint has a helper, check it out [here](https://github.com/sendgrid/sendgrid-python/blob/master/sendgrid/helpers/mail/README.md). +This endpoint has a helper, check it out [here](sendgrid/helpers/mail/README.md). ```python data = { diff --git a/app.json b/app.json index b63428351..e25064d12 100644 --- a/app.json +++ b/app.json @@ -6,6 +6,6 @@ "inbound parse" ], "website": "http://www.sendgrid.com", - "repository": "https://github.com/sendgrid/sendgrid-python/tree/master", + "repository": "https://github.com/sendgrid/sendgrid-python", "logo": "https://sendgrid.com/brand/sg-twilio/SG_Twilio_Lockup_RGBx1.png" } \ No newline at end of file diff --git a/examples/helpers/README.md b/examples/helpers/README.md index 2eebbe421..df1746b52 100644 --- a/examples/helpers/README.md +++ b/examples/helpers/README.md @@ -24,7 +24,7 @@ The `Content` class takes mainly two parameters: MIME type and the actual conten mail = Mail(from_email, to_email, subject, content) ``` After adding the above we create a mail object using `Mail` class, it takes the following parameters: email address to send from, subject line of emails, email address to send to, content of the message. -For more information on parameters and usage, see [here](https://github.com/sendgrid/sendgrid-python/blob/master/sendgrid/helpers/mail/mail.py) +For more information on parameters and usage, see [here](../mail/mail.py) ### Creating Personalizations @@ -44,13 +44,13 @@ To create attachments, we use the `Attachment` class and make sure the content i attachment.content = ("TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNl" "Y3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12") ``` -Another example: [Link](https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/attachment.md) +Another example: [Link](../../use_cases/attachment.md) ### Managing Settings -To configure settings in mail, you can use the `MailSettings` class. The class takes some [parameters](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 configure settings in mail, you can use the `MailSettings` class. The class takes some [parameters](../mailsettings/mailsettings.py#L1)(such as bcc_settings, bypass_list_management, footer_settings, sandbox_mode) -To add tracking settings, you can add `TrackingSettings` class. See example [here](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). +To add tracking settings, you can add `TrackingSettings` class. See example [here](mail_example.py#L118) and parameters and usage [here](../trackingsettings/trackingsettings.py). ### Sending email @@ -60,7 +60,7 @@ After you have configured every component and added your own functions, you can data = build_kitchen_sink() response = sg.send(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! +Make sure you have [environment variable](../../TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key) set up! Full example [here](https://github.com/sendgrid/sendgrid-python/blob/0b683169b08d3a7c204107cd333be33053297e74/examples/helpers/mail_example.py#L203). ### Using Dynamic Templates diff --git a/examples/helpers/mail_example.py b/examples/helpers/mail_example.py index c57520a9e..de1648df9 100644 --- a/examples/helpers/mail_example.py +++ b/examples/helpers/mail_example.py @@ -81,7 +81,7 @@ def get_mock_personalization_dict(): def build_attachment1(): """Build attachment mock. Make sure your content is base64 encoded before passing into attachment.content. - Another example: https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/attachment.md""" + Another example: https://github.com/sendgrid/sendgrid-python/blob/HEAD/use_cases/attachment.md""" attachment = Attachment() attachment.content = ("TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNl" "Y3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12") @@ -311,7 +311,7 @@ def build_kitchen_sink(): 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 + # https://github.com/sendgrid/sendgrid-python/blob/HEAD/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key message = build_hello_email() sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) response = sendgrid_client.send(message=message) @@ -322,7 +322,7 @@ def send_hello_email(): 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 + # https://github.com/sendgrid/sendgrid-python/blob/HEAD/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key message = build_kitchen_sink() sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) response = sendgrid_client.send(message=message) diff --git a/examples/helpers/stats/stats_example.py b/examples/helpers/stats/stats_example.py index ebe24f69f..f22baa5c4 100644 --- a/examples/helpers/stats/stats_example.py +++ b/examples/helpers/stats/stats_example.py @@ -6,7 +6,7 @@ # NOTE: you will need to move this file to the root directory of this project to execute properly. # Assumes you set your environment variable: -# See: https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key +# See: https://github.com/sendgrid/sendgrid-python/blob/HEAD/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) diff --git a/examples/mail/mail.py b/examples/mail/mail.py index d59901d1d..d2ccc80f0 100644 --- a/examples/mail/mail.py +++ b/examples/mail/mail.py @@ -27,7 +27,7 @@ # v3 Mail Send # # POST /mail/send # # This endpoint has a helper, check it out -# [here](https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/README.md). +# [here](https://github.com/sendgrid/sendgrid-python/blob/HEAD/use_cases/README.md). data = { "asm": { diff --git a/proposals/mail-helper-refactor.md b/proposals/mail-helper-refactor.md index 91ecfe878..70798c262 100644 --- a/proposals/mail-helper-refactor.md +++ b/proposals/mail-helper-refactor.md @@ -2,7 +2,7 @@ # Send a Single Email to a Single Recipient -The following code assumes you are storing the API key in an [environment variable (recommended)](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. +The following code assumes you are storing the API key in an [environment variable (recommended)](../TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. This is the minimum code needed to send an email. @@ -29,7 +29,7 @@ except SendGridException as e: # Send a Single Email to Multiple Recipients -The following code assumes you are storing the API key in an [environment variable (recommended)](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. +The following code assumes you are storing the API key in an [environment variable (recommended)](../TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. ```python import os @@ -57,7 +57,7 @@ except Exception as e: # Send Multiple Emails to Multiple Recipients -The following code assumes you are storing the API key in an [environment variable (recommended)](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. +The following code assumes you are storing the API key in an [environment variable (recommended)](../TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. ```python import os @@ -100,7 +100,7 @@ except Exception as e: # Kitchen Sink - an example with all settings used -The following code assumes you are storing the API key in an [environment variable (recommended)](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. +The following code assumes you are storing the API key in an [environment variable (recommended)](../TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. ```python import os @@ -246,7 +246,7 @@ except Exception as e: # Attachments -The following code assumes you are storing the API key in an [environment variable (recommended)](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. +The following code assumes you are storing the API key in an [environment variable (recommended)](../TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. ```python import os @@ -275,7 +275,7 @@ except Exception as e: # Transactional Templates -The following code assumes you are storing the API key in an [environment variable (recommended)](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. +The following code assumes you are storing the API key in an [environment variable (recommended)](../TROUBLESHOOTING.md#environment). If you don't have your key stored in an environment variable, you can assign it directly to `apikey` for testing purposes. For this example, we assume you have created a [transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). Following is the template content we used for testing. diff --git a/sendgrid/helpers/inbound/README.md b/sendgrid/helpers/inbound/README.md index 93d0817b6..79e5b4544 100644 --- a/sendgrid/helpers/inbound/README.md +++ b/sendgrid/helpers/inbound/README.md @@ -36,7 +36,7 @@ pip install -r requirements.txt python sendgrid/helpers/inbound/send.py ./sendgrid/helpers/inbound/sample_data/default_data.txt ``` -More sample data can be found [here](https://github.com/sendgrid/sendgrid-python/tree/master/sendgrid/helpers/inbound/sample_data). +More sample data can be found [here](sample_data). View the results in the first terminal. @@ -71,7 +71,7 @@ Next, send an email to [anything]@inbound.yourdomain.com, then look at the termi Get a [Heroku](https://www.heroku.com) account. -[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/sendgrid/sendgrid-python/tree/master) +[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/sendgrid/sendgrid-python/tree/main) [Setup your MX records.](https://sendgrid.com/docs/Classroom/Basics/Inbound_Parse_Webhook/setting_up_the_inbound_parse_webhook.html#-Setup) Depending on your domain name host, you may need to wait up to 48 hours for the settings to propagate. @@ -100,7 +100,7 @@ heroku git:remote -a [name-of-your-app] ---make changes--- git add . git commit -m "update configuration" -git push heroku master +git push heroku main ``` @@ -127,12 +127,12 @@ This module is used to send sample test data. It is useful for testing and devel Tests are located in the root of this project in the /test folder: -- [test_config.py](https://github.com/sendgrid/sendgrid-python/blob/master/test/test_config.py) -- [test_parse.py](https://github.com/sendgrid/sendgrid-python/blob/master/test/test_parse.py) +- [test_config.py](../../../test/test_config.py) +- [test_parse.py](../../../test/test_parse.py) -Learn about testing this code [here](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md#testing). +Learn about testing this code [here](../../../CONTRIBUTING.md#testing). # Contributing -If you would like to contribute to this project, please see our [contributing guide](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md). Thanks! +If you would like to contribute to this project, please see our [contributing guide](../../../CONTRIBUTING.md). Thanks! diff --git a/sendgrid/helpers/inbound/templates/index.html b/sendgrid/helpers/inbound/templates/index.html index 0de3f44f3..7cbede381 100644 --- a/sendgrid/helpers/inbound/templates/index.html +++ b/sendgrid/helpers/inbound/templates/index.html @@ -5,6 +5,6 @@

You have successfully launched the server!

- Check out the documentation on how to use this software to utilize the SendGrid Inbound Parse webhook. + Check out the documentation on how to use this software to utilize the SendGrid Inbound Parse webhook. \ No newline at end of file diff --git a/sendgrid/helpers/mail/README.md b/sendgrid/helpers/mail/README.md index 29e21dea5..bbf0a2ece 100644 --- a/sendgrid/helpers/mail/README.md +++ b/sendgrid/helpers/mail/README.md @@ -6,5 +6,5 @@ Please complete the [installation steps](https://github.com/sendgrid/sendgrid-py ## Usage -- For the most common use cases, please see [these examples](https://github.com/sendgrid/sendgrid-python/tree/master/use_cases) +- For the most common use cases, please see [these examples](../../../use_cases) - The complete v3 API Documentation can be found [here](https://sendgrid.com/docs/API_Reference/api_v3.html) diff --git a/sendgrid/helpers/stats/README.md b/sendgrid/helpers/stats/README.md index 4ef738410..f1591ecce 100644 --- a/sendgrid/helpers/stats/README.md +++ b/sendgrid/helpers/stats/README.md @@ -2,9 +2,9 @@ # 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). +Run the [example](../../../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. +- See the [examples](../../../examples/helpers/stats) for complete working examples. - [Documentation](https://sendgrid.com/docs/API_Reference/Web_API_v3/Stats/index.html) diff --git a/use_cases/django.md b/use_cases/django.md index c71175faa..809b491a6 100644 --- a/use_cases/django.md +++ b/use_cases/django.md @@ -196,7 +196,7 @@ Commit the code to the repository and deploy it to Heroku using Git. ``` $ git add . $ git commit -am "Create simple Hello Email Django app using Twilio SendGrid" -$ git push heroku master +$ git push heroku main ``` After that, let's verify if our app is working or not by accessing the root domain of your Heroku app. You should see the page says "Email Sent!" and on the Activity Feed page in the Twilio SendGrid dashboard, you should see a new feed with the email you set in the code. diff --git a/use_cases/domain_authentication.md b/use_cases/domain_authentication.md index d34470b39..4740f92f9 100644 --- a/use_cases/domain_authentication.md +++ b/use_cases/domain_authentication.md @@ -1,5 +1,5 @@ # How to Setup a Domain Authentication -You can find documentation for how to setup a domain authentication via the UI [here](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication) and via API [here](https://github.com/sendgrid/sendgrid-python/blob/master/USAGE.md#sender-authentication). +You can find documentation for how to setup a domain authentication via the UI [here](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication) and via API [here](../USAGE.md#sender-authentication). Find more information about all of Twilio SendGrid's domain authentication related documentation [here](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication). diff --git a/use_cases/email_stats.md b/use_cases/email_stats.md index 7799710d2..c40ccb882 100644 --- a/use_cases/email_stats.md +++ b/use_cases/email_stats.md @@ -1,5 +1,5 @@ # How to View Email Statistics -You can find documentation for how to view your email statistics via the UI [here](https://app.sendgrid.com/statistics) and via API [here](https://github.com/sendgrid/sendgrid-python/blob/master/USAGE.md#stats). +You can find documentation for how to view your email statistics via the UI [here](https://app.sendgrid.com/statistics) and via API [here](../USAGE.md#stats). Alternatively, we can post events to a URL of your choice via our [Event Webhook](https://sendgrid.com/docs/API_Reference/Webhooks/event.html) about events that occur as Twilio SendGrid processes your email. \ No newline at end of file diff --git a/use_cases/error_handling.md b/use_cases/error_handling.md index eaeabad18..187703b60 100644 --- a/use_cases/error_handling.md +++ b/use_cases/error_handling.md @@ -1,9 +1,9 @@ # 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. +[Custom exceptions](https://github.com/sendgrid/python-http-client/blob/HEAD/python_http_client/exceptions.py) for `python_http_client` are now supported. -Please see [here](https://github.com/sendgrid/python-http-client/blob/master/python_http_client/exceptions.py) for a list of supported exceptions. +Please see [here](https://github.com/sendgrid/python-http-client/blob/HEAD/python_http_client/exceptions.py) for a list of supported exceptions. -There are also email specific exceptions located [here](https://github.com/sendgrid/sendgrid-python/blob/master/sendgrid/helpers/mail/exceptions.py) +There are also email specific exceptions located [here](../sendgrid/helpers/mail/exceptions.py) ```python import os From b94fa7ad53fa821f80ae07a496e87c2d578e79bf Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Tue, 18 Aug 2020 10:11:13 -0500 Subject: [PATCH 314/462] docs: clean up and reconcile first timers documentation --- FIRST_TIMERS.md | 148 ++++++++++++++++++---------------- static/img/github-fork.png | Bin 0 -> 15189 bytes static/img/github-sign-up.png | Bin 0 -> 116981 bytes 3 files changed, 79 insertions(+), 69 deletions(-) create mode 100644 static/img/github-fork.png create mode 100644 static/img/github-sign-up.png diff --git a/FIRST_TIMERS.md b/FIRST_TIMERS.md index 4bf1b26e3..f6406f783 100644 --- a/FIRST_TIMERS.md +++ b/FIRST_TIMERS.md @@ -1,69 +1,79 @@ -## Welcome to the Twilio 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 Twilio 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 Twilio SendGrid -Step 1: Get yourself Access to Twilio SendGrid API Service absolutely free from [here](https://sendgrid.com/free/?source=sendgrid-python) \ -Step 2: Get familiar with Twilio SendGrid Service -- Prerequisites are Python version 2.6, 2.7, 3.4, 3.5 or 3.6 -- Set up your [Twilio 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 Twilio 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 -Twilio 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 -- **Setting up Locally** - - Step 1: Install the Prerequistes: Any Version of Python (2.6 through 3.6) and both [python_http_client](https://github.com/sendgrid/python-http-client) and [ecdsa_python](https://github.com/starkbank/ecdsa-python) - - Step 2: Get a local copy of repository using `git clone https://github.com/sendgrid/sendgrid-python.git` - - Step 3: Set your [Twilio 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](CONTRIBUTING.md#testing) - -**Step 6:** Locally merge your the upstream development branch into your topic-branch using `git pull [--rebase] upstream main` - -**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 main branch. - - -## Be Patient & Wait for Reviews -Kindly be patient & follow the suggestions as provided by the peer reviewers. Make required amendments & changes to the PR as asked. - -## [Explore New Issues to work upon](https://github.com/sendgrid/sendgrid-python/labels/difficulty%3A%20easy) +# How To Contribute to Twilio SendGrid Repositories via GitHub +Contributing to the Twilio SendGrid repositories is easy! All you need to do is find an open issue (see the bottom of this page for a list of repositories containing open issues), fix it and submit a pull request. Once you have submitted your pull request, the team can easily review it before it is merged into the repository. + +To make a pull request, follow these steps: + +1. Log into GitHub. If you do not already have a GitHub account, you will have to create one in order to submit a change. Click the Sign up link in the upper right-hand corner to create an account. Enter your username, password, and email address. If you are an employee of Twilio SendGrid, please use your full name with your GitHub account and enter Twilio SendGrid as your company so we can easily identify you. + + + +2. __[Fork](https://help.github.com/fork-a-repo/)__ the [sendgrid-python](https://github.com/sendgrid/sendgrid-python) repository: + + + +3. __Clone__ your fork via the following commands: + +```bash +# Clone your fork of the repo into the current directory +git clone https://github.com/your_username/sendgrid-python +# Navigate to the newly cloned directory +cd sendgrid-python +# Assign the original repo to a remote called "upstream" +git remote add upstream https://github.com/sendgrid/sendgrid-python +``` + +> Don't forget to replace *your_username* in the URL by your real GitHub username. + +4. __Create a new topic branch__ (off the main project development branch) to contain your feature, change, or fix: + +```bash +git checkout -b +``` + +5. __Commit your changes__ in logical chunks. + +Please adhere to these [git commit message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) or your code is unlikely be merged into the main project. Use Git's [interactive rebase](https://help.github.com/articles/interactive-rebase) feature to tidy up your commits before making them public. Probably you will also have to create tests (if needed) or create or update the example code that demonstrates the functionality of this change to the code. + +6. __Locally merge (or rebase)__ the upstream development branch into your topic branch: + +```bash +git pull [--rebase] upstream main +``` + +7. __Push__ your topic branch up to your fork: + +```bash +git push origin +``` + +8. __[Open a Pull Request](https://help.github.com/articles/creating-a-pull-request/#changing-the-branch-range-and-destination-repository/)__ with a clear title and description against the `main` branch. All tests must be passing before we will review the PR. + +## Important notice + +Before creating a pull request, make sure that you respect the repository's constraints regarding contributions. You can find them in the [CONTRIBUTING.md](CONTRIBUTING.md) file. + +## Repositories with Open, Easy, Help Wanted, Issue Filters + +* [Python SDK](https://github.com/sendgrid/sendgrid-python/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [PHP SDK](https://github.com/sendgrid/sendgrid-php/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [C# SDK](https://github.com/sendgrid/sendgrid-csharp/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Ruby SDK](https://github.com/sendgrid/sendgrid-ruby/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Node.js SDK](https://github.com/sendgrid/sendgrid-nodejs/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Java SDK](https://github.com/sendgrid/sendgrid-java/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Go SDK](https://github.com/sendgrid/sendgrid-go/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Python STMPAPI Client](https://github.com/sendgrid/smtpapi-python/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [PHP STMPAPI Client](https://github.com/sendgrid/smtpapi-php/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [C# STMPAPI Client](https://github.com/sendgrid/smtpapi-csharp/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Ruby STMPAPI Client](https://github.com/sendgrid/smtpapi-ruby/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Node.js STMPAPI Client](https://github.com/sendgrid/smtpapi-nodejs/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Java STMPAPI Client](https://github.com/sendgrid/smtpapi-java/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Go STMPAPI Client](https://github.com/sendgrid/smtpapi-go/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Python HTTP Client](https://github.com/sendgrid/python-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [PHP HTTP Client](https://github.com/sendgrid/php-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [C# HTTP Client](https://github.com/sendgrid/csharp-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Java HTTP Client](https://github.com/sendgrid/java-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Ruby HTTP Client](https://github.com/sendgrid/ruby-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Go HTTP Client](https://github.com/sendgrid/rest/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Open API Definition](https://github.com/sendgrid/sendgrid-oai/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [DX Automator](https://github.com/sendgrid/dx-automator/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Documentation](https://github.com/sendgrid/docs/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) diff --git a/static/img/github-fork.png b/static/img/github-fork.png new file mode 100644 index 0000000000000000000000000000000000000000..6503be362193808dc19bce69d8ccdf518d9ef93f GIT binary patch literal 15189 zcmbumWmFwYw=UdRa0>*26Wj^z?!nzHSa4_ImLS31o#5_nA-KC+@P)g>xAwd5J?9(a z{<-6x^m+tEH&iX1pylJ&jUTt0!d(; zMP<|w5D-?j6gMHa7%mc8E~@tCE+AtkGeGs5tBZ@7lS$wtA^?yAGU6g??#m}@AU&+R zC*YhqMiwCkR%Q_i3Jw;obhH)MuEzE01H-NEMNxBUPVUE_9H19aGE(2^6epssF*1?=E`ts&|&`rN^ltNGdkgNf_bN4-8R+T#fv|Pyg%M zcrE~v1Ihkf3iF@BPd}=`{}*LGD#ah6|EcWLH6|u(PWzY-)3rD{s`K*SN?Bh>hS;3E{afw_OJYZH)K}u+{y`(Sxgi|k!q-P z{&-{E(*7(P1tX$v`xI2WWFsITpsqeQ!6EW-Zh|yx48;;&Gmu5RxsB7HSdMCBWTHq(4FwfdXav>=Nx7o4S0&R+%k@ujqbz^=sxm>? zc@c`2)<1o5B20q~J^)Y89U%EL-_9|8V^7P<%2J|v*2DUt{DSg#={d%Ybg>r+B5MWz zVfTX-yQdeYl_1O3jAF9ae`SqyetY? z(>^I7fs-}Rjk@CJwxp3MCZs#cd8%C_l>5?PIxOaL8F)dRErD(vUxW9iib~j+e8C0&O6druu_Tk$X9C)Z${Zg|ae$K|o+_ zZT(%@r>nTr_eC;%?6v9IFjekdPL5JeUY;nAI4MTsOri1Gg=70$rnBy1d@28)allHj zn$MYOFeq#{_!tho&%O%n6XpK6Df+eS)TP4wos2WzjeSHhxZ>VB3sh=fOYtn%Sf?14 z1VXYGy9K+~daB5bcOkp|0(((gKm0-!QpXXS*D0kaa_Na;U(Yl3>AI%fhf5s_a$Bb* zX88aB7cGzLmYo;&(6_(9%wKdfVKCy$&R(3{yd|9X^aj##Yf8H78p`(LM{D{K|b-7SI;b}*Zq-aQt4^C((8 z85Gh~+O~Vw7dQt$?GYZ=;61&nkl5euJYp_WV|0@>j2WCw7db?WcbYkR?%N{VlByn# zK!*W$EIntK(8x9bcJG0K3JTnDZv65_ZA@%p9)*7*GDzkq*eWNxA$x;PTTiH=3?%v&Ens2r}@p<$1G&QOK=QC zuCe!zuX+w%4sw1`y0L!i=^aKyCq}bzku#P%7{<$o=eu^QcO^c&?VUfeoF3jvR(#Hy z=TuJY4`FmB*!*U9a)0x~jQT4q08C#a)33qv@TK{2{IT_Q_yO_sVXvz@9G!QXZUXP* zv>t$5vV>BfOr$mD2^a96meF8``5@6NiF)idyiv-1FVnr`%R!g-(1VZ&5Q)O7Ll zg#{(mct*DjJ7dg%c;YF4y6*615VsN(g@{)zE{=-d$BL$@1iB&2cX~|tvE*HcWhPQI zn$>^jmwwCPj+Zo`2&fQZWOT7LPg;suaJd4pMz>jc^e8ux2ALTj!*&khf|7URzy)Ej zb$9KpqGsi{ldfbtvFHz~akOTDmHLNde6>G%+usd}US7W8ECYQU^UW7L3ldbX@lg7b zOT}GR3Kw0fZrB0|-?KPWevck-+ z8ff`t!erlZc@4@&jlui>{nd=(uqpBwPJyR&dpJ~TVd=pI?FP4U?0jZ&F9WxArmt$A z5$9?SrPkn4z_PMm(M4OGxpO6dk*Yd?TXp7p)h$f~0Me){zEmBN8pj*c(-HstMk7W? z6Ox(9$+BrfgghQyrlMut`Lk%F2ewmPIM_^BQ|YhUKnt-R} z*;l`Gp~e)08*OjaU_P5YG7Z!7zMGLq8|EjShSSnWA_1#8a04(o&?mw3%do3+9T^v< zJ9Mk^EW(cUYmd0D&aO}}XT969%mc0lqadCMH>8TXKh6Q4Mi$(t-fQrsta(o8ecPuX}QAo%N+8Y}$Xt^dMxcTXI7IpSyK>$FDaiT;YI( zer)^k9Jxzy?M4(5U{regC(iGK7CuHk(cc^-;oR?ea#gCE4)TluFgEuc4?arP&O)tO zy9}G3v4cz_^|@mU+WG_F(5R%!7b93#?NkD%C|eb(F3=w7U9Juo`Ew98npZ)6nJZL1 zP{Ne0e$85vdCTOm^BzB|A7RUj|MSkLykR|4F@jVDEvLxUqoR@s@MqZh<{O$4r$z&h z*1#UtMO6Upzj$E%cSaj-kgJox34JH2;^#aXs6+wPgGpk0HkipSBMiQ%R#JR0cW3Ou z#i7xE`XDanJ)o4z9*+B07*3_uPw}=kE=^bYy$UoyS2+{W`!wT^4SzhS4FlNARt-z& z$!O@F6bNZ6CXPcDzxb@}sHCZ3Fo(jbn^3X%)m%XP&_oU&`s%4ADZ4RAtrW4D#ASj5 zq;`XbeoL!315m)Iy;H@Dj`^)hTEIN-V2JvOTH zv_pvac&#Z~G?ziI+GsKdO4e7e{j>-bsQx-fCp%);>T;+ucMrBS0runLoqMw4u`{7? zl5mC&m4^bg`Hl6QSUyJ7v#6buoM|=D^Yf~HVRKAr_`1R{wuyK;zy`#xmmvP&ujzx z97R8&Z@r4`ZV1XIH&3{)1f*xj(!#i%U8GVFEOQB1Q-)QUJjT#(V z!J7Z%+Iz{$qQh|E06(oo`v_RvouF`9M*B9Wq<@OR+4vbd^~ku~Xi7nk`o90P!;N-o zq13BP@&kZ^f^vCvg-(L_-1T+^?N2TRod|Q38e`7wK7AcKns$XtmATPycuykpz{)$Z zZX1w&W+di6U=7dEy0!V@_q%Sua{bfyGD#fCvwOQOp5<#3>iv~zAyeiLfro>ZV$@Yx zRry{?C8c~Ri1!ZY%ll3`HqGXs{Pw4h{3X1ro4Moe%y=%& znTU?+5mui;(BZ^Y#cbmv*#DKct^(0NZ1JPxC-t#0Jqb|_8bhnSw3m{w!TY8n9GjEb zGJ26X|YgfJk7j8lxRpcHjq-5zdkuJZ9W?{1rm=2a;OvEMYgHF<4m*$TX%OU1#t z5AwtNiUqg4=5;uCg(Ct5(0CI$lw6CT(L$vS5XS3<^V!!H(xHQsq5Ka`&bTBMqGCRVY*d zqtAP7lL?sj#-TbLy2f1M3jie=O=)2UO zyk9r~n-$EMtk~3~tQyx9H{gD`ujO(QlXj!o!#Yxi@lj`*RDeka3D0pz)Zk2p^Ih%E z0YwZ|pP9e8BE0NYBx9L^6dN@W!un^eTlcp|@|t!$As-8vZo(Z9xTq%B1NS*8BVt=_ z6{mST^1*Z??uLslJ6i8~|A9LA?E>GDCgN$Maz@4;*MsMIShIuZsC~Zt<*;{qli#(b zOu52jk+lTHzp@t^Xhlm#tZlE?129%^H=aOu5@nLk%Tyw#Y=!oB8>M;EMyg?73heGS zbm|~$cQILHw4H2S7K@n+`n#`e%|88+$0kD@gl)!$3+y>O45)$!LnC}$z9&H_>lvG> zf&vds&s>=kFgY_&q;JvZ{9YS3GmJtuW7-O*1b#Y@=|Xycr4WG8qXguZYUUfpjqCOLa9cFOK`9wP>H`uI zTHKT8GQOygjlmW_DkwJ`>ovqN1XetYRq72voN7|3%p9lHA*LtkcxlTApqmW;Xux)CsBkO==hF{fCOGzW;|vw+9V8{1(rgvETqWULrCIlHHAq1!CU)%URbIbI zhH4JIjr9a)^6reDo{4bd;-^{fhpKvNn@}Tjm{DI{X=f&5#FB4Uy?1>LvI18Iejo~O zrz~EwKkVB}46#eey%|>vbWGs92;#Ad1&KbaSRb~yO(7J<+}`&qPOv#q8YwRY;tSa^ z&={>fqRy4E+a0Gw9xZT8(Y|&t&2Suz=ZnntRa^9SVj8$7z#Gvd2C(l*cb~2D;8KI+ z{pL9lq5Zci#k3jr{!$9QmsvAdw6d~-zddoQZmMF#dR!QZww+o9{nfaCMcs!Ip7H)M zv4^_tUdS)c3(Vvc(Zc_NdHL-*(_9!Vw5B#~{qYL?_$m_YZHT?2b=S+g0`r1r%1I}c^ zE2{&IBhvHo5fmrj79-c)3MH65Erk{)P{$xD>}XCm`T*q&ey4mlm*SN8>_8%;KdHV zWKRNnm38;;J&XH&H5EPy`jH~4*53i|AG&Qw@8527UaaN&1)lHp$`3?% zQz@CFHPH z0cp9Bm!jx`fPZ1`CsnANA>(NPyH+QCbB|L4>U`!AbvVwX_tCY&<%nedpar>i503E3 z_?)Fs9V7;%p|QREnBY0&@}hEpllaLBq^i-xK!>lVA*wjJxuxy$T%E^u^Y(US-(+@l z`1m;ITuCx4aePHYxcZuJN3wzEa#e)NdE3KQ>PbQi{CZHlwd27{RL+x&rW&NbHQbKT#VpJ({Rs(+jz~c?hgSq$kxel7)9u_(|8;v$lLBQvO|ik z{&*CYHd4lZcgJwVFMJ=xu@r2OVRN^P6E7NYS%Yh*#$8*h?(Sq|cS z2K-@S25>bLMp!0&SNh5*z%qycBTI?aRLBDhG+iXR?(@b?bZ)l&!RwGdRKi^9+Ni`# z2tE|o?P&fC;?I^%(;w+6lcj)^0 zCqPL#hG~`X60BwX&=OJwTC9=N!Uo_z&-$iR?yvrJC_SL1#*Im(pgGG@SE8XW=Yh^Y zMJS*&-I6juvhDd24i`Bl;>6TR2rSNLy!2{Y0deleCs%cJ<4kEd(Zg}h5?izm1sFi+ z5R=tdI>aFg>h|~RbK0*~kJ?iL$Ln=P_9V$7Fz_h&+&Q&VT59!P6U4h4>0EZ7IV*jB zXIj|jdw|y~%0yEGzEi@ZFf#De7Sk`>vyS!LQjxY3RqwjRy<8eH%kA(RaCGncW=c4t zqyTt*-n4*z+A9Tr>!Qn~mI2$mNBfEv5E>P=UhS(-9~^AQx4%76<}!*C-XJ4MNpqiVB$EmFOvT2O2B&g}KXy9@ zb@tu8Ev6+WXj)G@>$<%uAGh8N@5MUYgkTa_xtKmlYs$)EMKfu6C)po5UrrM8nO)J} z^^`cH=bZgc6{F%^8usmKxm{X={m5f_WaJ)`sqZcYz0hN&C)r1#*v_EUdr`?keV$i+6? z*YFBrQiu7CL8pY;9!n*u1$cl^)5mUvUn;c1Qhu!pPijPw*}KJ6q_IKS%$*ermVR%g zL@eIl6#MLh)DS8j= zj!TK(QtKb}Rq+KhErze zm=$(AJtZ)kQjydkYWZx2w!kFI6idu!n1v|JHJwquar_^c&T5>9Cj^zCk=|dc^J|bW zX~r!{bc40V()9rp_LtRgTmnuz9E2ZgbgozZsJ+3cWqkBHie3cEiPRJEmM|8n-$jj$ z?M3+BcHk!Uj=iQoyoW5wNWJtEUIz}@rT|vo>2NY!{K$8|o1Z`?sE^(!R$?nYXMcJK z>epog2oy{g?TNVOf9+Y(<7#eDHAfO0aoX8j+5KE8_iEq!^PJj^`#P2!z|Q=)sZFKw zP!j{Zhc}MT0cuq~PnGppohr+0Niw&Hx=0jKV3F$F$lN(@@=_P_w5FD({Zw;FT%4Ac z2K*C80Ho!bnaAvYy#--op)e+r!$d3hFqOOWN0|&3D&w{Cc zwV>w+@nOMCuFWGjO?cmpExW8(3S9?n1d{id&wZQWTs%o67WDj8lAD`fc6Bg8?epZV zKT*E9mKkFwe+ z)6M`kjI=Y!T65+=4S0X?A)HoH@Srtn5Bn1Ufi;@EnE^bGUk&V9(LtY zB%&s20x64i@AT-AkcPOjewj(d#Dd;BBqo;{Dv#VJcou{UUo*+3L8k_OiRD&Psd3)A z2JUkpH~c6Cq73FHaiDpbUuFKK(K$8l7xf zxNS}X$!R(6+61o&&>`%+w-rSn6a{4F5aO$LgOKR$vd8M%rPQ$oDH%$ge(YI0(aI}3 zbExy+=zfx?#jlYXWJ>CW2F$LV6vLkm#Ut+mlN&*{(}DlY zkNkO}u|pfyZ0cpBG!4+X{jte6FVIYq$caX^o~_>sY9u)e{Z39$x>WzNtQVIo zkA|4$+wnp&TzBoZTSdlSLO{N#%|uf}mqMBW$HLqk06+vKWklUsowrm&=K3s zUYowtW#^=s1G?44sR5r$ywSAg_Ja7h-0>Tx?9c(PJCbJi3tXIVf!>$4P9Ht|^?n^^ z6Eq!PBRCtxRe?7g2RTo#cMREmWy+KbOJ0l?OoYC!uNC@l>BPJvmoT>~B9dq%0Ba@D zikE!q-lyQ1kf;peYQ{N>fxhov1q|tkPy_;Fq!tT>9s{fiSZhli&uH8>&1c;D`?3$i z`Ge9P;Z)5IH|8=jmgMB$tgYn58j5$^EfxY(bCyxEwh}@HlxrsPUSO)IEG9qH!5fQTO&onfr!)SUN-$xM^o3IBN9Ius#t{bS(&`9rNmmJ%xHp+PREN=8f$HSsmhe0!`sWI zCvN6!`K3xUs{;Irgl{eN#~YNgNkJLBuV$wOk;lcT68CkbyrpoNNNkfcW6a)jwIjnT zi4=5FZ_TqN$_pHOP0a)A_Cu;V(b-1t@Oq_%eH$^RGE4Sp z|A&MJ1Dt7n6wW2uGk#r6Jmy3dOGVbs5dgs7{f7&n@Mt)?;4$7sZ1Ke;{DVS##CO7C zV7DnN3<=7=WbfBPSUBT{L5MUB$u^GhKcVHvzl%#t%S%gN2?eRo3|+4qkD!2YG&hJU z$Kn|BWxP2058ooUh0VQ}3PYVto)osV4COp;4ye99c;u2jxJw?Bw`K|7=2R03_qn22 z2wTs=?6l-BbzWSu0y)z<4UHlQxoGlJ<;dTK-u?|>E0*)}3W}Nxd==}sk`PHoMhm6& zT&{ope!~^HF#G55j6HYAlo3m&#&ShI!_{!;94Uxl>XSVU7 z{VeiPJz^hM3{K4dU&6t^^o7UO;?iR2Un&t|Z5Xl?baZqnq!C~X43+Z#&=O`pa;B@R zU4NlVG^SZjk)xt#U@&D({a0ifZRaI>;U6wU?w!3#jH#QQL8>rKvfq@@A2~@r{)3n? z^P|JVlG0M&N<^!>V$N&!pJ@1VZa*!y`M}9H3Bv!97w|2y^5^FRO3{vu|K&XV|JV$% zTMx|tk-BDyT?`=UfSLH;6p8u4zQkEw|k zN0y@f4DP}GSjg;L_77tLVtzC_tG!`r#UI}o9;bgNB-8S zlmB||F;0mE_TVZeDap>5Iz`;gR^F1>v`bN5)@RG#UJT?Q+j}fo-0nq zNRJiT_r67MUFk|KS7~t9sYZ(o1A+3rWqO6KhDMUlnSp@Uwe*Da#Neps1FQPdd!Ot5 z?TFFx=!S&kPxNnIpLI00qoXQ${%MnjCibuz2#wGpCHsq-5KJ+uY$R5?B}jtv@_(k zJCIHdMvCMbQU9nxy+bi=$2KfiSI%a(-+Q@;HkYIMMA2LtcsKBDiL*t1GPA15w!!s* zu~|-)p;zAhcjWKVcPHVQp||=E3soC~ri|ar>9LVwr74m}I5;?#jc#@>k?nfT}#Rwb{4fhM0@R^DA&28^;!Ju#S z;jM)e+y(ktwd5w{Z_}TSamHQ+woyo>o3r!Q2iAW8kWtifWtXIQ@AV2&@D}~6V%RlP z6bRLAu@n|+p_~OFhEVPNXnkxG5`8X{EqcBW(c_K)-9^Tc=6i#V>p;hsT$CTTd<^RgNlFX>L}hgOi1F%P97c_ zZnxFB--CWHV8%|cva)Ve@B;qTDb-UW21I}~i_ppX;bacr+9jWV%?lpjI*gv_I;!|) zFsIB{bl5WjbBd$cHRODEM>G_~Re}x!Ga-drCt1FDFx4QL-dQpIDDsiBZb6~$6X1{i zfkF?p0|R65-0*n1y)=^C= zudR!mUNx%m@kgJE2|boZ68vvK41Ty!)@`# ztZrNMJrZo_Vu1n*n3)KXBBdE&8dI+l41z>J*Yl5x?B5)I-V~K*wd4L^S|m_@S8Z;0 z-9-QVh^P~%iV;sn$^h?$$_C*+ex$N-zxq2{3RBZv*`>X}Y5CHP!qch$VKWY*yf7bq;QY2UOUv+m}AgW-&nccGpAJH%GkKNPX`OyNWPl zJzHCi-}o#|wX{(_;^7Csoc+qGJe>(0r0n!@;v4CF&UKk3AV3o=rgmND{0{XcyF>suq-x*TRATw3koM`M81E0 z9mh(**8uGp(+*7Uylbdf)h96qJSDvpZOIgFR1z-F`{t?MkdNVEu%oY7Pk8f#?y_wc z-q>?NQph?bByOt-r2gKR7qtt=nFK{TPTpI7Ri%$%$7QsIK1m zz${k`h{=?CZFXDwd=e};coH{%CrJ+Y!$R1$!(U0l-c_6%K31?KyyCUqwKjL%J@$&M za4GCk1QS0N%&Wtq?y|frh#8mY7uv11SQjO$Yr9YW?D{CCF@8E=#67X%`)4F6$@OK@ zHFqR$tSHd!y+KPI=fmR zonkwQ?cdX;F~9IPSPi{J`x?{u4Yt6x0Worfg$BN+r-?fGW+y&bgBK5M{;q6eA+MXQ z4qsJnW`iMw6}>~nMbRbRSqbrYYh{#+o}P`oCP|G>~7 z0$%yt^t(LT6o!azMnq3o}e- zGsrWoS`$uq+)Po$jFuj70PD#D>-Z$3+YeJ#F55Ye7anhJ7?ag z)97p#IC6Ar=9=ngs%qj(vp~6vpb-_tiua%HaUC#h3?k@_ER#B_=_?jcZfBQoBVL0Z zFD)GwNj@eO!8!*_ekSH`gfE-Iia;$WA8K!c*vznm6U`I7`>Wo@12XOdaU>n|S z9sQD=F$49XQr_!YT-HRcq9ab%HdQ~FGL~hr!)0d6^8ZSzOQ<(w znU$2PsH+`SdHQ+X1cm9d<299)X_J$=Eb8B_8}le0-<@yf2E84)Hq|>F&P*)DiIw>oiG^K- z?LG{po3Fd`g_3^puYPg+oA2N4ij3mUiF3;YlWA*x_l23cs$;p|WKU6g$(zOKl?Zre zfBbigO8qQB(x6i}XKf?jZsDQT`Q9|y9055!%*KZI7F^RaTHdS{J z7dacAq1eN;*{kLrwIM}qVO!&anVtzw?CRh%zBHbT9oZS3E zZ#T~R$Yd>N1)|&=lIBVShQ|W;Q-)`AF_0>^-Yo>SB&XFX*O3}dNNanwF;-nw)$8Ta zb?npGYjOB`=Ros>7xVaWXG$*-=SV)TxIVYn&E}F_dM8K;-ajw| zq$2IJ5Sv7_Crxe)Zk#V`;st=*adS)d`oCjm)grU^u*$9jpyXJv729#Pr!x`P1+*IX)CERL7THZ*>0i@mwJ2+ZM8&IJd z?uQ`&&@wON4UDJCJX>^<$7__N*YU+;U>QWp6fHf9PX-AF;GB^y*QZ>sF8?!-MS`Q zk;L7Xx^u7ZlWV214<6n~$T&(@gAxU%sb zCmvqCD6}TGArU309d$-Ok<{YdOx;D#cj7X>BCdWs%Cv^C0KN^*C%P?7G^(q*@S*;s zMWtcAVbRq1U?&JAomUMvq+z_9OgcH<=^IJ|wZqyxs}V2FA>Zr{PPK4tFr& zriHqp5$WcY45w83yeV;uCpho-bM0`hhu>P~L*9L+3Bs@70M|F=8JsHpYev4^y}I9$ zc>^4q7^H&?R8WI?&tHuW2psqZLyBAQ*Ql4|TrsfKgU;pMn7SUac~q{0DO?y%q~y{Y z>`v6SkEPJ-E{X{MnK%$S1zn=&^`@Y8+3CXroUG4F0*p^m1*E^L8^ONUo1MH|)zES2 zBRz8p?oW#??oiUNKP)I z=(73tV|JDuPsZ(Vx*))C{vONP7pXbR;0tt2;OsBaxQa0dQ{#R2$?t&U1hx0T;OJAd z3cW>7E#5Z?$>6s%3V>8Lp6N_~+mH9>kMeU=-zQN+>=8g%Ek%b^V}BkzrijrnZM-NE z?Vka^klo)N+IqnNU%b4=4FL8K=8P~0OWA&7o$KBtJ_OPzRx1q-mUKKBxp;W~x_kZ{ z7`QhP*}vr=!Bg@;0kq3JZW}`Z_0{L3<-ylq|LC`T9HWL3PrXwtU&44)gEcZ=$&_yK z5O_hANh*ACya{TaqU4NWt$l6Bf)&>O)FnqDYptySg+%#a5CyGjj<~ zlUZ{Ar+9%(lOkc)U7RU^0V{GDF1sFMI+fXr+!QQ9kCm4FS{t$HloBZ(ZEk5lf+1zd z?Y!F*6VAMIoOP@5XaB=Vf0xDV*a_;Ni7KICa*-qwpGO4+RdwbM!ZDhpaj|(t@P3!b z-U+nOKrPLQ8D6!^IgSQ|5rSDig`VEZyZvWEYJXH$>uS73=<%LNpuVaz#1#M*TrCD$ zsyp};Y_q+pwGiak+Iq4A2m~^lft;(8#@^?K^nH}IPe4*^Z%Ss{z7`Y!dTfpKt6CLY zGUHpTKg*yzHDv6dPlOT%ZFJjC-*{2;*~dZGISVxjK*jSly;98_veVPc?yl)GGK?Vx z77H4bDbc8?&Oi_-&+BTdb7P_(Hz@WCIg>dBxIq+&Jttp}c)aQnMP}JBfX8N^C(VQP zf}=$}jfDW8Cr7q>3cx=pzAc*c*k!Zu4M{q1rseLB2O9$-(By$7ruf4yU6>q^*2$$05sbD$41jyLCY+ypot<^w)T`zdG-zRMUe0N@+o4~z4ZITB5ajg+|# zj>eakm493AOC>Fioyg-RECO%QGOH%`k_i2D-$#r#?(kuJiR`ZW}9X(#H z=U2(QYxx@vZ}yexCa7+rr3w{&KDW6Z3%`IJa0y+gFVX+>_IpWSfgXKgz<{3^xm}yj zC!`i~GD*^UAAZDpFbICG{;`$$eTF~>XpR}0^m^>cW2|f~wKSJMO+lMIk)OEJcnUEt z*P6|$(Sq3($O_Ll5ML}T@RPqWJm%pOy1t$_^XhX7G@I>+-alSm7@Cz2XzeZM!Gjkg z4%Vk=PO7{TIA&jd7MpF2IC{w{5q#^#JLb7CQ@T&{z@2SdRKVNM_;6o4)v|ZH^w!yidJ1P80?+4LLVzl(AlC9|Dn*6fPa(W za*0g}9zHc9;%B6zlZ%bhJ@wzQmk#8!^LaZ$`{`V#Wpm4pe=$={khsmSQKhJdizK~m zp7+_pNXx9tCwG>lW)vc-#0}DXZKhH~2|ibP=a(puPo}pr3v9&~O-(7;@XW+SMhqlm z!w*nEd`U@Bk+scg!CEvSm#BOsQ}W2h_D?8w{0iHw5*uUV3$jxZJleYI>Tlx?*XumC zBlVoxHuXuhzeib>ER=XP%_{3XuhGhqOq_rSX}f&cwYm_<{~Ll&~Ky9%jFHf`pm7DYSV=XJERm=1S>M@ z7sqViLDP@!+Du;cebfx<8y;B&o==52k@;(t)V#C$<}F?PzC7K`dax)kvilx&k6Wk? zn4$+)!2;!JyB&}Nf-bweyR4}Z6p&;WwITjrq_}8?!1opbEX~tb-{^?+c^QIpcfvwW zH*!d-t0(pteg60nag_Eq_CLV-8G3ZtKj1k;St@7CvQ;Ezwv zB2p@!!7rcBCJ|uzi;I|s%TIeV7k5J^Q;44yt}ZU7PR2nq@DLE+Af$eLSMgXq+wjy; zxqE-S#)e|cpVDA$g`n70hqMnWHfT%Ht|?g7xj977kg=+X)GE}2pR8Vxo#a^PG|NZ_}5s4Ts zS?I;~_V$KF^$CA~MJ_TDJA7GHScwUm3YndvsfC7 zmX}XP{4>cIh4%}BN`(d*A0Hp1e)mUs-(0b1vcw$6#i(M1gtAprlz*+};9n)O3%)C5 z#lfhl7($L9@TE|#>csVbNobZVU2GEcMPy1<X(ZX5dj{rzC~c7N)`glt2jGqX-l+)6UM zn_FU@NQ4vM@Ai1vL-=*PS12^S^~W2scFWtJ&gq_Sb+a-PkB|BuOBG^Tt1!y0yb!di z6(JoR_|q!oPyvh#v6jUCA`9=(5S^=qntoR>e@g8LH;Fkzc=D#w=KejIU78XHr(Mk^ zyna_QF8j@o{*@@K9M9&cOgdQREZM{Y@bLX+SH)gUR=1aH9V53}etc;kAL)Pfj~9yh zz_S8A2q{O?YnK~|@$;|G9?!M<0mTDd$+0!>&$~b3oAf`jqSM}!(f{oVyEMf$Do=sX zOv3LE3n{b(Y1`3X3JL0Y4#r0w!dD3(*Lw>bs)lSh*N{3P)>}Q3irCB%Vf6moc(ruU zq)3Z*3;mw(uOelRDVul9W;k%RGUUT-P~bN>Xg1koazC_WAHKKWIW6CS_b6g&QJ%52 zBrsoo=zX&s+eN?MYAkjCa#8z>pV4h=y?4T_86>gj$}%iJ?0Dbde&r_un)Ke*nj+Gd zZRuH*k{#4e;Jm*B!bLvYu_W<&Y`$dMGQ8wqoC*zkQips|_&XWt8w!Wgx!2|i`igaS zPOPumUr7l3T4Q?n9-KW^34OWwXDrCoK#a`nqIp7lrqX^;x&Uh4Cb?X`#9YZ||Er zKjY`{vU2$FYHGHOKT>6J*PF4*04BxEkAW;geGF}=I^)d&wVtK}H4^QcHxmvHf*71d z8t;k~4yXublC`7svpAMqM%Y6V4OnbcoD-BHxH8W_-A6W!KL#ZmmSU(W4`uPRTx_Fu z6&G}e0f;%g>>`@EL~&2y4WOS`KY8Uyw4yLwUeaGjId7MQQmhm*A5LUOO6g|P+gZ)@ zS^Pa; zDyRPGwY0BYl=FWOy+UkAS8T{h7{dU(Z1C;FDi5<%j_i0d6iR*-Jh`1+HY|paR?Npd zX$0}JRec-F(O%UrtXo^in*{xCzMgVDUr=i#uZl4<_iuzKMH4HKoe_l7fjxRdgJz<-(^NTU2cuUvB zjlDGVAR~%kp^d_3OWa=RZN4x6%jMQ|+Yg4OESPyUtik)BhZDQ=V*-;sorZ+fee!BN zePVveB$08U`XaOzrl=68q~=S>pmUj{^&Y}Z->e&Gw$Nh7t2Ni+%@`Ix>`EX-2XWdV zKmXNDRcK=)Tic!!=s<2N`z==J!^g+)F15CK+eL&F>^*kVA`x)F&H8YPtHQOh8M~i< zjjN4Y-E}91aOE74Prh?)$xX<~8r`)9R{6zPvR!0}Zu#!h_rH42@ChSZMEZ14%>x-+ zwL&lXsKYX(llKK<%^PWP4y+3qEbO zTx`fTiALzD+3|S`E55`#Us~LzfenQS3UfNhXh#P)rU}KH3>-Kkj@x-8Pgr6|8 z9#|cmi~ZKXml?#f=?r78P`hn=`Xkhyc_%@e+v!z)esT~xOQXkGeHrCL(T!;9yGNy) zY}9&Rf_@>7s}Ga*q1$ln_B0a>s_LDm^|Mx?-zeQYp-r}S{<=)2zC5+hmLc%kf)nMI z#(`@qZtw*dk-3SU(nL30KH_e{X2!zzAmM!C!7kyKKD_;B*FUDOk}xUNZfE4$!aAeM zz-;&cgs8vj4$-FY2sA4?iaEVy*kg)!t&K04+*Ct3|!A za+5pE{iLhK8D}}%I?|QR>u3JKFu;&L_yXW*VE(&We5`kf4FieMa<%0TQ|NNp*N)+6 zLwIC)?$jK7RkM-N<_9+%C!yVg9pmm8?jd!|)DRPET&R<*>xi9{lsyxu42+}8zd|)V zJZ1=SKkpygn9Y8+!}Q~9C$xmxGQ4)@<96#jYU@&LArr$mIFC02rc=5!5ik;w*oF0zvf4`{1A)g#<`i6PR;zZy^+uIXG{r z=}`6E%(>PNKR$9D6c5RHmK%!td`uIj7;pM;?-Zjh#DyTPXR7`4R-ij07T zWnHmdR?9mu+OtEG_^ZG9v7oEf2kgOy^_xf#leHe?#oWYF8RQm;Kj_ zi}QU=Mpm(Tj_edRt-`2`{s24OBhaik(j;Z0iMYeNoW!5MniD4#I|H4wE|*J5g2cYD zoBr&e(&isxcnM^i6s)+ia6$)7U*FO(ezvC~P-5iQC`dBFoC@I$9br+i(4DYZuK0-f zutmHc>8`rs53O@!iwNpYke}iZSEvCsS(o58GU0EJ7ecpt5hK)^$$Typp@?2KdZ0o@ z0!8-4g_oIOxq>n9IX5L9;EBcMQ2|M`smHfbWT>7nd4p@l2C5lk29Ezv1QdX{~&IHMP|Gc$UTxSXr`sQ@dx7E0s5DnrTnU1|4q zSjuvAi!_f9yOzfK_aidWJ_FbIq#uX{U}3#EdYhqy!(zx8$Z7yV#;%RK#2JCDb!%MS zjaJ~?Z()2IRbV0Vw_)YhE&dRM_GYnstXp!XI(M%how2xB{OF5~HCf7N=EuZ^{xz1e z^`}Bujby+5BY*0x_r!3A9hDF{t>t=wZs2wxRZjb}0r&4D40i^mVm!c7X~v6H#_V?^ zt|Du}K*uTd>VVjnSEnybu2J85s}XmM*EB&hNxgY~vi7x@sc}bA@x6$WSA>H_YIx(2 zB*+U<#cGwUjzCJ-{+wX(QW@YOG+dkOIO|eB+q6o7=DfPa(`w~!pL5l9tECbBX63=C z&N=RgB?f(f+-Yj(RlL^|2=CM5&*|wjb#3-(e``QX8kMb9Q;}_!+9yt{2ocA_D&2Br zu}luzzEfN6`Ms1h+c;0E5NiLErRx5SqDFsD-bCPU9EeWuI+Hv4p*tIiUug&<-*{MH z-UMo3qW&n+=gx-0URcSF)@RRju$Wv><5faVq|2r|HetiJ9%S^xq2Ve-y&r9dtC5K%@#`H_T^8Vm-7Fm zdF^@f*a}1vM&SwQyYsAAUr0=Lh_hbdtT>OnCeMO+h1cy0%76~G1YU+AuzA^{^8&jV zbM0KR1mkCx^jZsnKV;Fz%nT<^PRy#KwTSOCt4|eQn2j#HACHZBewD$gGp+hPvXBwG z#SC=b(Ven6d7&m>uW9sfp(Rx^@p$!s9fFpw1WYdU{=4tE-qxmy}Rs#V*v9+Mecbjo&7i&CSK&}oKHSq;RY=0(cS zYDpl5jW=5%8P7~C72C$X;@EiRGM#Fr)`8|%!cY9$z5m%!TsW&Q8BNR08<*JnfneF6 zamW9I-X~G0g)E50I`IPFvwr2Kp4e?jwq{JfB_p3L;-urN!uzuxGV-&e)@7f3gRj#X3G~Zp7J5 zVxB!+*Wiep61g}n*FT+q?yiSLPcoMu5^$g+^KKgU9p@b%yr}WTmU9ntaXDNM>y~}I zFWTpxkvH)A6HkA#prUw>13csUsB_)S|ZIm z1iJU^M|+-HHxrmb(3!x>?c{L(+LgkGD_{cYXeMJ&zXys&3tstTh5_pfEAG6hl~Qz} zo7fY7de1<@%9tL{_j}k|Y!+BKaBCd|Z&|%NVE!}x9k37obDbTdrlOm=Yh8G&h#-Y9 z6~cH^DgVB3@Xxgih||GkP~JL?j4eMb9Y*$vb16|ztT!AUA?W!oKi7~7T2RaU9Lh5h zo)#-9)7nAZz*&~I*bV<@w_L-7%-ZNdy5j2dGjNh|w^M75214Eky|+QsQWzZ`i0N^e z8yk-1i0c2I5of>A-g#>T?b6QvPdh5!PvhzMMTD2<4Xfrt$ zyjtTTQRG+ww4tf z9{(#NLn^@E_oU|vHSW!epssW0It?&uVr~v{0)FGS7EqYWXjGOINj6@rXmbJ%A=|BY zUZ<4~J_4ai+;2%boA_!V&vDUba!CSc1QGx*oDD9sl!dnUv8pqL>fSPGhkLcSGu|kU zW-y!3>J7G4=MGVow2`y+@&+A5>p-=-Q~kg)XstPo`q=a0cFL@3RM_OQyUNcfDRMiVg>kWF(bBj&g1SlJF z)yB}eZ||~60z?^NLQz)HYedDsr3fX|rk8MZ&VD%e6og`Q#fski?VY@gVh8q% zDXC$mKZs2OX&va)5ihtqupiT`L`1^{eCJRCxVs070As1IP;S24 zO7B|e^q!X_{3?@JStS-4$7M9>MCAP`6nDF>h;vwz9Cf4q@z zv8SREBv3ev%dOH&5D{KZU6>~}^ZE^cr`wx0M+7{MWD(QW$S=4K2f>5nNJ+_q1Uz8? z)RRwxDmKqaKm;|+81mEGnN?(a7_p!#iejN!n|t!Fe}iKs()k9QvS~6?01cpv@e&N* z%P|x}I6?gW5C&ZLeI-ZeQfrItbCgw8a!ieHPir)tFV(JWoZTrka(#+NtERBU=*3W& zdfguR<}bRQtyEJ#)=H06ZBVy|V)4&}Jbr0S3n^^0e}rol4;ip0RxK9J7b{g~(ygql z6zR5p{xg}y)#h=GZfD0K#p|4!p042px|eRuuvV{CDmN%}A3`Mrv|jW1 zS2ry>m2y&No7P?L_^6EnL&Aq~R`_dkPFE7v0z=sYmW11OmcHfNO6-b@4c_+BeqG$W zEXYBfir!B@vvfm;Dlq!Yt_D-h^L!5y>|3oCE5z&>CgP0=tTCvTWVb=0VlAeUOF`{u z(i9699}MO%*l+jnu?UJY$Mb^|2n(PMbF?1=MfqHTTUS{0miHEO7Ob0X*Q%8&`8H!! z7>*OPi5AE;2oceFqFcRM;wWd|vvwNcpS~c$zi9BXk$y-$8Mk#UTJxY`cpsX4bq+bd zu5)!J7U`$~46n{5JK|+Y-1am3P?J;#!@nFtBvTSfw9p%jQ7xt7aYB0yR@Wwbwz}a6 z#9Xozs>_7Qo_{Z!`_mS@md zBpmn%8!AS1?28L!QDLZy2o!r9`}DV17}SWcWC#}>0n(v($o7oU_56#ir$PG_O2Vtg zK8*SCM$We$X0As;TbccxE3459qG;L7qR}FV?a9wLTp6}STP>(NkkZMN7(_7IxfQC# z+&!2rWcbl%KuR|E{SV(F{c>U5d?{zN{qi#xzKpBcq1SI|qh%6lHkC9j@S^px?5bs| z2CV6xCgsgbo#=0&oSzZ)QT?DoQDDcWNmG2Ditb`G2`kN*_#LiX$YY~r^~g6A2~Tm&^$a( z{^q8p8*K?AlY>)J@(2{kl1)}MKO}~e0V)}@gEq!?6P%|M!{MCJ5@>>DG&L9J z@W<^T=hjhrX*XUxnMgx?uI7OHznKA}4yB2voEb#B$ikGeX2yZMF7V6FaNY#8!fyAv z_QjK<2b}r8dz@uF`KUTvcuEBNxIl4N$N_z!0ddfuv;L;RS~0cXS&wFmuG#3gr!4Wt zCj_Nw17Cf)5a)PDfD8WCA@Y=?bhjbf_do3eO9Sa zfsY-6>3W}CAD|;3EZ6I@piA4^8=m*=|K*V$93I7khnb3>?W`@%W+~(q8Y!< zw+pJ`Nl`L*?E_|;aRNn8^o4dfvf*3P_Zaro&?-^&-Paqjd6Hz3Zp&;*hk~_%f~w1| z{22^?6{`gYstqeFt3M{dp?Zs7#lnh128{2!_UuN&)3fp7jr8>+vp>9UfZepFbuYsj zl#>=BrE#>G9jDx_{o*q4V)NmALvm6+f*$ZS7>5|Fu9zL()AcTVtjW8tBGJYsn_c555;DI`;v7QgM+<+`G3R*28e*uF5_7V zdUgaZ@kXpzc)-EUO(H6)(pN@#kqD}2)2cMKeR2-QPrt3CgCUq=gcuE-H}B@s2XZ~p zTH)R#H6Se3IXG8sw>--V>HngO0%-lAn>VKp+~oCsQB*o8hIQu0_*z~-IX>7+fgto3 zo~!9!SO&FJr@z^a6Oj63HI(;YOUGi@DBc=Ia%>kBIam`nouD10kD~J{VYVh?_xNEE zWp+x3LZ!Uq^rpq4d!TV*g5!LvI)C?O$sO66g`xo&W?Y=uUG2V33gB#^sZ z@Hf#>buqUFS0o3ZiGWYP`BsRWLwM$w9H%sL=~=5pS57HG}h7 zU|k}`w1ZPf-%=Z9x3M+0|Ndk*oO?#DIy?NyIDWBnBqg~Ns7{85T_)Q00PdJx_E~@+ zhU+x5M0oK!eG#h^7ehF^(S2y7IMmhkz(-&eP*_^(v0)Xec@&~~fqHp?Ny?Fxm6SpT z=-c{vw5ki#mJ~jHF6&QLH9BIJh;e4@7K6KTb?KeAuLt;WX<%AJ8{)2$Twc6lf`0ajeAazG1qQZ{;74P z8|P-;m%0Gw^H5Yx6^apqHAYz3qk9LFRdZWkZ}FywiBZ0}?0Rf6y1Z2>tU-)-#gH&0 z+!~#vltV}puQt2Y`_+~=`GF*@B5`C>^QCRA)0kD{EPtLocUf%qYW1jj%r0%s9sd1= zGtI-J5{fLQ)lDFTHXMx{)lGbu)ADk2Y2WQjj(`60vDv`$rsE{dGm?n8R4X8Ri3>;< z8X7s&{*9xm_ZydJY$Sl3(uD|o#()k#I@56qu{uyis;QK>Y5Y&PGb11QencE#zirislQ;)gcl$%ySE&2irzlxhBfL+q zLq4T*UrS+*u5;)~l}{;kM6B~AQ*`ygFEeiQEH9V(yuq1~+Vha{jcrJa5^7EJR`k)TD+|6aaJKx-atAKo_$91= z4k($-_=xU&rVbl|Nf`5|QH=o>#Ul%=QcY+9cI(wVO~khq(Mx9|PP%Z7HLyEMk@61J zeB&F3bW)(RqtXwg+&b;Ce}Qe2^qd%#Sx84!~)1 zV5qTTF0$rnH-wtU*4z|9e;dW2q!z*DH)A2Gk!Hr_PQwu!h~RPW+9`>FhMtsYEPBep zNF>o!u-X+DpVt24W*r7i~|)z^7#Pl|$f{TuW2l+c^YM0zUoR5f{Bhadj_mJOsZ}eQz>Xez<9jj+Fay|MkwfgKCZq4S&ULaiPA` zArW|DMkBlMDRp=sL}xK#Er}GxZK*=6k;XV4y|($Vz(IStXbQ$%VY9^n!~$^*H@M_r z3E{(2nGFu~T|stxfBfTqZLWp~Ys-Jt&Y-=^b}_3@)6ImG2Rna)A*Nrk+4{5I1)K2qk(h3cW$eLcw7)~Xs_H5 zDT6z2(~6mVxZyqcvtT?I^nsIx8!C+>WWTuZV|w?5z5rhF_u)v}R-&W{T^l z)_BJ}!Q{F{Z4!Y5Xlv}c8SZG!Zsp$AwDonJe~why%>C)yGU(K6osO>jZKqzgjz7J- zw>wDu7E)#aKoy@vxk1VC_~#~r#PX6Q&V>{`uxYq-aYMc|FH~!X1BVroE7^}9Xf#*=T1oMN#)awjrfO**yB~|0?7JDa0qp(d{pUq z!*#3EA=dnU@AV@6{ ztn4wb{D4Hg-&2}R9FtwFW!1X{6cL-q@z?D*eTeKwmZ0MpBR?h%{@ea|Bu<4akzm{c z7lfNaM{DkOk|6k@0vo)_@0RC5{#33sKjhaUx2_<6-^~kq@ozCieIpeYVd~@T_yB>X z8Mwdi9F5HO3;k?!GR@N0X!oH2VKYwovk8}TnV1iI2XC3)M(n7RDHgS0sMqQO#46p^ z+g)X(#UfaX^VjBZ%*xF{T9-FmYO!@nM(>7a&m|NRaP9`XByIU=bhOy)IJ1ZD7j8S% zBgF8=oVB4gUK94Bbj1AY8zwzq7*d_qw1C9eWslIU7k{3AF3A7YaoSYbwbrt7sk&FXHt>^)XyNhl zPe^4tsG?u8|2vTHE()R1&6=o^6Za5~+^Y&O9LaY-zMp2aS-&OT-Bn2Zl{^4*TKUa( z)rd!O z3(W*ZL)6{qVd(2E{cPsC20MHeA`uR^J989<7gkeNQy;YCMt#Btx<^|yqU{hPCB0-M zO&3!Ks%7s+Uh{Wp=0S|Mog$Y9wEfIUbvgegF9!RE7k_ZB_I!IWSXN));se0nveg&{ zO$&uriucz&z(S^$^yqf15%uqbrVnNc-cBhfcfsA*D(#-{(xRV91~AAYM;wzJbGVa9 z5PK+PE&KB4oo*Xh!0lwY28qukM8fiJC|Gp7&Y*)}q2y|f*^KP7Gp7&j$#7DS2WYY+ z*yhsjirAYfTJ3a%8jVrMd7484XfVZfUhB;1+)=;G1YO=sh0<@cqc_K%#Sk^BQwY%a zwG-?lJU5AXLzKwEH06;rH9KdU_)_T94^Bt0xUoI3?ndehURyy_S#%F~<>L<@D;h?m zls@bkliJPy#!V@3AG?ux>AuY$EVg{6J%;LJ5VyPp+j^lG)?$lnmkq-b! zUo4)KPr#$4R%?hI$`h8IpNIC%CXfVgn)XQ~!Cx#zzx#Rvf3~2Q`^!+C96}G;-Rd}v zin0=Md!&EZyy;0x{tKoRQJT%emP8lOu1v9zqRP#~lk(h=k&$6yey9P(l#yXzPK8Sw zLd_M0+@0RoaP%?tjnn>>Yq3%l@H5=4O7F!U3N8G zi$uth364)R@c+i;<+{1KrOQ#?QqGIkf^|QZgz%90WbVcLCIahpJT0U|n^L9!f<+wn z5%6^~>jvB&-#l5_ILL}d$a}EKdo+rF8~gJRX3RNWC;4BPTFB#z6SXBI7>ed~dokR; zSm`mmt@ z08pi3bv6_c8?3RUMeF4R-{p~=hxwcRn-n_PJf5s~XxM}&^3Hd;f3)>5cw46n1BMua z@plnDB;j+69&gXCq=FffT=+f4+kEyQ@x!=ZSu$?-=iYvo2b0=+dk0slds=EfSWk+z zu^3>{e>}OJuW~p!JO3MH$Y2oiKTw9jCShN8>uK**=P4W=%cZfJGEHp91kx&1Pd$;A zk-#aDs+0DAz;!2FL+Ji1FPO5E_z$5J z!|V5+2rm*5PRXGE|6^6hetr7irgC^r-D*sN{}=m#ekE?+zbl!H|ECQ9_lN#}<<4M} zz<<;;Fi%5A7mbL`7lAZ z8k`qxC;V5S;V*V-aCT6p&Wq{D?9t+xCNrm^a&mI|5C1w=Oa)Z~Nsy3Q`Sy+p$)A+ydrQW9+&;D1blE0fpNdF(44mz?Hc>qh= z!XxN+MgAQ$#k+FibLf-0D6%yFW?U0pG|yQU@dslKWoygd#gM6!)9gd^3ebA{(Q~D z(|Z<-jc$Kyq?0bbu}@Q1z7J*4YbOBLcxwFLd(%qW2zs=-GHcZs0n#b7x2MD+FZbso zlar>ZCMJQ$mowc2aQftG^tmR*2;Gv~#W)-lUuIv>YA#nbcbT|>FJYy73<2sK_tRDP zdsXV-MevZe>%~Z#V)p9`FUtPtnz>D)Qg8%Ul4`Y@!>^vsS9j$vT;p^!FZ|cnRFRRf zP47ZP{vXfEar>PFwfa51Y^oIw`zKfvW^BKra|pp_uzLzzI5GSD@#L?hxjMyG4c87E zQ4NuN9tqo5&I>qxu!lS5*YF>~XmIvQ7C$noWI1a-g4L;V_cM0P%UBpQwmH-zIy4P3 z!0Slh7dOL}VWa&47dxEu&+perAHj9=nxV~^8D61i&h%?OW;(33wQb@$YO@QbC3>a< z*(UrFIR!&M7zDU*x&05&A^WH2n5KB8?<@&9!l*F3o?)F2^8iC-CVk4_ZY$n9ez5%E z`h%NonV6Lg_DqG4l`Hxx+`}uRew}@E$z=~tO7_Kd$Zp}YoGTuP15lWdG8+yb32beG~Xrm%@vOw*whQ+`Q`VZ4vaj)6wdLgPXD6oH`cvvzEeo? zbgK2+M3bh+oc!W6{%F3QB32J+pvTcW2+prBezb%S)FR%%^|UkpU@%LPA3;7BVQxLb898f`t+bIwY zp8=@bx4WEAwK^JuO5=%mNd2G768fW)S5gh-z*DUoIqa$)dYO8;jTOD}uNT;Hj6dd< z6_OoTBcwN-ZAJ7IT#>Na6X^hBJuBzO<1CqM|CLaVpV{TPuF-9kj914MGM_@dzv!T~S{lr}x^%nOj0^|o zQH=1Uu?;h_Ii1?FB2r$I1bzp7?0LhP_dTnUja+li!rk5S8xM2*DHo0zizL`>J}r1| zq_-)_w^RKFtRdE3Jzq^0+x<}9ZbMn9)tDf~RX^gP$_Z{M@KU8<;6H0kH6-PI$9tR3 z9DTjwd2TZpofa#P;m?9bLHT{>PRe%o6)wJHo`NGak0Ec2mpsPx|qiRwRRA5Tlz;ltQU_ zMPGXWA-4w54`#j&h`v>7V%$gc(-E@rMQZqs83%{`kTfeMUxmhVV>%8*lW``PDSun6 z6BnjdoSGJ8kP^rg(?$@}BW4B|Tk<6jhaR?bgcnwg>~^J1w)1s*2R{&S9kSA8PF#RN znV6LotyR67RlBTMy_&OB1b&Dj+kZiOJWF6}ca;OW07Gb?Mg(TH+0xaV!j5TMA48rp zha{HLBSGXf;}oJOhd-Ezn7xE;fGV(XM`oc%;*w&%e(?wWq`?Bd{kHa$7f;S(6_xK& z`n95HFQ5I-b(=7`wb0%kK4809o!_K-Ozy#W4bv*nO7~zrY^7>BHE+vl|3p<;Sy@tw zM`7#K(j6(>HtHw{kOdzh^zH24Jsu5WG!<{Q5@b#*C~tAti+E9Eht&2NQF;*87^hJb z3Z4PJ3ZB^yO)E^0wM>WZ+V@b*IEsxzvm~#jEmPw+N7zDp=*2w4(`2hp5C&%FbBI-4 zrV?|-Yumej7BvKtrVk+EdL1mxJZY#lcbzFxUo~FaP%MQANz}=p zz%9MXcK}*;Zjd=G3mkP@z0O$2t0ChrE;WM9G&AV4Kj45&TREs{n%26_=i1Ya)ooA> zb}a7lv`N+D+$*02ef@_b>v}nkls1Tp5e)!yjzY(+#Mlb>5kt9$s&Sn{&z|B70hRUT zkAAL6)-=n1)3s6@iKbjo>kgwuS9Exd$$2YT83+Qaw0-=C=2naC0y2l<9IiDim#C<| zffE-0>X&1Wn_*)AtDPdLJuPB^cw7O5Wvj)A=O+k6!H<9P6Y)5lUhCQ>gR}Hm**A4= z2x9rnkof5q{)ctvC62{nrMI!FmnU`fSQ3xSDF;@$K!uG=a*8);Qu`V9eM~8{D=QUZ zEX!AJU-Z07Ua`y-n7n0JG{JK@et#k<5b8%XZiGh{#kKwqv`yy7ST?hp{hFU9ib>D* zoeGbw%cDK<)7+zDJ7iBM8n6}5!Rb$$pWiFy4j12;+geQC@4InITn&uYbB+)1hYI_X zdVf-}4NQl$)Nu6Q+wBJw2-sHy-d4-%CyM}4V!59I%{s|%sB8Wv7o~X{&KY?R#365R zpbw&lUXAB_nsp)lJSQSD4tSy)!HqXaz0&UK2XZCswBL8P=dZX_kb<9!`afPW34tnS zzm@I?*wfc7HbI11NO*GE?pN~bqjgWi&(!R8KR)oTtjn>8xO2llZI+X5&z^L=F;s4tAZd3~7O1x4$J1)IybJ z68PY(Zg1SQ^I_8fqWjfJ^S#q(x!3_<`mp(1eWS~lOe#x|Y_~E`s@*{Rc;(kN8V0=W zF)V?hbhOf$4SV)O%+^(k5x2GW*TMxZj08#x2iOu zF%#~;o{*!FmcMFDK_q0GZ_BLyRoIk2-9?lDYJIc&;j?sjEOsXln<`wScZxzwm)s&= zwM0*l$|OWT%G(JAJjT-y7AkfkZk8Cj|N7+=Ww{#(GGunJmgNE{gfZNsWl-clEtdLSf2Vc~>NjFJ>(TC`7?01!L5Dt5IW`TjA&b&}3## zHtO5UFS5;n8t37i2kr-rWBsl1-#3D0D&Delw zGE0aI@)NbXp0_|HJ>mJai~z(FUX1#ES64DvkG^rb0^Rmwc$0b#ovO@~BS@E4W2{8g z^c8aCppyiDgyi}6=*`Sf8~WP~lu4ZYt@VY=vi&&8yG@C!runA7Wc}mlRY$_E z)`H}?P5}t)b{dg~kTETfg0*jk6$yDUsSSR>{iug}L9kQqj-s&`Q)+6u*A&a?dBOx} zIv@P=U_=9FsDjxf>~=~T{vlHuxF-}}>aT1dCw;oI6Bt#f(i$Jx4K ziGSXAK>XJ_3msk7?yQ0IVyP!>`L(8uB5`|=JO=h$9K#_<_nCFPcBo5yeyPi19HMa) ziP}+B`=(D70OgD~$_oMAVa~W6a!F0;>O#Y1y49j!Z#MlJf`BF6?dss*U~oU75cNq9 zV%_Itw-1KXk2@rywwsDp5JoPE%VKK)E>_#TPsjrX1_95vE=r7KFik+?bP3zfOj}D- zr1Wm66v?lJw~UGSV;tS1*CAcN(ahnYSE0J(d+l`yvQ-qX?(i|;8h3&E_HvbA4J8I8 z8+8ip0t6m5yKp;W@kBe(Mtllgcac_;l~0=ukG@~q#EoWiHW-Cf?=+Y;=s#;iDw-#* zrL`#$Kl^E@OaX@z2p4m=Wxm;P+zE6_bR`aH_%_<$PnT>U*pQ)-wsQ^hG`2^Zzh39l&alt=I5CC8#f;!o!5^kS7<0Q)%yfzrOa;0?2tAK_GH5|Q zSza&YCfs~CKr`N-*wqVzCCk}SQ(^uhR{u4n>eNNeGC49AcRXabHKpZ^$gg)Qx3wSMjJQs{v$-eku3Mhsw)**l zp{_iKh4!80svD406JpIuy1unF48$ekFcoDvO;P8JX(g7M2*O`!Gs=*f9l ztLlYn5@)|7=I&FJs2!(ciC!*lc?aXsoHv?7(;plJEm)|aYQP*+L z`&XTq6k(i^O@j^?4ehj3bY1+U&T%!kzeW(z0i!oHK-giqVz)SUd(}RD_fHqXzgS0F zARLK;?-KGDj??+06kY;D zp?a#j$2{4-sxpQ7_O{lA?2)iEOpDHSMO^(s^C3TZ%Hr?)VIJ+&0b?FZ<#=WrqI%ym%t%iTJuQ-yzbzNvdft8Zc=0OymO6=V zWHG-u;vPN+!6j?PJm@>R-h~%tbMf8QR<@1CJ79UOS-e_}#p{flmCe?@%-zXpH+uST zw#-m8>r}0qH4ux97Ua37V55yQMHw*WYQbg4yJPRMyRV5$kzeryrN?OC)s)^aU2Ulg zpb`+ok*{0xeNBaUEM`DKPv9>T0~AF}X{}{{W z>R_hw_fliZJ|q=*L@E)x0?H9GR9%+DqlaYkcBFMw!EyOX@Eul2>}@ovjLwJo z)syrFw%6V50MyI)xMp#^y;nue;&H8!ykZ@ROir}Cb2R^T%i9)UWcpRUd=MY5wvOeU z67IL=s~=z1(I@Cxc&-Q{o#=f?m{fPQxt4+k(;kY^|6&0wxG2p_`Fc}2`MWF8RLVgw zenFo#A8O`!c=!kCI*4i06D9-ic%O+UVc{g6-8@sd_z4`wT8AR+?bfmAoeI4LWR?L% z^UFM2@NRj{Kh$6GE2?#SFU)=aX}f)xUv>fk6>}dS-9Ooq!c4w>`XYi{_D7_Mez)Q? z>DQ&WnmWYOC?W6fUvvgl=_B%u-VhKZHN_TGe`u=P(9v#SC6IQ7Nd}pSn6ciyzo&mu zrp(YwN@1fd*P3K==s#YCxz-T=}Jbo$h1|hH;(1snJtAXyB&(H zFEVS<3c6oYq-$2~7Cd&9&Ak=A1MTh4lUYORzyD5fEq-C%dkbA(;&GXDyg7cpd~&>G zIpPgyjM;wN_cjxFqC9;d0{$3W)xevw%F`|jIH4{n!#v6q`eN`NQF>E1_CarD-OP#K~sFY`|c6n-to4Tady>X_A;z0LfSM zn{{4@Y>v%xgq>#lM)9cPf|*BIhvUk2(C>cr<(v+MQrNEb@GjUE5g+B~$VCquDy(`A zztzHl6B`-!%E?55MuGk{Wf@U`{_Wt?^zB%_uXrf>NOIqn-AIX0!m{Q4sIivrnRD-G zh^z!c4(8JO@}B<|a99U(=$sd6{mDT60_mi6pMGO^T<`zm?k$7jdZM?%5JDhmaEFit zx4|6(1Shxz9o*d+1_>HGxI==wyA#~qEy&;w0}Q&8{Pw@^)_&U$TW{4?QMb4=_s;F^ zW6ya`ci(tVNdi{=cu`cP_TH=z9r0%bOBX%$->e}vOv?zA_;-TN^O(|awmkgbq4-Y# zEap#KGL!F!aA}25I-SQw#`zGD9iBXW|2$kiIarm0uIR>vp23igEci9YOSAFUnZ2Fm zuZ8bS`LYtlPB8RjXhUi)(H}08AmBPer{@_&G_REVZD+8$V0?iv$Uj_bY&}@0&#mL~ zj%jN8+on;NpG(-{)xM?;&y~SvI~?`gD%yrt7=m~`d7t56p39iDB>7N|aUac&y|a_W zvMoAzcjkn0-IHpLxFp$2#!3G18ovH}x4zBmX7(~E&j#da?7tG%C^;QSEyAlzW>p_{Wo))f`S_6`8tvujCI#xJ`35| zwIh>P@G?X$zov8^xM<5YMPrP6qsxzET!AUWeOtbpJcQqzeOmdx+8E*%6`o0DGw8@)PR8_gOg--^Qcrcf0 zP~81Y_aW3Gnq>H@9vG~zhfc(kaG6D}UoIrB>d8!L>!6YHV6`{f)v(Uv)f%x^%*W-Z zf8438qf451c~_Kotg{HQ8&2?qdS(hf+yrzBwhtl+Ie&Ev3dQ5BJ@_$J*Cf%ZzgN^6 zijVGb!S=wfl+)Ok9t`5FO1VT%Wd&@&R>bXeMKc2nA+?*@PkTFKztlAj@Gy?(djWu zk^{2QF}*dyA*y_9d1*$GcK1rdnw~!+iZ)-k?+2CmL;8mdiVl0MaXS;l(WYi8mSR-B zdzYXJ?h9N?-I)TRV(m{whz`8;LJzplPkY|=q@7xoNw#zA#sDI}Ht@~t&34v&h+&8Q znAGt4ZjgJ66UMOTX3;p{$rO8jTcg9g7z}xPHP1kmPbYl4)nLQS0P&Sja3C`qr3L<} zbot}XWYm*vYJ%R8K)N&nDZ|1vZrY~0)4ABYojb(K0RMuWKi?iYFYk-&^F0XrU0?f}GV}M?5uVY@n&+ z%l<;)+NUnvwTb+|_C9V`d3GY6^4H2`7Z$~C&=&2eMM@b;u}d zVwLL+-IIZCzmCYuRnSzhcq=rCF}b>ccK-R8?ACM^7<$NN9>dfVH;|laZ z^jyfc6O3H5>b4K*VLhC*agP!v%2y!Ptl7_RCU{3L5vzo|DI|9{n?MEYW-ayh_ zbth0w6dXfL3^(j8z>DcmZYS`W-*~yyS`)E|C9D`@Dnv<0ya(^fn|j8cIoRfS6V4K? zw07#E4HI}?!O+oJ(sgzk9qYb6{_GYrNZ(ifrgwcWq4!po6jwyB)54vAXZL`vVd?Ab zpyOIy%2y`|i>#bl5rMdHVOT;Q8fJ{U1dY%a{rZZhNC_75lyub2Nnw!y5imHO4-L)2 z{R;9tBJ<{ZUkkzqa%_l`ldEYiIHxGGtQ^y~oWa0(8 zO`P4)utt$f5nqGpQ+hGE;WJ%|ztD$c7Cr2Ci?7(&ccnXj@k>!|b7{TGO9P^MAA?T( zMi{D0#Dp^8VdCA#kw>vYO6GLmC(7I1Y@`(tM;v&CY&X-Ze}1XJRRNR3T!FU2dY(Ue zy}4g&yE5RVxF$%gC)i>&2aV4rzte#L(Tm9MT9@NQrQPd*!)&D~>Dv~C!4k$e+kRP6 z%K0Rv$9}fl{`uM=nCnt`%31i&0MN;s&?pVN=cz zmm{2l{npPgtO_Bhu!6X=lb7yLfcwgDr3*etcj$XL@Nyh7og4#Y7@wQI_C+nx6I*_s znCs^KmhF1Vo>R^2Ogw)RE$^|-hk1^EDU#f)J!Lt4NRlt)#GC<`a+~@*_wcB`mQ_z- zd{_e;9_c5Vrt!Q3be0kVHy>}-sXly&Bb9m#a(<18lHG2VMf>=G>|x0+$afE~oMM^O zopvV^V*$8%ltfgiPRCbVC!J1i$7>!ggC!zH!<|HGPap=^o%+H)-UO6cwLM*BO&h<% zn+*G=kD8x}*L*z*q9Q1Cxr`Z%`TeW5Rt0@e4Jkal**M4uKzcWv(R!9K{XuWp!lohD z)?KH}&+*3nQOK@8c1F`2p{CsjXUx2?@%|2WV{yK;C{KFPUwZX1;jgul`5R^D-lt{c zQ4*X_MMECG9-sv-SICjv0F5`YOHp@+S)~Tj)MYyyGrlgAgo*DYeb^qYp0c+wC&)a$ ze>@lJPd=No2fGZ2H_1qKJTU2xu^t6$d4&ZqrCxE8j<-geOCeu;_gcI$#>RTwePxZ+ zym&(_0ID8X(`SrOwI^A=J z*zmDz8nU?+En>J>9lg-;{DP$*4wGDf{b4h_@nVP(Ktie+7?|a|_WShyTIRrdikVrw`xYlYpEIcel;Q0$n%Mnhq`ytJ7a?f#LF++}IwBb`L z53l|O>CGxnOc2_RjEKAvDQYBEfPO*XHn?ag9Z7Cr&`w5lqap@r-S%C4g@|;CC#K~3 zYA9h#*!ky3W~6O+p`QwruW-`&Xp8GY4&?*?4d1`sRQVX+jwR*yjf>cC+&q#@=C*~_ zx7FrEQ~Iv-h^+ghZEhN!v|nO^uJ)*olYVmJkx;tw0sZfe=sM=LuJ&DNtmmE=8m%DG zZ*YZ~dU@Z4^m}E<*BzN26Ch(|eDf*marnLY$3DX6kT)$%{Z4N>iqHJ;1=h2c?WMO< ze5P2(QIFo5V+kqnr5$xJap~VC{RI_#`?DpqZ6IiT*xNu}ocPN;#R?9DJzuwqKGz#9 zeqIaXo!{cTrK634Xh6YKE{Y{cRv(1e=$&DVZEoA@XiFYL$H?+Zb4%Xo~N&M|wh5tKSR%roL|F_02U)o<+Pc=R# za-I;c@M*XW1C8sl0r--bjOGp zqRNniu;s!}&&2dmNJ#I0U5ZyQ%$zm$SH=C`arTD#5BGi(523G0+;nTn6MOl?j-BSX z&eY5-HX|caqa>j|$u0ic`V;&a!swuk&C{DrF!ui`#=pY+{t~V(+ch8l6YRfZAkq6V z!v9~NXO~z9-1M-?u6w@NO&mW@q)13b#r^_9LpRrzzH?Q~k+EjM|4(H9r1&MCKY_JA z2bm6dS6&-=Z2RmMF-n7>AeM8&)zfM~+1PR5lLNh+$+ zd|Y^HYNU3^PGUHbd7;H#q~+!ae^(B%wjtQSnVLG!@XL^<&r{yQCwx-rYd3=bMk+Qg z|9)}uq$%Qd^-HU3?S}5fWR3*51H>#9g%@kZJzZyQ4!Y)zBOpKv3hKCcJg9rPJDikU z)P+DWWo1hjjvgtXytMIe0WqI*Q0K3Qw&t~)75i!C8-_t!Pf zyX8ZJC&l(1#;5I%Y#mV@tkEv(3B_&k|H6amjsltE0sNBR_c zmS3z$E$EKZ!GRO)^|OM`haZ@hnCoc%dI7O5Dwd(7^(e~SF|QNzzG=lNh8F4JHl|BG0V} z*T)J4&btb9VJc0>wbKeYw#(c{D^1~FL~M6PI9lSSixaYPY&IY*JAX*HGQ95gNk`hU zc$I^viQDBqsjJ&?liD?;)z;4BHqtgMhj;z#-*@>%hpHc4FxO+hJEpf+tG!fjyLCgP zv-@Sc==5mT$9&8}yy4piB4;Qur*&~o>fyVR`Q|-G1!NUm1N%WM=gFR?Yka$m8hp)! z_5ClSmhN+~72boF{At~MR?e>@n<6Pv^1oM{?(L`_jgx%#QLyPx51t#NCAXrY|LNAz z?>&A5skYoAPA48*%$aXDFg=_X0AP(2v{C%$j~v@yEZDg9t+=4+)Y}<91j;b!0|@=@ zX%);yo1qa+`vhy+yp%PIY6V-Su-n5%_k^GlJKyh3-U2@quyr&?O#5ypAM7v02%>FP zvMhPY6zc6(73}WaTuxR7Ps3}o>0I`wh$WO4nq37FOPQ|jf}b_C^-l`f-c(mz)&z~{ zRRuk23L|<}nGgWDU#RPl(7wa2twG%rqEXay?pYsZiKO^J_m;mnTn(ChjyXu((NL(&r2z z-jNX$7HqMMvRdviI^SKC$LxlwEU#SyZoUzNm33V!bS1x}b@md_EkiMC%sYmIH_?N? zWDe~qaHZqPm^dr<&jh3u=qh3f`JpYxlraIXC^*d0lHJDr0O`J<)Vf0)qyj@W(j;c% z-Lt3d-Mt*ZPUku08U)gySyQthVYb#3LGF&x#-1iEAQfp*$7-9YjgDPMII~Ve%xX3Dbb*k5)A4>mKKq5Dui@~V?TXXA-bmUd1Haz!up^iwgH~8^ zm(`wS`S=%|3ilpkX3AJA|Mas#eO0UXD}wI| z2`kz+{iY!yc5;WT@1e{Uic5ay-%t4{qVd!ilJj*>cu$f7WP;Z*EDioZ&g<|Zr|VOJ z0y*Nza*?VODw;2(^IO0-;}ASsgSA%E(nW7{QW_@mdxzD9Lt=v>-{m<^EcR|!A^V6! zEythaBNWf5^~%#t1r2Mv@3fi%dl}s;J9?soIg&&_NU;^WC)&TSfr;-h!4hqyjts06 zTGqsgIjoe*k}N9b!$-|Zo`&@(FQ#I@G>)~VdX0tH6SVioE>ccC?pyDH*C>fScLvGJ zY_wt2I-^=@dVvkg$9vG`z<$dC%h@nE_B^=6GUY=4X+1xdusl6I4aOCf@>3+aqJi&N z%aD#v%vqy59=Y6bG1L5oj9fT=q^`o@Fm^{i(^IHDr{(M8#b*NZmEQ220C(*Z-CGon zqh05WV@3kXz2A=J?~=t}yLbTxn;ec4tdAFm7(T~@D5T?|jG+VLaz zE;-Tly!;vrvDt*3U&014mirX9(+(*&yS{0(r!)lm+6qg@khAN!8cqx-M+h<^{&Qr3 zCRAo@Ks9wp`gf={&U>*-oweLx>sh19#)-w6vNgoq=M~*%n^eJ> zV_zAQPEFM5?lZo|Lz-Sfc-pdu*$UoV{gW@#-6?!J{SjZe+X!S1`S}a`XG&geHTe>a z#aUslVl)Y5i&=)W?9J>KUQ1uwEU-ni%FUM0SgQ?oJnU+Z44$%Q<7Zo#0sZqVMt!IP zh>mRHdfK3DXEh4IM>UjPnJ%NGpVUY(0aRd`#cy==oXWhMQOXp1sk~ z_i*J`mEtys4v{zdx7LGf3x)3mt?^VPcd|viU#S8!dQACZ7D-u1xd~0`hiIQmSzN{r zdl%bUro^xsmz>VqRFipgJv*xaDYh)BL=0*G{B~lx#U7se%M?LdLCyMrt#uZoIaGn& zwI{lYv6Qiy7%dx}E7a4Ouw(3j_Up3>&BV&Xs~GtV7!yaoYk|ok+0L_p(OCcw$AC%p zv+Xpj!%>Ls2-wBQC`;^cgUghB$;4ql4xhh+{d|F|*aH;cp(F=oq}c-Pe?At<2nrp`!cw#M5&W za2(fX+C1)YT(^`&0X4XN+L9j9C^u)d;u=}3X-}CJKcxs~dC_DkMwN3In2J3XDXCi)<&(8e$<~Tyl=Ja^-^+*lZ zH63Jv3YnRIdop{i2EXbe0BWM^$Y>1G0vP2U2Q{$g&l_s^RKF=b)M}dRoS2-S8bqnx z8%&-wHx^7G+Uw5)>vH}OpvB5B!yee{!)O3TIL>y!>~TDRaI{hcU)=>LNUd5NLOg=> znIX1~ZF?twh#~CmjQOJLbr{oy=}tc+FcDXK>sWa5sEbK`9dl8GmDVf z_XLf2g>2Gp&05gv?Kf#P34<|iiqKS+^*T+>&EBRju>8OXGRpd^cs$%Z@ehC`0lSP& za`)BI!1+06Jn3VhMNZtSfGojH7mW>@pO^$cNpJY6dh(*mxVGjlCYhe?id1hn4;PTa z4~dYdsjhgIF}aWCrp<5PEu}$ochC(+f|_Qo5?n}pxL|x{=sPxdH+G1k* z#3rdwzm)6U$gRZ`<9+(9kN>jPA8Pi>Ro5pLTo!e%S&}8Qzb5BS5uM)BrS%xPk^6IN zs=1zhwV3W3m=q6jj3;nJLF*{N;qP9^C$)vm@ysrJCyVE;JL;S&T9ffai%Wd+PZ%bN zd>y@vJGK5wW6)&c`9KtWdBL~>b>?2ZXy!2Z^X_if)UeQ9=Uo_|?os}qq<+|CM~VI9 zVf43lT21}7Fyb1@jj?oHkjtofr|0ATJ}q~^^0E2*=@XTcMk{2+R*?V!jhFelEG@h5 zmoyh4w@X;CO=;&b@?(*c!2T^uPVxolrO`>Vt4yBwD1>{m{4{b!DBE^sJa2rUPla!i zC3ir7sE^oP#d)<=rz(S8kB!BdJCUKCRjvj7s^gV&{tNEU%YTkJRJ>}M{wkCB5F{t=1F(py2m)HAbQ;pYaBn3 zexg<2leJWZL9jY@P_GP$6K)&4S%2zbIu3b=d3x6A>E6jiU3C8@ zy@B}>VOO@+4XbNt6_D+71S&*iur%&f~%$_?v1pTT{Sd`{M9;1s% zb?1s6AsZSSOrT=?V^37W)FdA6n-nefOYYL>iYKW1&^iRsN`@mR|HDDAkM811jsyehZWWgah| z+UGUZ6a4)@n3yAvxop)gIvjB9t$4lt(7!rb>nKDIv%_E3h1y?^e$rLbj~tbiF^w2v zS~3^yItfr~TdKyf?(NWvU$ptLn3%cy7~^;&zxnj7(h-uVeQxrAWU$uchAUAE!4eJ>WK(Rs`xc+on!03&FRbt`c=I#-lBr11}Y|AR&x}ng7F6il3|v*Ab-pP zsf5n$L)`P(<5L{RX_z%8TcrZ~^2!f1*20H3wwbo?Id*f8O=sO&XWoa;X(`oqSUq4d zN=UG<*Q9D{G9y*9%5j_N!0UD_!kEuz#94MM*UtK+rrd-@eC7@(XNOJZL#w(eO#zDY z_^)Rb64RNkzAkDWz0@URrhBc*=)@EWws{l&?q0`?->j6ZY)aP@8B-bWWMaEod1q?+ zJ!_WXd^9A6{)45v0scDKk0inW2rIu!$hOH&T@vt5#K|fh9q;ekH*pLNt0Io&fh;6$NKKN zN=DYLkjTg$zyz1RN1Yg&E&&lW#7iadv}D2TP{rlgO>PzOkL3~DW8xzG>$eJ z(HyiE>)PIGnmMxDS+Qw}t5?Admg*_gr->)9dS~*FhlcZ}1%` z4JoMdXhFMX-Kvsdt+a5ivVU*OXfls2U?z9GK&&m2l~X4yL@I;~I}MwCX!I0%)^~sF zGPF%u7OiO*>Xzr;kUMMAg-m z3z*d>o0MyN z0$x!?U{*Tae#a;n?mI|&?#f*(Sunp8ii>A?d>`f5{XrGB9x&bH*69*KKDLo~FL~Z_4palj%tXGa z$?7}bV=6y>=Ea<{6tH+TKHivEFEb%6ne**r^^!pq*M0QRU%#TFUZt#1B>DLQGe6mfmqgTqOj(AU!*6<;8lZO+jm%a=FXgvK=RZe zpEa+0v|x@#D`wna;PifRB2r23#AJlP1-IN>doKqtfUg8G;29(o;LMOtAXVBp@Q$PV z*}65GjwrG(uwnm1&+GQl?`aRpKUc1!sG-qewd{VhqTck_aytKGx>((V)`};d6#2w- zOf~vU!t8Zo`7Xz8sml|7)vbi$jdBB<*(45u2zQK@+kjx3K+>vk7=oD}xG!S0i!ogP z&zx^SUiZ6lvgG0VXC!w$=<)4<6izN5~uS*zb=qz;{ms zS6bvmf+rwJH-G<|wH+9K!3=KNT2?=e6S08vOF}b^2EKr<2?Gk$$tWz91hd_UO}oQo zrc9{&T9tphI^pZcmeKJ7iEl?Gi1VvLIP5% zseezu&R>9bcp+A4X_lv5Q=P=ru`9V`k_3*0%2FwvrQyI{T+|Tzc*#T#Lt@}b zJZRfQY8T^d9vEE>$1|KHL2z<9WfaUIXdRl6oo%#2fwkHKnXEPqmsItI)6_QHI(Ql1 z%-~$2h^Tgp9l7_oFloVbZ*-bZE^xh_%GUmSythzzi^dMS=>cU4lC<5(b)r0X!~eR&W z@UQ$8_&P$g;xg?~vi1z)dm^*WM5z|d{S9TOwzlj>Mf*FhzZAcDL#|%SH0cbg7a%v6 z$dNzeiDNSGyF!6n*dX?&`EtF*hv{)%J%whsW0DIuD%dgeqRq)i838s{dK2KAWDb`% z4G0a(dW?M-Yp_U5vYwGI^~9TeS70u4gcrTlU*cU_bJ_iB{?$^% zjK$MgG0+3LKJAWWq|OKx6ciXXEYq{P2YN@u<9#rz;j=s`(lVnN1(^l6=}Fsp$w{UO zlxdlLjk}mm?d`&p(>ULLlv|iB7HNP`w~tLtsnD~|!+)W~*Ix}$LK%=EF9Y$gBq}E4 zKtHr>1icK?GU} z?J|Nk{g6qE&pDIQNT_TzJXKBk#)wm6|CLL`ya&vs!~&#BW{ucW04cdFuc~-NZ9e^1 z2eRNDh|Tz41tSzObnM;m|E=ef_;>`c)gLLD40X6j?9-}6B~eKK>Gt2F#RFY&aj^=< ze_i|U81suR3|?HkSd97K+VKC~1NZ;-`IEDCU66AXHKYC$hmG@MD$0dpINsq`QpXSI z^3%6R**{g~!o*WM(iftrol^0iuuw*zE>sZ}Ez@Q|EhbsUQ4cbZohh9Cj-yg`OE2DN z)6wwj?GwUFN?wN+$+l8Gkr;IckG%DRH!#4)toSVdBFOp;O+e6y?TOmhq-!E-b zx?>U-Vl%E83!aMUZ^QagV-}y%jKZL2${|#rEA-z}`1)InUu;poAex$*K^vjXrD7RUQ+GJJvuk1AwblJ{t{axDek`^36o#}~D8l;(*-4Le@ z7d2jk2ubwdKjZ+j-ftSpY#taM5%qGX>aecy->AYeWCMpGW@p^SCP88fT+ z8@Lhx#*UVA{tONL#jBKklTC3F?xlno)N`BV??l)8TbcZ-5f8Ed$Fg0&Et8Zr;HTo3 zvhH8x!2VA*3STb;K1mx!i z9(y26972gAB=ID=_8tDX@CQ8Uq-jR4=HT{+zvEBm2Rntc|3XSKY$!9ApL1+?GL$U3V_V#?>)cdu% zM$tM-fm*^NYw{D$QKK3}m}FmAA1W!7DS9nmk_Io08l8Z_9BHE9?NDa?V+(@BrPOOo1)$=k&gk zGJIc=?k(sH8)k^xhG?BJ`6K=HR%O;sG24Mfoi==hvO{C~#T4tSQ}DUR>~}I&Yyks2 z3_A&0=8q_m5pj(4VJbChFB7dsRWQnOhr&$iVw>f}m=#wId!IwJ?poHZs9h5ipd=@d zdC$(~VZUR|@yFm&aFH@jWvQdkHwM7>>XUm6UA&Nu}ZQca?7$rrh3P& z#ii?GANED+=4=xF41r4E+ppsO_SilZ4qzx*aIM7ybX_HVg17a2fk(91nhaAZ3tB5) zdHl6z!f#)`x727oCa$h_ZW;EkMBtjp~Pr5I)pvN~0!wM0C%n zvK;0R=rqbZ@MFYCHZq?@Y_GoI_7X!8CWffm{<+xi4KS2Q@+fJ764z1XJx$hy#2afenqNanN_YVNGE50&27JKh zLccdsFtJ!V;HzIOn|FHA`loSueB_wA_y>eJlXPY8&JOL}6c+SC9bdlS(=tcKC`9RbDjLgYAO@q^N!bbn;+N0V`t;Vs(eBMO}hv#w;kQ^p7*(tix}`eyaY z)S)W}F+M?5G=R}sP8+2tg6UeWdl0lOaxDiRs?ZToZm@PRUN_w*t<%fAuty6{S3&n| zHevPn7zW^XPM5@I(3^QcwyNRLkqmbaOf7N+J+QB8)~% z*x`^0?f@5tY`Ci51-T(QIva$P72?D9cQV}_4xO4x2Ko)}m%9A!AvKnj&Et@kW}=8b zh49Yi(q;k`n^zeylaL#v{Ed3`VDM>F?Vsfka%eOVRDpLJf$(uHzo5QDCECpO3;lHw z6kGu=D)YmJ>vRppY=PgKQgCS8%Y5zbkYC_mhVY9&!Ej<<(vS=hlE5!spaU9J`0=S@ z6*p{>v@#Ua)PjnN{A9jxf(xv8i>&XYe8v4BofD^}o!!SniPwJN5cZm6FV3 z2_Uu%Zp^UXA0lt9GD=ee<0`lJh#V@v8cO=9nQL4IraD?+Jy&V}sDhw+Go*Ed2M{g$ zB-vxr=}O zd|w4LqP(Y`qMllyGayO62>J+bjO7uba(iIDUy&Rv_Nodvv)AV%6L=ByiVi5I+1Uex zsW5-GONOcz`8l=}GaAg~W8e3tr!uhKi!pPmAi?J}Ya^(7n{x0ka(_IGF%gO_m5PN* zS5x$g98M0RUTG-*mY&P`B#Uz3&BB#iD)mc<1t$yo-DDi}YH|>iE13@t{fv+^4HFZL zS0DDDA*!aB^;jEbYge7UefutCPNJlta@ zgj!vC>T}3kGGiZ@MInK6pQq81@le}&h+#xl{x6RGT;^b5YdmN;ZPqwY{{$^AlO7XR#!qE!Qtgq4CpW!a2GAvgWGe~<&_4^}j zZ@j-k0iqhbAf;asvcL$8=Z)<4>7y2X`xmwk%GyHExHRxcLN2K58OsskSwGSoDz}*m z-IVp|BH&iw&jauPJsL2awJ_^IxC$9}d`QE6FP3?(%(8f5=~7FO$jP-%Z#fImL~ zcN?XCp^IVGL_>si*bKn;d9}5ozjs{ZsUXA|eJ+^( zRjhw>?IdW&^HFZ@!+YXrYtE$BB{X=`NKpADBr2%vM5Yj8Lp&IOFU^FkOsaqkhjrh2 ze3ZAy@2Slg%c~+EaY|Qj8F8`iC2akwfH=RvZx|0_aHt`2ct#y`+RR~_Xe1>dL78kY zmBN<8+bW>d6x*J{Q2v+D^3oWZ}C}y-hhD& zz^2jJL;G6uZ=?hLYHzZu*8hr%=@KTJGOt8sh)=>n5zTshqptcC_v11=iT@mK1~iFK zy6;DD`=E2viWVF4!D}d$k|nFA2qL(bfFF~f(mC^6j{Ilw%aa9pT*akQpVD+QW(~53YAPhTj*0AJE z^?QU{Jhk)O2d)#XXd}Y=JUh6vHFC(y%}rP7j7Ki;iz$^7GyX)Y{_bOMPm56xXm`@k zB$s&8Z7q+{{j4v{78*i#)2w{lujrjddec1lGuMm@L|OcjwBxo z$0nE%G;BJcv>OCXCKvoh6+v9gxP#LLBDbd%Jz+Iz;p!n|r)x?(QIH?h;7{g?Bu{>m z_5yX4rMFbJAC`}K8x%c}TIe0JBzf@mxX6NN_PonUlmt(pc$H3NCU0X36;| zD3`mkRO*#-MgnFd5d*fWO2i&U7rg=8olX$Fg@ii{4gM^qJ8@ELz*WO4=4p=e!B-j? zRp!K)*)5pOVdDxvq;I2*;wHb(DG$m7)ZlT}FR)s5M?A)^z_HtyjTQ#$wH!EswMxD7 zZO8&E7jov^@ar6~at3?zUya7BT(9M`mco;d&^a*`;~`#+ifNPsUNG#D|% zC5fyTH5bAOO#S;@1&22M*7|h@a@MdhzdkSti7L&W>ZhCp^iN`977TD(XxSurER_oT z7Z+t>p@oLMKFTB}M@bg+Ks*W2Nei}I^A~@h9uD#GKbu!W2SM?j3#=?H7!9Ku8y=H6 zJZTdaoQBZ9I`yfBg4*yX=`$VhA-caE4IcQb7LVbO93E^f24;%DRm--V0>_+1zxwt) zMeCm34!Ufm#V1KgS8P3686a+&pZgv7gFTXrG~Dli-T*mi0AZf&|1Pt8{go*7^o{ z-LV$6V)Y(*Y1`zxvP6m%qxmd_iR{R5GCXQEYbMV+;8P&bmb)q{;lw}-*YG(H7(bLG z4QM8IgB@>5_D~!VlIZAxXOzn{)%ZIva1#v3USY@2Xj@{om!>`m#fQq@noT;?P)Lj_ zmrWWC4D@(r&+Pti;6LZpv%mn=N`A0#RllQhjC@pizExG!k`l-+Zq#GRn(o~?*UQQ_ zkmkynJT&GI4g&lLp3L^J4N%Q7*o6h}@dK*=W<73njN@hn4KS4Hkcl1LgN!jZF zeah;K-s5xAmt`)k!A(D14&>LuaJ@>1;ZDUnv0!eY9K?7xSu}McDz&=xqqx{Zu>G-3 zpR2L@H};<7Vej7m%mpY*q!Cu4-_H*Mq_dhi7tbgdJGT}x6vKClRTs~ZVB^s>jVoIj zeadFXlb}+sh_uK*b?{`{i|5xQR|DS@L4|1(x*@&C%8OMuL}fauy_KerDUYaQ4+(knY3K@meYnK?^&}hz5-EJv0BdTFAJ`p7MsQ3 zQ2)ReaG*UOesKN=3Q}yk!9$9i^b_t?E8_k>cUg39DpV|R9X@FDd&`Z-3L*zjjQ4Ie zCl8^9yu`3KcsWBMiNnL~%@12Ssr0^qtFSr790k(FXTJ)1zCjg{*z(e=GaEJn9J5>y<@_ zgYUlE3O4}_ShgfQd0v3`(;uGp?u2`N97_7y;{$hlSqT=ufy&YK7u17B9fhCQ*>Yz| zC-$xiDX{E95C!d8DX{dTkp)kBgxpa+)Q5Il&)piW?lGtdeIuhg*XVq81e|oD3w&%X z5)!slnjk_qOmW^)Z&%ao4213mQeMJIquU`mGog#s?>i<^@2PvghbhWvoH!V8Tf#{; z{Kr4r1zxFd$r9V2ii#Oh4P@0ghl? zOy1!ENF;DC73*hc9$>&##$h-Jh#4Xr1bx(sPIdYx#iolqt5A(_s##rS-}YO&Lj6_> zdk?LM_ah)Y86oLcC3ma6IiNbL(uEWwBLzEp~f zbs|32Uk|BaIcU_ZV#I&uZ8gOke_mkf!RI;8?CjpipOdmdpZz4aygm}kTN;do9S^h@q4no_#>$mAy_r8-j~9VL0%#Ru7HB z3^bU~>S9iLMP(vX?D>sG74_Q5p+y%yVF0h4C&Ro{^I|HajjdN^F~5H zmKZz}{DB8x8vxDF#k2NE!f@1)3uL~v$O28JxhwRY3{&(NAD#ZFtE z)8*`p{7b0sK$&t~YoFlP_j&RFMAtFBfBtE3t+xjM<$2)CzP=BikbbIrU4$kY&kC34 z&=4U!>l3V|pjTFDmp&pSv6{>Iga7x*c$!ENOBU4>NI76QDszM*A2^1J#{*#K!u*Sn zjVap_DL_0dX$gt=+9ZJ zt*rq9NH{!WqxuQ@q^`V*x&Ff}9$iChS_%H-r0sv4ki`FBBVOYUc;Jlb^ZA?RdN2Z| z{Vu3okmMf{SY>-T*{5SggNw6GxM@q&z3#3CAd6CKh8s!V9%tFp3vC9d({km1@&qm? zn=mg{N#$T?1>$(iR5|MAV@(^1y3-%Xxmo^1HMpz*W=1uE$9aW4k){(E4|O*zYhE|B z*M#w61%@k*yRce08N=a~kQ8gp1PVO)S=uKP?y197ts8ux|NE< zaGZ!8^7vZ6B6h{s<-Vn2pgkzd=hwI*wK_`jd;CbV1#pCqVvQrOG?Jzo9zdBW1o65A zVAS#U0wQ5cmOtjmd%G)kJCrohzj4-Y{9dINPGo-5zK+hLOuFK!dR~sio8Qyy1+tim zin926U(l~53nX}j9BAG5b=(f-`BBg*x+!Q4r^?N)g*1&0_S|2QW_s%1MJrCO|I3ifYEee+Q8 zy%P{>My!yh1_6gJgbILXn)$$=YD@UFyXm5fd&<95S@@bGlkcEia5!rx$Abn|S3@lx zY-rJl)ZgN+zsouU6%?3@io;9r3E2_|L)$MSH#2+m3Vzz3XbJ7S0RZ)aSP`MOyJq3%wK9X zD48CJQVK@ETfslPpS@SV|CKa74&{qp<4B;@2Z!KWUn{G)3eKegQWQ@rB%+C(^G{}w z4iH+j%L3BmnfJU*edR@%3?6#SJ+7gq;Fd=o-9kE9n#XeD@1CR9D_+konO??x>vqh2 zdd!kqrN$2b+;Xx_rw*Gi*I2#=LbMddbAQRElNX#@2l*DCe26-_ZK!$<%3LUG%+!XJ z7*jKSPv#J1<)Vh$@el~ZIB(<>hxXe)9^G3i75e?>@DgJeHod{an*E=H3$KNDG=g1A zksa){`15pSOd-Y6#md36zE(2YR+#Vt!(aY}>2cLuDKE>BUJP0OJ={T&g&bIh{7jJN zA`y0z?RnRi2e^v{<{_UGMR#kqzjF8VDW^Tya!6*|7Sl!f4)Y)@E0vmorz!dGi)1T` zxP_Y*I@;bc{U21lWn3HW^F3TBRtm+fXmKmbznyr*0GzvhV=8}Q4YP@ z)BWbGvTt)>iVP<3fVB@BT<;fjUch;I5zdw>GlOdCv$&|l$ehWkFRs;(U zUE-7%7-XxHSOHNOAl*jA^bOg!;_A)HgNv!;1Mwzv#uk~#TI{V4ndZslPOa}8GDXPA z8~rB;v9emnX`m3PKD`jWR<}n|o^M-!h8vKQEZta^^__n8SXMQMwXM`EWzswJ9|qqQ z-X!YH5%au3_7p_e2tZ{QgVAjxW9QpU#AMW4Rov?~x3f>B#VaHU5W2FVzphRb;N**& zr@K!fLPc_W2MyF2DOyIAy-5`17lUny=;^e(UCB3_)0M^g<4a$+SsO0>W+bFrHu`EL zz5?->JkaM&e>5ETZjHUus+6s%eE0Bl^ergRz#;eXNtClnF3+~RhR4pZGH(Ep%d^hh z#3ZZoBaQCOJCr2?0|j(q-6dfXXJVA43{6>kT@>?3wP#G>%+t))c^k%&i^)+G`cNFh z5L~ua{X=H|@ceaJLseHzS8ddN2v@_R!B&m$?_JUTkixsw0M?v1Y~o+?HV{_l%(yt` z;r+T8udnSw#oNHbr=lZC)5+<=Ub{bW9w)*N9ZasC_qZhmDtU2XwRqLk^clNETAfCd zKl*^1uWTVvF(^cQdtiLVJ#gNm%d0)G6Nbh?Hn){qFIxQ%ASN%s0u@iz@q4S)Vwo2| zAlLpozS&~g!gt_{43t?aXqF5)4>o*Id{4a?huk?5;j>NumDJ+%y59Ra1)?I+f?fHi z0@kdnxrjn-?Y-Xhr-Tki>heEE;$o|m^2@X76y!|dKW9T5AFH_Fk6BsdsWZ|0LyX<0{pO_AHIePd1Y-AMJ=?}16Wuxt;vDHqzT82*Hdxz3 zebVAdbS$;^?)?YB#Br7&e+lj;_c=7F>F3L;M-oEGXkkPGn(myyW0h^G!q?qNv?$&i z7>_;748;*IYGJ9H423k^h2Ze)wg~A)t(#68tbTD3O-2vK3&*S7Ow<=Dwm)lb$Sckx z#ZK)a*)MbKZVbW~c7o>w7RQo3@FQu3B6ov{GZYL*i2fvY(V!pt$OZMDvxE}#L=6SDbfF6t@>@nvBThK%+!R@^@#9VH5 zUm57c2Gm z9e~?&amo3RTjDYV00)2AhqVD-6MDay4NcX^pjsbBT6;T9j3R2x> zd1ESFb-OsIw=BFf2@({kM!KT11tN7_UavHQP18j)wKc=D2hGFgf?~ z(FQa&%ciV$Lhqd*za$y|0&E~=BN6$01Lg0OZceD^F%tB<1xIkQ&-ek(nNYG1i)Ez! zz!yz0-Oe_EGiaH+PmL7~gdoIOWLWfP>M+63@Z|weZM$#dWLtMlz0l+Z096wF5AbC* zaaVhS(kX>K@CgWHk-odg^zYXolrou!G@vf>GH@ttHzHDhWg!Z@r%t7hcb~(;NFi%LaQN{W+);0kIv(G}AzBfjLGeb_fO1^K zlXLqXL?ty6oa0^>Q&#u&@j$i5H&`U?neB(Rk8XNPeQobn%aq+z9Sw3THw!-8tZmWn zZk$;kIqm*B|F8<^6{bho6@~rkG*32CbM!MaI9pAoGOGfr=%4N_dSZpR`jq10ew(^zI>0vcW4AZ%H3Z)Az6zLAPGk2IY8|ECgLGc0~M*J9{?6nc{bv8y~&FfyLI%5=QRf%AF;4PbEfFsS1;To`fb)5 zJL`bK$Q$Efs&69ViDqe#Xk0x0zJAxla*Jsbz1=-1J}L=!af#cFGreku?+MgqF~>;A zVq{Aw!!qt62w?vm8g>5R@(RAx`Sc?A1oL>{8&GOJY)^fuGF1X%j^|9?c0>G<2u5T4 zHa~x@IIH<==R-o0j|0Tu!7sZ2cI~?rS?H>&nKk#$aWV{KB{qda{!po#u;L!}Ha*B$ z+L`5e_pAraT=ATp>F(z2{9I0LEWtpXKDC>?yCTWvqQy>iM#PY=F=RNxKM&FQ1t*=c zRCC1kBGQR~n!V{gXrPu>q}v4#6C2!R`&o%l(0Mz3B>#eb_7;YpO2Gb}-+RZA>$&Y8 zpI4rgNka`=7LV7gG%NL(^oJeibnp-5FU>%e7I8#=WU1i>m-A7Bbh~U|+nM;*orGYq@f`BGz zQ#qxAJ@8>1F~L$qsa^z@ih&(_NRertFx+nKoKbQg@9nmzYCj7{aM`tqOV2<}aEuxb z(I*K-Ib~t$O6tjFeTLUzgS997)vRKQuRE>W#VK?(S=!>1%23P0v$-czs8_q``AZa|=M^bdhGpK}AG!g3gY_Jf=S{PZY0JsQ4#QW2{$bQS|GtS?&Fp3c?0 zdt2}Emy59$cQ=1L=%ep2JRAOK#5@362r`8~r>vaZ+3sHas*w8ULjH4LPrgS}+S=99 zUQ<`fyf$=G{~KDI!k^Y^jzn^G?X$jo!iLXM-@9cC-Uxwzj;@$ma=sxc+V~tKvh=6> z%3%kX>YgxPlc07Sw-xM1_LXLBcbWTN?C=B3Qm(-vk6ni+@4A+`-`IU;g_5C%%9UiY z>J%3nmUtg$O&eGgaLjSJGbB(JscXHJxvMx=`$ks9Eg+_o^foDs1#p)pIQ|ZlHDd4h zPI|PfG*GmJphu}n*5`|z@;=&sOn>Sz;Dt^z}jPv{BwTRVIX z&S_2GKGkgf`l+5u+CR-wJD|9QTHJ8nJ-QD!c zLh9y-qd7ggSOxd~SOSz!#@CLoz&W&$q+HzOJ-TRe9Vn>8aZ{U(xy;G0J@m@f2 zE;ovEFP1E5xU+svn8KsSUGj6*20wu$ztyU%bV>%i(_Hv(EPbX!4}J25E;j&}QZAvcLxmQD!0ERNUR) z3cI>?`olg^D(RnXe?djR))4>@v+7LOdJQ9YgLYWvF3jd$)TZ6aPh>s6JLJe-E#v3I zCz#J$@*Vcs8x0}n_1;q<*h`T0yF7Sr9~*Vp^Jp8!q3<}r0Ua6NpKl>lA+Zp`#xeLn zZVVUvGfLNOB4BMCks-qS6|a$01awB9*7ay^b$S|s*rFFZD)-mFX)>5>@`s7+9S468 zksG4idzrfl4dTTuz>W(HALV|vKRbHIOYhxOObq|!JeQa$fEcFoadnH9YLJos2`MEUHZyYE0;;_BEE)Zj#iLw zUJ>1*wK8dhDqSOLD|ko$!+tkSS3?#O7uyeBRp(eF>sTb`_X9ZWAg!Dj38?5vI@#R+ z5|kkpI~*Or(E#BG>>&TxSr$s>sg^79%GvasX$8YVy#uYlZkJOk;!e@rLoN)3LBu7i z(+=|e;qV)!m2MgX2(-8qYByvtJJ2aQA<@WgmdFFJkP47$XoSgLXCfl|B~b8*(*AxM zJCWETI?!93is4K6E0TnIHjA$2K1#SXd%(Qhm^V2vpBxe46C&6Q%=D38gDLUeL%<=? zT^ws$ieF(Zp}%>Ba!$Mp@Y%;j&rep~S$<8r@`Q|2$gd$&1JjCsjUIV{!>)uO*}m?( zR22R}1lec!TV}8g=!CstGNS5S2>&?i3m+hIFx&-Wk-@W-{RC|fEMuL^$VI8w$l`2` zlReD`SY17R?g05a8h)OIphx=-Of~0#E51Ua5Dtul0}u+)ec)%A2ub2Sa3pDT*81J2 z_wM~!Y(fw9N{5$5@q0E9FXK*{(TO1-ukb-^v7CW;7fsDvb-gD|L|3kBzU7Bie!$!x zmDo_1(~EC3L;PwVm7>N8tyzb$od1N;%JwqR!mG&YrPY~WfwzhDFs0wr{e#AP;Qs@i z;uFI`-rsE}RkdRMm$`x+Ap(p%e|pvG+j*tl!Obh=EN|3Re{7xH=h;FG@~ZWN2?j@k zZ>qWxRY|pX7%ysSF=VTi_!f)sYq9RIfsC$gc3Ud=XtnW+7c)l>76QVbFzd{f2^bS6 zO4TQ68nVG6rkts%7GpWKFw-GiTCJYpIi!AICHFH_*CN?0AEx_qF;wlMC(3}bU>x_im;(SS%A zbZ*~qK!yH139)t7b#$qwX9t&C%e?dV(!wKuDH`1OY@4%mAXd8vDlUC@I$UN zl&r+Ng8>asfoNYM*}k`rSKs}U#bWZ>)0>=$IXWWUfHg<;ewb0}>p1~~gKttLD!8fs z!;K9?^y6{wNrHSGLMqXl?9iI2K-IO3gc;uB?AU&)K{wW?iY*seO6eS!T0+;-yz5}UO>tBzBTizTWEPX#lPV1b&hGFx(IY&l+{$7vkMlJ(d@I5D81g~(%xYSvA)HAfeHHPA`FHQ9H@uq?Y$iq8^9do{mS!6-zp&P?@x&(hFleU zG}$S|j}M2xj5GXksPYCDW(XU|_PkX%_r&O!o%nSYiHQb8&x-%M$N`^WsZYz?6WhUz zt;q28+V7-UV#)Y+I=MF+N-%i6+P#L+)xLn-d-MA_rt?aB{V<}G2S6;~cCkA)RY6|+ znC=DbdoTBau=PQ=4OX1s!69c*^-95CZ%8BJ#@XVof`tEHW=YMlEewDoW%R|zD?2FCu zVvl66v{+O8MZWMa-FK3-ye>F7H>PcY-Bp7Oq?zPp~9k6|Avl@oKyrOA3Zw#^jiz>~ao-8+iHMQ4;NkbkF zzl@9jM-wMDf_HeO+88ks`oG`x0ZaV(09fG&R2xT#7bfy0E_{#-LnBWJwJm&F$?I=V z1s`C!%}pE0kFgfSOQm+({$=V!U= z;iRZDazC9RpWSYQ8?NYaM17YeY0~OIG6Qv>npL-E-`y$H)9TJiFBK|qGV1xUgZA19 z;%o;3MUBtXWj5FWr!*BR8#cj-siFJ;`kA4Wnu%?G25V01i5*m!=%D5f;xkZ)hF7e< zDCW5$Gb6)M-q6MxJ-uE?u2MqY093ZXeSN!Rj^a4*?RKySJ)gfI0Fo)YdN~i}3W-S^ zoVOUw8D|isFJ%?PsbDTpY)n*if_YlDPt1=zn`eRreV%fJA*$04vgmxe4O~@H{InSO zO?QAzXfY2G!UD8!nH#dVgF-`vXd5LXK|bwLyz%g<2B)=Occ0-h1%1^xxK+NI3vit( zK2C-)yWK89UOIaB*F$Ywu@ zYLYn1=JO|fhBiM{6}umoWE8p8??u9y?a;+rT|GJcV}rlDv)yUs24(HcnNea{c=z z&M|HBa#y+AjpS94vOUjsvK>$T_->D`9I>Du=r9UFtGr<(7QAN%dFrTB1t$!ua7ki>|y`2POQr)gqx(QEus>htlm}Z_zir z1^dPceXZ@AX12D1)%FzhTS1rKv_a!beZN0OTg*|`d{UtIKWD5+cKqYZ5C?9pV+=Uz zE4^7D>hj@N247hyE99k2?4Wa(NQ$J*RxrzKRWj0zn+<*bt#$=-Lv&VCr7tBA7Y+W% z+UKpTqnL=+6WTdU<)o`Mcuy&pI=5NlHzxVRpN>y3kGO4r%opBC^8&egKY(50t$>{k z*Iofkcr=m{|D6o)3)oX%5t^<{9N$Ob1=v3k8AQY_3fxK@cw0Pxl(FH85oCjQOa0K! z|7n~^Uq>hpaB#f>5cjlCEYLd>ePpZlxc`!~!(;mRx1X4jbH-0}dv(E!iPvc7G-$du z&k^&n9h^G)=#R=-kzBGjW-66*@RHkJgpV&c)i{ocb79p9k3@WLk+LC8=Z|emvtyiGYR)vlv`otNf$)0yrx{2@a#BH)O;<$a-kjn`Mf% zl|F3&I{LnqxPC)@?-cmA+Z%T?k@N2jb!6FtT()@tjBWbwwy*7*aui0aSlo3xnc4HY z3SL0|(A9oXnCwknm@duS>B0jD^N&7dh1-9mB;78M{clvm*0K%)uEXn6KC|_0LT20s zGIvua?eXH8(uN9tg1@;Q-wJa>#Ms?fXxnw2uRxzR}zL}r!0x)n)~Oli&n;c@xaL%YN; zB2O86zH&7Zfr#u#C|BA_FM^FI8nTu{$=*17n?#^~VD(V`M-0|-9FR%UXft?+(`-gz z<=5A3O*c-o$?zBP?U9W*g2qpS$yG3O8B!(q|4PeaFbV6ISfq$wEYn9FL5h^M z^IOY7fdskochRH+Ko@iza&+wfs?lTVwUAGu>0vA>Hj=9Cex*V97Bh2vP3%ngI}Rkc zpC^Cu38~kTJQT;%-!4i&7FthRC`vFBO15ftfdvkOck-^m$Wn$}?LSEIkv027xzAe? zI~aIj{#m56LHWl73`_X2-|qL1qIS&!jx^tuhUXm&kJKfaD=DCwJM~SMSBnh)I}?a6 z`#a$#vrfY@KjoJ+Ov7V2Udpb$QAr5G*BBZj+1zp|+1wttv7{9bgsyupLwNyFMngdF z1t9`d2gnBG8Kne9!ZdpuAon9;hVeP`KUtoFXJX+0-;ZfZobHXHqv*euFd_;FUl!8+ zFZbw9=caQDdl|BLA&Li386+Y#^uZ1euP^=9ablE`nJ&wDP?bU?vV9)LvvTqklfse}$|*csE7= z#J&g=qRYQ?P$=1s`Lj8tfj6xnEK7nTTCxbUs;iad9PUWu%6r`~0X$S3A;S63&>pM) zq#@OId@H4BI+?py0>+PKFV?J*h&BTx)AAw^rJXcL{t|!+uejp3&VP;<4@1Iwy%Y(TQOf1oXyZ?PQuM(p7X?UScV)J{{#qp8gNFY_7=#%h=G9*n=3a9J zV=Ol(owP(Gz@Ok1c3ZoS{vlfxmXxVGVbrAw*+@b9b+ns9%i&m~vZEr{R}vNLv+W#X zdU$a-sAcBOl)5eD&%7!e?;gl zViX|gTj};*PK*OnAM7D$M&9lf$duRv&HBP?z3TiJ6@#Eh<{&i_i)6W}aBu!bq*&k; z%LlMu4aI-WT#Ceoh4?svJ)Fag^aA5Y#-`Oi`6!e zKSVi%dZ2;+{drC$6ec{MN@iMA&>(8aNn-ny` zieX6)+1hhDjf%}qwUgUpk;c?fQQd8Ti=HPt(cne;lt$L0$i7_56CQ=#) z3>xC2?fh`Czcke%5J1w|ZP>ubJl4B=WJ-ruZgnJ+0hyt?&lbh z6%TS?N66oaa2Mvim$I|K7xY8~5(9Rqj^B}l=1qdez^7rL%d9LfiP47L1yg>U!2?m) zv?&o!hnwe{aXy0M(@j}RQuipKF%>qQX;kZoZ`wn`W8>?@G8M&*dD)rKmbnET@<+afi*hb=JI3SDR;_NC1zqK! zY|oQ+<)-xyv$gy^4OUPQ=-l`tEV@J4y=xN}2w+3?lZe2ne zD*D?u=X$rKv&>vO8B94<>wipH$7qzFa z8Z87)yh05Si$h*@T(XYTl63AAuXXRxB@8Xf;xv!qwRW`! z=S9y%+A8jryuJ|oinIc1<3+w)V>yuC{UwoGEUT5fZj_Yks_Om^l) z>uU3#^+1aM9EqYvN3*)oG^W;*=yX?#+9T5fb&HD~iX*j+j{7q%UZ2(%DLU#IcMl8@ zW(mX%FP`q~{shJCvBQcMdI|k9rz-Irj}`f=_O7fq(i)or@BII}b*X5VncOvE3zNF<*$hx|EGD9EYz>YW2EU+g|<$6AM)3b|t?jYY=EIxf2I>9#K?W%91 z6!LaZGXC|`prrGM;FR3uA}6EJiL36h7(bA9mgG?NCC#P!;lv0F*~MW6yIXYmAD$LH zS|_)YJI?%jEC$SL#~tWeh7oYWoPU@<)WkC;$!g?h9WDul{<#OK7LA7EnEWNBNK*7y zFi%%WQidnZ)SP7SWAFyW?W-0@D3dwY`BL+6P&okaw&HR#b@b)m&(NbLTLbuPR*&d0 z0mkJY+1}89p>>0R5bo6WL$!jkN!Cq|*o`q>Nu1*oEo8nKw`Tu67|9aDL$~|p)-lPc zuTYNNy~?=zJ=Sy5)9qPN`2+DYf3>(9ynEcj1Ws@3!xx>nm(Egn5z_*Q&Gx^aHlA8n= zH`LGsO(ugk4B5$N7LCfkj21-TyTh6Chx~WyjA-^<@o6Exf$zS{Umnh<$Hq!b{E?6p zY{nrM<#~K#wQT(T_fj(aS2YR_4NsD>4=xQyL*?o3y^4~MgV`40WDU#lB{i^o&GhBk z9kfUy>QR!mbSnHDirmVWxw!Jo5aT9C(<*cAUWnGostDyXYb)KjBNvUuKt}5Gz)mOa z+hky2A_8MEo{`SFGI0GJ4drzDZ^;iAXDLo5{;LbU244bz+E%On*|<~5PDd#yIIk~o z>Mk0EF2BAhy5b+=FV)r#zM=V{55CqlbAU~GlFo$fsJ;63g!3^t$$p%P0%zFt;Zo)H z2AC2{^QZq;jT>9%pRsUW+8ov8PRj!rdtwEQ{4ur=I+gzJ!Bq42XQ_lW11o7RBH9zx z)7aot-oF$^v>i#{@IuM$;ES0Ci%-%s>Azdx@3tK}AdS>cqbVAdwD&K-qFIM{iT3Ig zBYXWlf2Zw*XrJPklC`a&$P*5>ebFicba|+1R#c(^$eRLHAQ~VGxL`Ii_@zlLa>S(U=1wj#$r$ zdf2vCzlF-_B!wwosK{Zy{~>VmOi-q{*^7uffn3Ek*{b}Sb^FD1Wa-hZlV*34t)I>{R^qxhy0N(ahFc)_`3 ztdqh|;YKu5!>-SU_ z3(%dPoBib3LMfvh7d4+L7wZr`ye@QXqii@-Qn71f8t-8oKqwg!g<^MIgQ=c9uT<=! z+1%?J>DqRy19jeu^s-apl78$DG4JV4dbPXl{M1Vu9fKfrMB1{DbWdk`3t3-~jR9jT zLA~N)raqk!wh-cwBC4pN-K?MXkT2n>FQ_f0XuBTX#wgTMd^r)<>iJ1)$TKyXKef-~ z@_7W2be~V+#{n3JiF0+mJrlTBCaDtoypw;;B*pbNqO?bvSKfRC220jIZs%j5&!pMO z_)E2-1?*3qpfI4d9LFS+!H4FD*kPO=qz5tWQ)(4Gww4&-@b$$X zez)7w61KVXo2}ZHwCNH#pGfBgy`CzUbfxD`i#%M_#}VQ;X?=Z*xP-0hY#|?XEe0#T ztWL-LIbkusKg=q<^qHnB+i5ygA6LWh;% zA~@pHWA=4#e~nt-U`)5v=GFkPsVWp8-5iI$@)l?1483ZyV6z|5ln)TfbFXN}&QaUC zFKPTa`IM3@B{`_ft>pcFjd@blgS|#D`kh4&9z44qcz!Gl6yc6Z7p+k7hr|EKTIKj+ z%^%cLJzjI?FN>r5Pug0EI@8ey;j`7+YGr1_s#>~ulHs)mG0b(Hg&i4)l?fb+2MR#Q zhnSCWTUJxCy!zb+Xx7-OB4?nn?XMU+;FbHD;DpV1bT_6m2rh~~@a-N2vsYvgLN=do zb&H|QA&Wn@baAgY2`k(!&|klp$fBfhU7EOe5ZX&CjvNCfQLvn!G~s10x8)MK8T$y` zD!wX26!rqISPBm;m@Qy$~X=R`D= zN%@~FUoKH5pU(rdT0CVrnbh~cw9R#U7_zfYyrmBvvr;9I_I z8s%<*RiF+0?V`Fru`5hyyME{ic#HD^#tL}a6}a0U)@k%J*=Shc}*h~9jzqy$0ESmSCMaAntS8jc@?L&(3jAmU~D%T z)|LL|0H}ShZ`u}ZyvW6Sp~{=AmJZHb-qS{>Y_YTm&w|~{WgLS^^iy^5K*SZHEh$y; zh8%(NZ>_=0Yg-|}7k2?&-GZ>VnN~xp5y>%-c45@&)lt_otW2t)5YZs&s;1sEPL}tY z5!)=&>h)m6%Dk) zNKC;@3tqDnw|GR4OzC2)lj@((a-RhKiXO=&ClFNPo^^9&rFUOATO-4%D5g%^@N)$6 zLv>M`KW{z;;?83{cSO4k!bI>svN_^{WfQ~I@&@^d?EapsNGWwRD8J7%Qs*rqQMt^0 z;bma>$!UtYbta6ND{RteB&WSeh$!{@qy>(z=AlZeN z)ju_m?Re{E$vR3HP)%(P|7C-YB`LlQucjVA9;7onFIo4Q>8F&rnuPHrdIB9qI^j|ryNVzq1&IAgc|0>VzjhbnAH;18v5QLJ0Rk3YQt6D9yW#yEmAh?h`aSF zu*_)cE2-9Gdiz1D(>&~EK%5*Ea-U=YYBfwCD-bjGEAwFoxyTqS`sqsT?Vk$ItCF9r zDFH7Gw@jEBoe!#wmL{_5@-14kiE^_{#ihGK*S2+(Ne-o$^TzgYpkuf)?mzEz{W!9)Gj_O2=(f(HP$CyCWQY8vuUSPxQJ_r37CPyV zbBtSa#Sm_=qCQ~?<4QN%#AXy*rprsd>3BZ5;Xoxxar`EppV8Rj|#adOt`X zTwU5SqVY^VhsG2#wDeKYnR&v&)>yEalQ2`L;Khqz?4+!fTXnfikz5d{KsSKcyOD72 z%jVC0+7gNP^!&=Jv9Bo%kGv{hE$W8ea~ezX01RrhFXVEQRCv>(68vmAF)lOd7JzPTK&vpTYJ&v=<(v2 zI?_?-K%xukS4kEu={0cq%K6gvoe=Vo)Ou-UmK1ykxv+WEFpiQ9agxLIA^bYj(r#XF zJ;KsBK<3>)0h#$YYtKuvd%G17iR@->-Woi${z!bfNJaSEbknfuY{>EHcFQ2OK&yUKAzm`pkVIX5pTBNvr`;MhQzfPYg8n$V z!zm7wK@wKGUK`-NBAa^drK}N??t|B%J;rq?`smEXFB>Yw7Fa&A@>OYWftj?^E%CLd zG9E9-$F?@ho#`d2=gUB)`Vaf`t|a7dNuyvG328|v-ElG4vE(1M>`GR3qEqOey%x~* zCm|wXC#e>shH-G%mSZ!fq#)e7P-=GkW0-nR0t<2jHAbA#11@UXU-BG}L?>R$at%d7-^YVar926A+I{n*)u!~juKp&}1U4$%)=sFuxu zz0eH{Ko9^GclCUzCIBtJaz#2^HOSL z`ip2?^N)fC1_Nm+ez=yIX8$PzJ$|3N!xC^_p-m~4EA4iwjrV7qk@)Kb2aNJGVe*R& zO0JL(mT|=&jM%rDfs^KZDizu3gRV2e%w=1sS-6B*!pWukj#ayrLUcr)!sa*!xW&{B zEh`5KC9|Mx91O-S6aq_)R?BE!?6%hp2Dvp2Lbji|Y-JlIs)ER4n`Yk2=*Cw1T8_5x zKQAg6Xi}Ud&!^Q^GZTAg#zV_4A?#0LF*d}5otyl1JTxRPvsl6{OOE~@=m z&G2XcBP(iJ6k0ZJW}Fyx_bnEYdWT7BSyB)d9s(2t@ysUG#f6VrfL`FTb;r2OHo2kl z+NNe68rXbymKyJg)#|+drs}mCIycCj8S|m+HTxiYCKhSkicXhoDR7oNO;~g)rw&Wb zSVn<}QgNGI7MGHypXvF)!IvV_pO$SY<}teX)APx`^1W`SEff5*GNgua{x9^>2|g+g ze?N%B*b68B$fZ`gYOO>r9Gz3+YkD;zbIr({D#i-_X?i&Dn7;94-LFyCX3zE{xSf_u z*0ULM&62~k#aOf1TspU4USO>`RP)X^+?5DJ04KqN+{Dn2N^EkO>HUNAx7zY?kChEx z!2K-rEOB!a6F%5DOSIY^!2%o2QV9|Xb<_h}WB+DDruVrJ{G3a|(_JYg z#8RWEVao6%+3(GdKUaQ<3wQlq8~XjWVpCMnBQU9{&2}wj%?iEOL;h4Ji0Fu&E{VkY z4il3%gn?zjq1Y$L%jX8|Phf@ZgZAVJAq~5to_w^9*8xi%$8`8kW=~itj;a1o&5Q{K zXHxws-hcn)gul6m(xuT}n1GyI=9V~2#^?5}&}v-T`n{RK1NY?q(YDVIV0*=)3#6Ri z&Q?PHhN==chPt)dz5eT}<(O(k-0)SwvA87CR`)Apt8!*_JSVYq%1+_SuW;4r8u$ zzpP%Ft~DS?B2gI~85|4Lvjqp9uF1^+RGDTx3RVEU{?Cp$>TnVJF`*I6mdRdEEh|4- z6;W5zbqXzl6(*d8!XEh6)+7(Imb4qY;Y7!)k!V$&tTPY03`gXPOA|wVwcqID=@akl zHXSiU8}Tw@)%dwI;xPi4%{6?trJNo_Vm96IQXlnsoxoZt0cW_(ko5j7ybhjw0QKFH z=p#8Em(7VXz3;l{z(Js3q)y*S6A|0@kFeASGIP#`ZI_w`C-<>Q5AEOWR$T#tKW|%j z{f2?(#zcb<+!9!7K>gk0xug6^1FnMku=~^9B{pH9xwe_V71Ba;zs+~t5atoW7-tnl zU*FAr^Wj8ghamJ+`D#hJ~Qvmg090!<~=5e+cp-;FQMrEE0qXTC)P%4 zHYDnGIp8BFblQU!S|nWMrL(s3Hu-@-6<{TtzAQ7I6}HP>{0sV|YrAfcsfdc;fwk|3>*;nM6?0(%z zT$%K3M#|+&-!s*|!UR9rC@5RBh9~4^e74t6j0T2Xi;&qh9|RO#nYW!U!0);5jK}KO zySO(0FmILcx!eY`@h{|&cXe@{+Y0NX`NwiBQLCgq19x~(c3L!s*jCtVA0a!=>IttB zc8!mJqYK*zm`op>$UAhP-0U>l0K5&|1mc*9V%YzvM-2IdeA@#HX<^>#HT*KRsME}T zcOqDwMv;xZ504kkGEQJ;$U{SsQ3i4zs9&kPUo$W?RV}#GSlh)uI4FH?m(zFR-9}g@ zD^~_)`XPj+^sTAniIce@Pq9_kRcvUWl-Jm6&_1BxlUjrE%M-FfI&0p%CIvrcE;~+s zz{)5KB_9v1=s=Kg#c`6P9;l9W$6l394=7w3o#dw;zqPd_F_~4P`iB6w`BSztk5#v; zv96E}kBW1t9YMiR1*DmM=zT>E0iM z*rQj^Jg{7gTPCacK3d+)U4`J(vf{p`g5zjvptzMzOlHgDX>z2^JpQ zaC$Q-=EwegH@If=zV4EoL$Os184;TE>zsZ)jhN3_-rDr{eS^+rdKG{F#Ryk?vZSz0 z{+V!C+djxGNrs!Yau+==qI5WIE$3;>`h^_F;I{5xKQ~(CjtSX3Z+*YW;B^;NuLfSd zUVPSC&;Cr-BSUx2=++Z$^x1cJ|6K4j_+hd0bm;5UzW`uD+FT=0rST_H2DyJ$TxwQo zg{9jmW_8+jj%7B>X`PsUhh7b?=FRojE^R&|&~@l5?~PxwJ%5Ci`Z>>IaAmDDb7TVG zsu6vZ=t93gR|IGSXlE&J3EVbhddH(;+F*}i}&j%LzX4r0b(gFMu8#AXGeo=xxt zX*=wwOtingR`=i#qStX3u;yE|)}1dH|C(X2@GLXqT}R<~3S*Phrp`Ju9*e#)G*%p& zcWwl0@8!{2A>N_gA%)AglaUP0Zpw>ax%z5ZN3woRhRxkLRPw^|ugq^UZ7^ILNSnQv zRUF;9ee37Ga)=o2ws;%9x#>OQ%h7ezSY+qTVdu51`+>SD&66{dwsyqX<4Usn4Y>z_ zFITZDwKMe!a~lS7)1j}SjUTMxNr;&o$kJ%GZ-?)O9toYz-&=Pnu0#j>_f(TmvVdID zS7y&a6ncofr^^2F@ITREx zhwe3j7O(rOH_xxW=~;FrKX-7nuVtND8a7Qk%=#>+1!dMZ9>a9QE9(fixFTXPlIr_q zipkq)9w@lp6ug6Pb$fsbGpp}X3IavACXy!R%~XF@EjBk`!+iDn;L`$$=_gPS9Zijw zbk)R*%lLHJ1C8DzMyT@pFY7{F#=XA>hRJNnGpUWIfae1$gtk>*E(ck)xGhyfGsP#e zVxEvv;A&BB!mZq^58%3I{1sn+SA1verYTOUp^vxbQVyF)h-SUT)&7zHwcsHO8XjE- z@Z9^3?zUd0#mw{@dKl7WFc+>^G<=(^MgYsU$Ft|xF&_KfhI|*f?ARApMbKYMq86DB zXqm4`m>Uiz=H`Ae6(4Px_MbVcP^88)g%OEE>J&b?Ro{CE`_kq>Z%-MK!Rw*^?oR3( z?byp^1!iWqw8A`?rvY}wd{=KfHS|JGe)blmAw3@T| zLv`ovDBNE95d-n0Z>K5nXaH6G7-vKTqKk_wjZ*b2t{vJUj1~B)tCFS0T91nGbf5}? z<6{UK00g^`Pi4v%CZ%U&46nX(&{S0j+Yjf3hn_oBD^i(F`J^d9)kHxdOxHLu_e2zyMm$o@gYYi67#$e>LHsQ4E8>c&EfCCJnYZ%;q^e_sPy+2 zO|yQYt)+pQJ#nTgJMoHbK3l4 zVF;?1h(OOHo3hz|u%sk1g+ki#Nf>o2ewOmP?7^HI{|;r3A+P`V{k!s_kLaO;RUXYLHGFr=doo8O-&88C z1trU=CU6V% zjUQ>sd44ckho|i+^wVt~gE@fYk8t}5`>h8@Ln;NQ887Mlc@55C*63`W22S2g|7~zI zUHE&>M$Zsz-kYG9%it80?L}`<1c_Abma>2{*qEE~;ftm%rcx^h02+r}iyQ_Rxnt+0PdIqy_!YemydjV)_b6fNw`E zlrBS3VsDeSGGFp?n!!1}J+iqYr!zCqUX=01oUAFp{D+wcoTlxxR9O1119TY{zFe!O z+D$pl+?7BizKtr3<>E>#AvZE-8mVY@y%W7|lK%gAdI!h2zBk&tF&f)!Y-56klZK6L zn~l}jc4OPN+1NH3TNB**{_cCvQXEgKnL2sQ8ar&)uL~ys&F@ zuFgt+dh}b>w8<7ntf5o61vbEmxffE=a}GBH4?!3dW&)k;xS! zXiV^NCIH#y1*Afl$iRp=Y+>St zG3UethQ{rbK!SJ}NlV7im}GO50u3EocxB0xF@ARhRUm+30>LCYEbn~Mrrta!ca9Ea zNFigw;sK%fL4n-o&2t=3oag5bg$N%%qXfE zL)Lnf*LOe3-t9CCf^_`SSlqytdPo>%5XP#Q*5&PD&dY)qC`3TYeW}RZ67u@z4=6#Q?r;mpt zp8eFCk%(HEP3((V%->NH`!_u&y$IBC&qf1fUvz)cCAK$wD2~t0-@nmg&ZvlPMl~bJ zIWZSUp1(m0>pY`NW^VN&>iIzv`32;^V-!vxjQEtyF8TBX*+Oz*=RmgAJkPunGb@O< zdPHaVdF5y=UTwqxJH`XUK0BZ+MxsH&-*EuTq*9zV#hcS@qZgmSesAlpLEKNR@BR9^ zp3rumS%gnlaCOd1t&FG3_ef|IE`)~QbzJF~EXtyC(;^&TgnH6^bi}ms( zbZ`#9?|rE3iD*T5&Gp=e-8`c?siL7Xx8%Tw9lNLAf`7RxmRP-v21~nszelbveV>P% zCKh!IZ8(fcrfCc()o@H*`c+!9Vk5b}2@;+yPtCD0r}MFS(N_GOU+s28ay)r)aU(ez z#FdJ0`0yg#!)P`A!nL_JRANAd)1S`j6xI+vMs-Ow@H5800y=oVV@Ig7U=xWDb{miN!y0*|1uW97jU|=8 zq_~;a;OhZHQAg&zT)E=gp_Pv84dnAr$gerK_=l;up!D_RUgmzu`nd}(iqBDbATcZ> z0rx3XXJo^pzF@M}!+|YvTDVp4eW~0Fe{n&-akbjc;?clTMV5_NumX6djfAfwh>Jdf z(tqKFuBBL{1z_d?%3;q)QmOmQ7)Gdai~Vn!Pr*-E0}(TB`V>7bz&l~8c;rGU@AJ>*F>cCd7gq=O*k8NJ#2 zJim;0Re1uBAv$PVvfqUtO$DC~ky3#kOJ_P#Bfz<+5)?#fCLt1D1zI{mpvd6Ea`roweLm+h)-_AP4paWh5G*gt9}tLr8&W6BqPFF z>~LCas1PiYc`Bj?F2@?NLz!|S(tRv3uhywYrY{~|6%`vQGCkj8TJK(< z)!J>-6i`MUx;uzaLhbc9x2#~>EZ|>U0wB~gSU7x$`P z#$n$v@oW&6x+?W4FXUnWzcJZkJrDUiK5@x${9i9I<;X8QtE0$Vuz!no>^~hjtwRXI zT~ReMaEME%8i=gE#`qUE0IZW#A6oP~irmOg`_IJ>_PnOPVkQOl|Ihl_0lu|rI$2N( z`&CzealOrZXX?S~b<^Ha=)iEP72Vjk=n**%Xf2exVE;Xbv~(7cu#tdu_j7RTkOemC z>SpC~`0_A1z(1V@4G)Ss?g3NpD6$g#d!C{HledGdM=$}Wo`YFuo+{h=zTlY^Z*!v# zDjJJWp(ObF!LILZugmZNp{ldl%()6%{Jy8(^DO=wF|Z|2-TU6!!=ds_` z3N2Yt2^<_Uv-A3eEU5EAt%AZG3C~aB^c*ziR;P4uwCL-oEmBao1O&(KAWMTyJH8QP z{3<`+84o6FP;NYql^K0fy)P0Lbiks z316g^Z{XjPw1uiK!a^U=gpK>c&QSb6^`3!ns&00)Cy zql|+KDi&>P#f}!=U7k_pe_~Si!L*c9&x7iI9kn1ooaA(zN81w2rP+IyPFdgrkRIPN+?)T28k72d*^l%4pwo-eo48ZCE&|tdSD#A;AgTxOQ4Y(B1GDZ)6-6b1%Lt&=_42;02H+J^n2VKwDjyq zxr`AM6uFT6kOe{b2zOHbQQu;|G5>@ACqMT9DggX&D@i>)Ex(j^+Nx3KtzlGA->kY6 zeA<}S6n2J&3Y|Qb`R<5yv(f+fSqblF`|$jwz^v^FAGx;$sG~*v z)Z$P1tzJ{O(Ea=03+|nF9_UyF`@avSyBPlW!Q%wN&1Dh6UJl%cY<-J0ts_9v7`QJ7EnI~3}y?Zmrj(y@nFyMvyo_C7t4zA545somEb zvDRD#1!g?IuL|}oj>09|bMt$vb!9Edgz2-#hoZg7IQ}^h2;6B&fg_mP5r~NNCc|ZGQ(z|ZI^w8#(UQg2i*9_qKy!7#pU(yUgo>Z1Z%sa$%@fq@6j-i5HvA zEF`=qq|e(oJ-W@l1j|&Xv6n79R)cTLlRk-lE1Li##BSH^0CJ?RJGm`Q2V(Uo!lAIQ z%x1$4pxT1`pMSylhV+|pY7&y=L}l9@;dC`reQ%w(cU8waAKE6)_o#E`kqPCX=io@mpK@!qyzP0FjI z{c$jPv{AUlo<7w0s^KI=25;_6!1IBmm$X{{@03!sX6(3I-Fb`*{wzqq^YL_gh=44>^@>ByF`EXVD$_DR!@a5d+n3zqfqb;w)o2#Ew@ z|KqzWb9Z(m+MF@2d%5n9*Q=ZPASLK@ zBF|=0^}frO$&{CNS6w&u>yUl+96AZU10sqZAQDzN+2oYzs`pX!_d*XV3AOAf@ zqj^dmR>RRRua88%X=kJc%JG!FD82}1)r6Dz0n*J=CzH8+fKsJA?lXkHCW2+x01I&Mgw6)gTOsG0p3H%+v$se-a5DX%eV&LJmtqBCo_5c z$==Sb1Jv%Q^a(e`BTGe?*jTfj^W@4uY#DETUssmT>q%y5ZzWQ};Slj_gJ5nA7BG26 zGgfM&5{Pb^BjyI?j=Y!AnF>ZwgW^p97~gIs(1LKHu^Fa(2>Ey&5bUe6COvppJ`b1} z>CXGllh;?;yaew+Nwo({Bdb>yBmQ5VX*V1GYCGKYYR?a)6rg@H*rxzpk{1+90+rH>+O2B{j#^eC@Zr)^#)r0S?x&;$xQ=Vwxe-gvhI9& z46W>B3KWdYF^z>{jL{X~a-&MdOu5-Q&Z%`$yV{fxL z7D5&HY{j2lI*0JRiGYjOYD~Rf~EJI~F z-o?F$(rZrc()S$>DPi6VJw&bFi;Eai>~(e%=vB;btByc}wr`xb$D|vls|*zM@7!@r;N*yQ08_1CiuRDF^Genm?$y zeqJz>)`6r{A2yrA0>;S;NHJl4;=akpG`{;|%F^T@P2 zrk!n&1Lb7rmkbA|IhOgx?zQy?h^mqsg}Q{r?(m^~{(K;3H@P;EIBk=M0du%QaI{&6 zvOZYU2{xvlyT4m0EpJSAa*h1_d79H@Fq3Gb<%ip*w_1ml^GNhtwNW#gTyJ(2I?P)! zjF~|})VilMJnubU5*?yIyy_jp;~;WPg;IZEMY0>O42--0gJ_`hf_Dd3xpQP#PQ^XT zk%>CoK~SwbOZYC+wRyDona-o@tt)Y~rN7O>eceWW|BC7b|^U zx4V>M{^O$cpGouo&fBH{UbdNu+b~{z5Z%gH3;nup4AzXPE{uksA~dC{Z^jDb=J)?Z zLV6x2y16>F6DE?CLGkHUR0U^Pm)g9Mikjd1V4G8poDQo3M`absxI%xZqYkuR`3P00 z@OB#G9uk*0nEJxTi5}Jd!CS7ILjO5se&!nQyJ|yySePFtIw7B^`~S26o*#0ur%Hn( zq*$;^-j?d)F@}@r!u>I%$QGaw=ayi4A8!FqH zxDXG#teO%D>Kti)=dUx1HB{PGd3w5NcaZnQ2g(rV7CY_sPgd&lr^i?#uGI7TX)t5q zDpk_{tj!Xf>XvOUW+}v+>9>RAF@zT&izk%po*Grk;S6)}k3!AP%v8f;%ts-(&j2yWtDTgCvxwVhu}7vo#UmfyrAi zb@Nl$-o>Z|P*-eb&=ioc6!ROkYx0qbQi@9FK!$z*$JT71e(a#|x7CEv&b1CL7-|2C zEYI-o1rsmj@I$CT29-UFiiioW$>1yK;zc%NC7F|q%bY?TS^8;J(+2A$fi07e`Y0|X zK~(aROZL>td^#pzm>l{85Eu={moR0@tUmd&r4Q5`_RBIzkN-PRAXdPI?}XCSl&4Zt znxFew=k(g9?C!X(y|MPAtfr^J&aDNNiz{@ZhB(IdDn+s&bWW-+YNOWjpLE)o_>eGO}}v(t1?W?5&u2bUY_cz^NA=!qvf)OxCOW9?sOKDem`Z)U1s;q zVJ-fzjRLh7olvop)(<|DBCl*!n7Nl6q>AGXV6qzh$gA%~ z&TE#)@vPj0je<-~zHk#uOR(||GD$0Z&r?Ppt(Gi6L7+-zvOGU;CNiPSzp}=vZHs!t z1_%@yT5ckVb#hB-E82RN6Kz z)ZEP6a8vgR|8{l|qSow}3oyc?F=39Q>c8QXiipb-5M(c>l(rO4I7Up63j!JpL)of` z)60I6SisefEXq#gjP*x`-dZi|yz01baV zMe%FnQ|n*CcKmG<94%#HfUc=axk{XM4jCL`FD^U2|$fx=?5I_Z|^{BjUTG1cZB2LR(D z2&7le4(0@nsr-aBV(M#xQ1$`w>Df?!5NVm5u2IdITU?MD3m|zB5*|k10o%j`HS$}} zV_n_KUl=y8d}EJk3eUz}RX*=tv-qb4fdO#;VfA$xa_X@|x%s!6R6qG_pei*GwrE+7=&An3Yd{)tCx6QT5naBloowD@y zhklv&qH;jBCVJVsbkze)d|WKJ$t?VPpF_oand7xibA$KUfAplr6@0`AjCic`dIEXZ zUMe#CSu(lxdE4a$)y4)sGCpE8mjIWM+Of>9Z!bm1ogWnw zoXK@<5UH#$kE)Zo&4fO2_MMe}Q|NWwv%9*xDOBjYHJLdtp0>1J$l89zxxNzq$bk9< zW#GRJT5jR`Q}OwV(*jF0P1JqSei^W|!!*o)g5WA3h^OsOqM_lZtr!0V46|A_5fpU` z0(yffBjI2URd|1%f%q$DVgre0zZSSMdrS|~o}o+f9zge@^0|UhloJ$!k+Rl&KvZ@2 z_u7El`iR$HiKE_K7v47^FGSb6hHt5gKJ3@q>6C(zjMKlcitO?(x??~fqE{D32lr{9 zw_2Q7PK;X8W19=@=7hP|qtY5x4l`Y}n5J?=gM~HAZfV!~OIy&{ zuiKqY%#qg`b|vstL_%;ln@7F=F6^{nM_C2iyok7jD=V`Pv+W^Ixc;b2M8!^L;G_La z)L=68Rum=0+zWkq}lAa@NP_asKR*RKek(g&Rac!-GzzC5T+6%1g>}!k#x*7qbMdY zt{sWZtAcpkFM8R}6Nts;7siXg5vd6)VEH>9aH6xGgqWeQkzA-c54ZGVbwFiwya@OT z;o8&lCm@Q%>H>CW@~C!L9DVp(xwGUB1>2%&4*Lgvw4U$$IZ@f?!o$eoV@ftx=!8O=C0!b`GHtopWXSH?4dPZSu!etztq4mL%k)+ zZ)fOof@g|{)2_bEpTD)#Z6hZ=vE-y;_cqC3L^4?d$Qm_leDHY~lc)83L$at)u!;f( ztgntEZk`(EFIOQZ2nK`sf;&Njk}-h()!FdI_4v6a1ll&4$&Zf0R4vF zcAkQIjGgA#$GyN5pRP!gYZ34f;-kJgF6PCf5EhZ^l0W5Z{p0Og;yPWk`n6aegTQ02 z(<8t(4lw-Qkt$R)SmyuEpw`(*3@cx#kkOA1b@xNKGhx}4bmVu|UNpy4u6)A z@$25k2a?~Kc1S?tZP!G);H7lR`7h2GZ-hA8f+^*_JHg+S`s`~(`vRS*fyw?|ac(&p zVn66rQc_+Ruc0m(ONtXE5I~61-yMI}gYZ)>9OjlZB|VBD`F!|B+GBev83wC)>9*e) z86NownO!dNnE%r6G|})lEtGvXMb{?niU@bB&TC{27@JnA&r?gIvOtNsdc{570JUi-GT?!2n8!gCM-rT9*2(i z#^B~G&HO?WtnAQy7>5`qMj1z~G#O$B`T!6LH^1Ldt99xJC@YoK z41ljFSNo(WEsxW?T-KZEzSr|1rOSIi6_Z>KQ2!RYOjLL~6e133j!)t8IIc+heBLaT z(&_@T*wh&f%*y~EdD9yqloXAzzfyr4vpQ)_vFiF85@pH|APDdcA%>N+;MAdwfLv0s zNmAA^8Tt3qpXk;(>s+&q*gUn`1WH<-nK+WWuAD53q)n5Sw$R*khj9b=dvg@J_%-_mYgnXrGoKt;D#Sr*d4f?mvZTo2l{)Q zK3|Yl!g2xb0Jj zsfC4DCeqR6Uq60%M83&Pn+!cLlH+7ku7jVaOxRY$bv^8I-LYJIHJ8o}*gjldj$Si67v^84!PnfUO@dwaKDGfKuM z=TGRP$#vln7D`7d49}slJ|Nn9o6(B5{-A{;1hEj=Xic{*Nyw4y-80%`shH8u-HD{2 z;j#WC0kjg&AJTRQyXSp~7;;b0FK6A*dVb4%iAhwBc))tmCXs2cKg9**IcIQt!THu(n4^J`?|Mg5+*PhUFEdD?9aKK(p2&KpV5@{g6?Q9qE{1na#DA*3NUj z?_`8zFCPhe>U`pa*|f6Nik7EJFdC4$A$~dxzys_E+`{Vo?F_IvV$P6`fh>RjDzwrX zEZU*vmsARNlo-8&M=Rin@X{s8X01%kBfu3KS^rpnod8<>XH&|%=v*lO5YzsHaB$-V zv(~ol_byb3)8w3!r2d~s?4Q`v#~zwA@@pvVnL?l=_B=n95V>vnSGlk0>BnmEV+3Bf zJZ4u$N2T#mJuUBxYvvqX$o&q8z2ltua81!2`ZKv4 z$=!}qYVYSD4qVTsYR}a7h#JB=LR%zub&6R$!Z!9`bhbVPZh>2qG5724w_73*!(XwB ze1r-*QX2Ngzoxa{!XrA!Z!;(1rp~iH5k1+i@hALI{i_JXy>;IBteHRPz{_b5E)(@f=wwl!q$XKxr~ciwwDdFl05Sy$?Gos+{B!G>8V zwL9#l*&yP^n%ze+CFSs?)1)E|biYY5|2>ed2)yyr={53CHY>s=qGrD!( zc8~DE^-KV3wxByLy>%WkeHpL*w2I2kJNXW(HV%oeJF^Rk_Kw?(2|-!Ul~BBjkg(** z4xZKzwB2_BUw7B{W5a{7v&}d#Fs6N!3ugSO-gF-hOAQRc^%0p(WVJrY5W~GH7DthriGHO_F8&AuU?eP9@EDD@89ig=@8# zxJrON==ky6z2q%khaH??7+OsaI=>b6-#y*j@s;o_UIu+qcFY~owlX2{7-ebtWOc)_ zM>B3B^wYk)u_SB&?Mo9-CZEcbXT~hPm28Inc~~Ve(Adw^I<6nIs#u__Ez$rh7UXB= z;OxlnXg;Czy=rZ8Q;f8?-Gl-4_+Obi=5Ps~bvN{U<9Ex5tCAH)=10r^Gm!Vtd%xSl@yuXNG-vHcQN=m|F z#G$(Wz9)$0(M6_$Ew7d}h`l3~z=jeHhA(Q`swHSs!4c&Uq0n$~Ey8+jKx&73qf}>MqbUtA~r;DpBW=#Zs?5GTeHmN z!O<}hB!{aE%sV20!s7$yN#i@bw53*Iiw3Q1o__c=@o`U+(*Ibzb0xNOjTHL#9x?DK zoi&aeN~%`Y)|8J7!A`v$I?RkeQfA1hA+j5qi+!+7|-+jdF=Bf=M>>R?XmfozrFhGGO# ziSR_qfus%N-+;PgJ(Jz~{RsaxOaL;(UA$Ku{PI2HE9=0RcIoB>6xC?dfYcV; zSI-=5@E+#z;Z9H#_8gI6B&Ly!8kv=J$K%Dyoxk=8fh1b?=h8^k3Z+O?> z5U8?`Qi)~JIxVOq$lwFeERJP=AYI@FHequDyJ?5b%Su^JN-IU;St6Q8aU<2vGq-d90&-GbEP1%`&P zMu-6{7%vp?0pEABBwDyw)+OIjG}E$PzpGuTnLZPf_kZY~FtQMU#lR-^mT4?y=$c%a zs*sv6syDK^japYQoo4ILzO4PPjVBhkPFl8cVdbpo%F^G|6i=&|;cy8m{XE<^Yo>b* zR0@O9sejD-LNYDEj#WO139&4J|7` zp|8W7s(m+PgrMjRzJZ*=|DBFQ_YpDyjC{FIrn>VNPq)j|8?}%D@gFeg=|?g~Ry`;4 z-Vl`a_<5^VGw`z(fx-&UpTt=dq2U=Uzm`gqo2N!`Nh_Rbdh$p;yH#wtO?nfJ;*gj0 zA66_v4OM%%1a2kR*l%H?-&H4`v8E&e%rGsUL;Cl^_6Hd(@e&UB6@8{@qcK*sl1hcd z(%W{LT%(^_7{j5tCxcSJ9XR0@LvzQbaQrNzVE= z1owg3uX{j*IbVqUMKIzF z6MG%Gm$rErY89=HEQ5&EiZewLT{%Mj3{&yYSvL!VeU=x;XjZ?VcDGS=a;0T?Fb@)2KU}M0pfCaz{1kQk7FKR5fReSzP^l&XzfSun@?U&d-FUL8EVR*y z=OT_Fmd{`jM0{A#nffbzadA6rsmzP)2IU13aV!*)cEFBY#m|nO8Yp6h1jx_6IUZP5NXvjrxCAG-C{x3WBan#a95)@>a5VaM2M6 zL#vEnChJmZm=>Y9f?5Tw?MyHgtX2UtR&;UN?7lLEsbGH^e454C2oe*USmQ9K1m{Z& z$0F-R>e?E?PjbGtWH*i_rm{q^KVQ=g;dk69t_T{moOq%S*ulyT3Q{A~01bV5Jebsk zmpw)v-!-nJS@uA$X`)z9E)kIBXNh;kv&^o9btl0_A}o2$Qygs5!TMS<(yF#R@QOES zcr#_#^4g+ZG=K1Gy?!O26rmUw4IR^rCEJU^AGIQg9T4C8=g|9X20p54VmyB*NPY+t zkNK<2so7;Tkx^X(+uRKWmj_280ik+RR}QOv=A@`yVe~_~-^lXt#e4evTPam3Rl@OO z11w6d$T-k!3QUFmuXg7{>$(STzeKA!Q&mFqWmudv@<4h^t0mxSx*KI_wSB$`lbabmwHYLLToBSaNyq=J~3Gu0ZsGU{|>B0;N`FVBQ(xiC97YI8XslE_{9I%Qc7mvheWc8r^C_G zD-!0tvR4xS5w52z)-IsD=d}7}*+kGV zJtPhqvVT)5nDJE%11mMdyB;Gy(DGL#4@6`k*JSI~sQC%j%S84a$&&> zmUWFx1u4K-)PNmeMcmdJ9wz~lUY~&l3;2?h!YkxxzzE^GTtnKBU)3m!V=~7 zNbKXeMw=dcTaVWv%sr4g1U6JLA4wsIp&Ku9h2X+JoJ=p;a&1Ot*5?iXMfk(6&$!9jYRvA)5xNG8iLrS;7JRK?49O_E1sEEy@|T z;z@cFEe^#W_luu+VQDEL$)wM!>2yO#ankwv6ut-v_eDul6QSf?k{Uh$l{}W1F2bl< zScvBTX#q}Chm87+X5DV-=;#n4hcIr;C0})LB8!AjyU5^~;KZ81$OeKii31s_R4EsZ zN=fcP)2M7VLdSLAPuqd`C(C5RA)^L=ndBNWeVjkRYWQkPq#iYWjv@!EJrtNlZ0v+8 zao)fXOI3?=H_68RxPkBU6P^nzR_m+W)N?>}rs11l?BM8KUfD`L!ajO( zX4?#dcnMe-Fkx3t96qG+ZEZ~}Q=TRwKQ18w<2a-`FBre@CEqQYfzw`8vKtJ}7A^A&FUlacKY&1mgca7^D!Oo6GINn=A8rE~%uGu6GNb znZR&MPq_hUd2hI$I%)Vi92=Z*Vqtf!@bGw+w3dc*z@&PkHFxmhcSIY)2SAr*S*Pu& zD@n=y@vNC3qDAyf@m!6rsVlT>AxmXxV&RZG5p{s0MhkMXElVMnYiGEm_DI@jD(y%S ztxBbe>fymy;unpoC0d?FOe%4;KR7v+T)|vx6(z#?Ly=k*7~P}UBM4$qRDj$ zx26mxT43Nzyj2KTGIOwk3q3jYUzsOI3-2)qV-}VhaJHIrMfd~W-=dgMSoS=pM#xC~ z`mqpsw6%MyKv>8F{Ub;9VWl>IKFzEk+#A6f3dbvoOCM>$*Ru2HpbBA4OC+yk;BS|5Qou5^|8is({kf`_08-c(#Rh+RnKwXVuHpFofR!y3KN1$ z^5-6hkmWf|OgVk#;v#J;7yo*ulQe}qR6pnmW9&C=*j*9bo>N=lFpS7dLkg0?fHj7= zuQ*M&W~?_2@mdS~FLPuz3z7=AfC8=#$owY^#aM(4uE9ol=7z`bP2v&wHk9voKe@RR z_|}L7@q9SHcPyr6^TlzoQHVFJIKWqkn@2}}=h%BE*pgi6_yvT7S+a`+E)|@J#r!4h zQBkdmb%m()U|-A7>oI%z5k*fTGIQ$v13EM-Pcam1z5i-mHxuwp|FnwN0ET>gs?ZoH z8$O-Ac%~vhFvQh4J4GSujxzU^rJe+#Vq4FspY3 zmdpL&2JikH9dq%xb6CkB716-&@|--U6#CyDQpw=sYv|u?P0F7U8kBLkA&V~oTt?QC zOL48h`|-Mhun8GU75}jq@@$Rt4!$DjIeHR@6HrpS ztm8>`qiv6-x`RpF0zzCGsT0BT{|bwXhiM26A|^u?t*A)sB<_xVe%rR*0MR)4^fOU_ z4PGnYTJt2zj+9A+e$$~AZ%uC{U*NIw$eQWeuA--Rp~ ziQ}_GnFCb(J58T2UdDz|Qpgk!bj9mpXXlusQHn&5XX$<)(y^@*7rZSIR+4pg1nTsx zQG&mXhWToy#~~hrjh5Kji9Mz8Tg_H#m%PT7WWbGHpJ8JU>lEHf}Cm}C27+3ef zuJOs}T;RTS$L{7GgPRTqhq3JDBr7r2?JS3n&D-_2S1olbTTw*G5Rrh<00m^*f^b^p zdksH0linW6T-(gyiGvT(dE_WI6WxWRuVk_ZkyifEe$9eL2ABn#tya3cnK5a^*eYE7 z4pvrHQs7E_u+2vlzFQTzeNTz#>BU<1)nYN!jVX0S+B{@zfJdQs`cv)qg|;|4OSP;Z7%jrG%2 zDL4XpFlcXww4JFm?iyBY3>BbHCULCmG=5@TR28TyVU%P#A(mP}68~8G)T>ip8dcGp zx%B>FWd?LU?W}V;02AkoE$z(j&$8+J8=kBJRYkRs%`Yb9U(pmzq6l6t7wPsj<_;0t zso(qaZl_ldE2#@EJnr8g3@$G+WmmVeO}r*qyl--FU-H`?U{#}i4kF_-q(4wbb5RCa zmuN3==QsMoSzL)-SE6SK__n^efOqkZx!)Nn^oGsgNhg^lt-dus)d zHH01aamJ9A!;!g>WOAOIbNkmXF@3R4_|1)6?4vGcqe1T|_%FA&iv{^m$@bqM{ZCkJ zIda0!hi|dopFaD9mR>LwUNI%}EN9OH$2H3Eg&))J-kz>*;~yPy=z7a4 zYag(G=A9?&uYOI}6XLpgQ|MC&)*oN_SK>BB^%68(V~eujQJpE?|99RiUY|#Og%EA_ zfmMafWzSB(p6!U@Bl5-KF&kc0_3Xi#0^0`zTI5<=8Mb!T3%WN6waU`O|isZIYL!``M+dvD@; zd~ztOF!5m;@R z4Dwb|R+#4g76CVXu*YTa75uV{;*49$%Mn$u!Bwd0O>|WYQaWIvgp9L7zKAj_%&1u6 z3?&BV>_uL2kC3nGTgYPK`RL6%J;`S`<nKL_FFm&qu)U=qxC-M`G(~$^BYk|QC#Ky&|BUwFDLB*V6uo==dVxF zZS$M%;;uKA4bauJqZ69-D{0$RqcfhX79n@i2gXaj212J8ej=iBe&fd^YblO$+JwX+ zNHm(q0zrUd8RK7g=wMXsbzKHmlM8o+hIJ2Kf-P#4Oe8jl&z`XoeL!{JXdd0sd?B7- z01nBR8oDo1k!?s)yKd|KC)%6kQasDz;v#qpollF%{YTD98!hPM$6z#&@^YkiL^f)K z^(3M^HC6O(xy9z~7ANFNv-d6k>G|ZCl)MRGyuLj(zV*aW*R-~SeB;i@uH-95@Kezv zj6xM=aqKr~%ntkx1bsDcfUauZBT$;?%a{gu-FcI4P49hj^jN37xeO8;7N{vX-QxC2 zOa7VvCD6h5z_2fuW>)T4BGkHAYKqA?M^lI#2g5vtjw4q^CF$LgN8!V%WqUzg%T=Y7 zu?S73aS+_LT1ilisnx+07cDTzDAMknsU1${K5xUN4{j-+HR)-!*fDJZMbMDQ7gldt z!?^->&4;m}#_34Zu85)6MbuQW7mLgh4-3zar=QQJb8ghZtGxI?XgqV?la-3$PY;PJ zx9p70R(|*8v@(Qh-RnoCou0O~n?m_I-+uZzce_Gr>J`-n*J6lud0sWX-JE7B_CTZO z;;Q~^Du9Zu-WDy;KMtPq!ew9e7 z?~13C1Fe*%maG#nLuiHbd^Hf~exWeAG7%Te9nVPLdK0sq!8 znEwfj0*+S&^n+>r)WG}0_%y6Ui{&5Rmuj$Bx;B=gw4gotspViIm-v=gIt+v|az@{^ zv@{&q40eTV<+i`43%~n77D0t?D(9Hw_Ny)g<)TKUN!$wCOU=;^6Ca3{ug{n>AiiWT zM|QQUOn84{kb2|sGw7Oz^CNFaWC{f zk8=2+Z!up(xnJ^km;gLrR@hU5 zr7a}Jr5U&W4N|7)b>Q+xv>^{;t~O!_UWhT46)#-_#)0X*x%dWHt~rrR4(yP@z@Jl; zdnDZntC-Nk5_HsLSy@wzFCwum>y#rU&mMV=%F70f{LF{VO{7U=5-hQoFs$(riXhehOP z5`Ym+Wag+8EYqh1+`Ld=U#I%>%l#%rNz>eq2Hf%PD*6aa_Dk?4{OxP~8y8^$D-olFMV zz2>k?IBi=sL1PEXXz;KI$g>^&SuBi4uBfFI*|#0?%w$gdzjm=rkehnlY>%X0zjmrf z2KQLN?@8)A1g7b<>{_I1G}e)O=%M{s375RY5+pBNXGq9OZp@ckLy`NxM50EB7DIx0 z5kq?Kh*&jXgtI8j*CgS0ghm!>NOH64A|NZV2#F5pPD~84heSmd`Hby{Ej|Cnw2Kb` z9@)1#e)91E^UJ8r*VQBiOnbt|OX9&w1|{N!J9k&kETo6}e=4WZO%$cf*B}K4+%G{% zInVvZ$7ZIrqJoG^7x69_YydYyA{mIaxG8C~LYP(#UltzvvMMe7G!XbJ<-kKBVHikN zT-0Q>5IEoVS8}$)j@-qw$%=ZK+V9(CU{tiycq6TIR?g)Q@Lw#(Ls4l^zm;h>V)X+y z%O%{Rq~}@T9R7@c!FK#7Z znZ%c49lqsv9#p49nVmYrYf=}Vdd2!mSmv78867 z*YxQ)Qlm^QC?{0a>mHg zGFIFeX)ocL;VJ1(r~J^_rY{dwoO5e>P^KEuFMPt!7A_v z3s%}-_xH_fRq<}jIyK_oD5@gYlJ43z^TSpFpl|Gjc2$zdsrb7wzi+kwf^Di76{bs# zl20JT@E1_#35Er>c$X4TKR6Oa>i%?~TH7!|)WCQDnZLQrle%&$*?EDBdihm`B)|`{ zR{c{toJB~HSdtc6AY|w_T$3Hgf2s!TO z6nC8rs?38m{CP&g^kdgWJO8}@4xLpKkvwHpRaBSqh4ouT<0coF5j|DyA){n@D?U+A zC4-Z(+AlCNny;Z(fLPii3s)hueg4?cm4PzgDfDwhmMC5$Ltqv_Xxll=F0HDfI9KT+ z;owl?)R6a6*^B1;5FRWomOyEoyH7wi$u$Ytz>BZt6a|H5JQ|ucSE`0-O7h*7=`Ka2 znIKXLtldu)yE+gygN{-zi(g>|O`oxN7@VjLydqm%(hgQI&sk}px+~47FYMc}i=K~A z?*2sby@f91w9EAjKdd1sS9nnm(=n3`@^&lpFGNh#=Nax7tFe>nmkMS)>G>ZPWl)4? z*K%3x0w96t_qe!~T`(*x8Pfj@GUt&MlEQ+*a81)r22EyZj=#v|;h3@n2Dnzk-UTJ0 zY)I~f-)WF_*MOY9!fs&9wa^$9Se2}}Wql}tT{Ns_t^Ug(W#U-T&*@O=bRiEJBw~q< zQm=-=2ih~3)%?M!5-2pb7*^boaZyDLU?P6u9yqD`$~ z!TJ)3cWyb$gsxx1#z@bse8|T-J@JCLdSfzGzm9 z58a@5LsJLcbThHy508_^MRene#Kl?AXM*(04(D(3Pd z^fkrq(efxIHJnt)^aVnFwmvM+r6J~>wrIHs%MSBnSPiQZmz*(NO}Q=r&M#)`KQ&<= zEzz^sn@kU(ZcusM&g5{#hzA4^g}fGqny!~XtCKE%20<-|3&&0Rp@Bk}5PDd261H|e z89bLMhdr6^;o=kWiI}gUqV%ioA1%qqM|_B3NjMU)kWvDwhE+p?A!)x&@0ca2b2DzR zBEYeyiol_8{Nr^4UFtW2M7j9z6LwIB2Vo_lcnX%TJh>do{Q8A5Du0(=^4&Ru0*jVb zApZ^81`RT-;OyUw;V^Mi;|U%+P1slt-Mb}dn24-TN)!n{qJM4Fsq)a7@@<-oaHs|O z5&^;NvIShsnZ}#;>5al^3lVC?>Ohe-pnCIS#YZF~ItWu2C`B=d)PnwP%y_JU*gp?FR4QcX zd-F#`z78uSq_CL$l>N7N;3$mq4SfnmFNjN)tO?&ZV*CrmKR`xy&~hwO)g2!n4{}9^ zK1<90mH~-qYGnhJy1FS05Gn!v8Or_c{{6?c;v%q-u-MNaox+%<~R|HOs#F^gbfW-%YHK? z{(&GM5Cc{6FS%=?lG}m2A1&q<5|WZ(uX|z?6fb5Dqd&h}(kZmDHuWfZ9j;#cZ5Kf~Cy|JxJkqNb*#vNEc;`1nHlfI$lfC8c@( zE_tOyG)h^J@+=d>$ADhk=UY}rMoDQ9>TnnNKiz^Al*-A=^E){)pP8BI5B+w$qplz> zD|?WjC>wi}oV1PvuIwfM?+W|BFZktT>P}*YfB!Fqf8cTbCn)*9je+bD?*At?<-f1! zlKiu0_}~8gd#6XS1N>he6QzXw>qq`?32EVfb{YJiB`8S#<<0*RKtuVzH!1Z0ul~Q- z{Xc^r+dY{~I)?24+D-&}&!Wql4WVIjFs(O(6P51hA7#+)a^I7V&D21c3FJ+l?1W}I zD_ibpsDusvBqVgt#@>CIem>)-=y0l-ejHNK5ev4iUoJb2@7e-@9Wow7i=Pdgia8R{ zC&A4U9%fck_v+l87Gpa-=w7^1^>13H3mn#08_ZwJFAf_FvZ4{ZJmFAeXAVtpR(Zaa zBXTo(!b7<4Pj8<&@a*3(U*eUT9`p(AdGc#;UE@n}KH~JG;9l+)P6U_Gmt_ycc>luN zzUN-7{S})gvaZVO$g2bWbl+Sp14j=yoHm*JJMMapP;J*4p%gz^*cXW_I5++4;Eprd z7nxv6)fs6cHA^hGoHga)gFaiQ#bIIbPSumq_p&w#5r}#7n%KqscEq@q?G<(C zsdT;cu5heI52(DGZ_vPv z7nqH4din%HqUy$ivk|$l$w5 za3tCOr{u@-SbKHlcGQ^D_48OYQi`zwG zMxtJRo+0KXpec@P(CYBE5cGz(Iuky@+pCX%@*yM{gKc)1`eIPJ$|B?8^SzVj#eaXYid*7ggp}*&PBD{6CI#kL3Q?t|8rml{o z-jb`FH&v$jqv?xxVxN+fiJgGC7EAK&eG{9D(=@Gz7HEIY<(c8qJDK|WK-i@d#`LZ& z^pLgqy*-wP+++q*D^K4Zay9Qr5(&Gv{b|svSRW+Z|HcAvc=!o@&L-V@&*MohjwU9* zC4GL_)QungJMT>WCNVX*CNGw&J~YJCsFb%VvOJIHe~CIDJoybU>1>}mE!N!3V>{le zpK8(De@*UmLO{b@2WE6s(RB6mxHqaS5gX75xi=j;cOQCfI=JZ!W|!VWGaj+pSJVRU z?xB_OYZy)ni&OU-VL4wbxZ9Sk585W$pW5{4i-%ljKhIBIQ*X2U!4Q;hd&xJ@xl^Vw zqxG(hRBFtZF^V2Cj(!dKl$XhB+Da53%yUI`^kf7(8Q|6iu<**(0xs-ZPvEt^jzk8% z+0(5j>|L%dAw)d9hQWmLx^9j^a-rNr$W@!(9$v9|!g!}L+IEQ0VaY}4s>zUxDbCB1 zx5-KCcpfPVvyejd9bb3833amV#&yu9JrI<6^V;^ahuNvCz+PFKptEBzQ~Uka%!F`+ z_EhEOI`bA=_m3L6>iPgQrVhT-%STB?ST9-(T6pwlbY>>y@0KnDtsDzluFq|Q>7k7!6_Ne7xZ#uW5Kt2}>uD?CvHHW5qpWtF&?_o>RnbD z&iD5m+oB~oa6eC+RRG;dU28)=%#}t5x~F&CoN}Ape4Bu1I3bwXP7Q)}vGp^Jhau&4j5o(5>d?bjBS|Nwv9Z6&PZm0&H}9s<@uyuIdJ@KQU`~mi27^iU)Ib zyjY0#jlVEh&m9IjdOoj60!Xqo4xvLt>;CF5#8=nr&7UJiCGWPon7t5k55?acF`XD+ zte*Ylz8JP2bi^Iok>lNoEf<{vH9iyO&6mRl)9$UDWjuvBFBhq+?8-dYRod$uVPTzV zKrxFnqH6rO@cO8az z=fFC-F0X%t^7dsmcTt)EF#3)SI4|{cE;^icox%0MEfq2z?i|O}+dW-WxvA}YU3R`c zi6lK*oAA6ue1F2KmWB5~KD8dt7kcNx+ubcTrZ@I^h1Gw|RZOX0c@0rqKg82L?LEa} z&B<$+^#MbP35jmbSR>H z;N;qR)CD1rwJ~~$69~~w4qP9LHkn`en`Qj9W=%-6TkAIX;~O2Z%yIMJd6A#84uZ~_ zwH)B|G#_b$Yx{WpEd3A>jSo5&bV=#f^)MuV`swLfQSoTDlCb&1jMsCLpojEBjFiV< zF+(no<58_|viUj5EGppnV(U?p z_mlp37Emgz)v>d_XWu3_r(I>OORc;cTvm5V6BqC8h7tX1w=?VRY>XOT=U_u@I|pJHp?yZXG7 zDZ{hn3-#}ri>~az$#SN{t1{(JsbYaUVBPJ7Q1jLBs?L>+$d5zC7RGvC#0(hiy-=Y( z*O&F^rO#)#gGRb5s#k-GHVbGv!V$fVu!%F(d#qi&F}0W2bqkh1!^t97uf(lA`RbN{ zzq<9{=m;dl=_MCyiD?rMoX@B z0xjSNUGuddAD0b}BZ6WQ{%&32W$owK{kXYGHyU!~`{#1|V@*lfz3;BhFD8hiuN3}7 zIp9Tk_`cb>1#v~D+CP{$_XMOSXoTd<)6}%-C))MR#3t%~&vKh@7dFt)m73=6q0k7( z5TQe2OB;X3g5G`{BKoSL&^NaMyuCUa->84cYFu1#uXbKgZ#R@BK&LVS3$@@Sss*~A zMey_@&#K3EFvJ~-RtT|}lk*J@)*QQ%zaG+TovOnim@9hG{5|YES9JM)L~jMd=gpU1 z`wp>n#pbEoz;ZhfUP+3AW%|` ze`oah>?Xap>7C*2<^3!8>;l8}y8oJa{M)!#HG`87tC!6$gVjf_C6M}>fehRmXgy$F zy*mzS-u~8(ggXXh>2%H>#d-9z&D1(W6w&0{gUxa{K#0$+#oysd(Q%t-y8UnChPReI zYsox&`N~W3x4m9h5Mn zcxFx)jD4&)!(t;b(^=y;U&OiwGrSkWhlx~>kgB5*B#mV(1=Ite+vC1WhE2GsBi@i$ zl_HyvoW$0Dg8MtAKeB!%e}%Rh&sU>E_R5;i&>3o4u@Wg3uLt2ysvLXmzSPtPPM!@AMb&xY~fr@>^?hn{jzsJZ!f`Gy8^Ss zu%7OZqOR#RSKCv!kyKF+tOc8%?Ofs!@4HN=yV8Rue}?!`QJWu=sdH@Sx#I6n$I{){ zTRY5`+LHrBT!L!3y>+w!c&HCdP9#g-lyELCHD5SG5B6*c$UM1*!Nkf)ORL)_4PYPa zbE&*fb`yrXILR}4W$sU02q5jfrN|Dqm1`ouE}^F_UCpC|2fM{L-3jn|AK_RmWRgFf z3I;`3ORTtSk2{T;3=W>*o<)BXge4|>`i-BDDTEnSSe%VH@!rPt^87`dPeMPbl>y~_ zTK4XIsOGKmqQTC&*fsbtu^5C}a?{SxyUpd+u z9(wy$gR}B`VfV;hkr!`@^_1L;JlLbXTMKL4ZfxE7^s|;2cg`Cd4ZYOwecn1_E0`2~e9xwcL%yP)51 zO7p2ADk0z-y8x8iPDKJV*j1;rN(kF+xlpa ztFlM@s$9P|T~9?pAia`X?GKA18mgsrmvA^8O0df`ySR|OMbaC`J#&wbG{ryso8z>& zV2(s!Fs`1bxQ4}PK_&Opx_fpZlAK&bzrvP&xp>xil&awKI&0w4s|96xgt~@d+M9IeyR5!&F9SYyeT!!7G410PTxuoZQP5>qrhA29*=A!{e=wX{*W zft_+?f7CycEI5(yGPYPeN^l-#)2~v5x3sm66dk}y8N*yV=iXFH$F`YoC}t-c<1VFK zAC;X{_z2M#o!696S`07nuUlhi>+1qt$>_Mh&3McSF1gRcgWEAb-4cSij5^pqOg4$)Hv%lewiQnB|ko&MA2K`0#%f7Hj40G6%i@wRW$!B)j()v<22(Z)oY zh0LFcI;1ytn?D(OwG5md0KyRn#hefydRvx2);IQE|8EhUbc;Mf|=<*Eiq-&8!bP$nXnJ zgL(gv^3^OcL@B>$QS4WDbO_G*KC$xt+11$P{PbFPdH=ZXFxpONW>PnwH0VOGNa{0g z6Pd-}(aFvsuZdmz_?Lq$`eoOQ@p|$a1Bf@RM3=S&gYGGcGlIU6JEx}|3?(R9T1rn1Zazl-Cjy175#hr&%ON7?r@NPBGzjAfG~w^l{AKZ^ z_0T2P(^S`Hg+^*NTsIb+J{)csgg^66(JmsctApj@U%q1Rjh+R6-#dk$7>m~0jip>q zJ+g@|Ru1g)HppxHA0}e84AuuXyv#^8$ zNK}M|hKE{HvN8{B6qal_CE1)Z0R#5QFbUu^T>&zFyJN8y5UL;JyDm+Mr#Difv(3iy zC4tw68~G&wTNKk7VyKBmcaN#(&b{4Gd}?AZHJNUMY7Dv#6 z%8>yBTcD&rJQWm09c#oM;pbw{PBpS3ax&MEV@Z`t97EzB^VNfe5kpr%*uuGua}Wgn zwM6OGZ0p+FcXpVqOYQF3K1yHaxcGVnx8`1ZbGXQ8o5=VUWxE1v^K+(aN`?{*6jf_p z1ZVZvR)v^2el-&+mWg$ms9^O-Xf}2CL@T*`<+RgiBcYA;`ihM5xsrsf3o8_QMKc9O zc>s!5d7FmNbE{rK0n@hUT))k-8Y=7*T01F{-;2^wu02x^5q%y_^7{oZVE!@B?qU$l z>s$M-yhJoT&#QC4K(N=HEp%K|jOtd94otQE(8A}m@(;x*G#ivq+Xr){3(dUW<=rJ+ z`&o2Uk#P;{x7Zjj-=qH?%MNuD1zCv)gBy$(9`FV{*aZGEmNO%Lc)PHX_~T5%EUq}l zT$Xe{L7}f?bnFw>{jomPZ)4h6&)RN$hy^_7Yp$Ag6#c1}}nVmPfK?lbs9_)g2u{TdOtH+MDE2rs&kl6s11Fr_1)1AD2j zYtM@E8i#=TyzaN0PEQ(DyOl*2g$|+UUX^9kpy+y|@4n^uNn#BG@i*1fBf}u77@rgv zvs*t;7>o_kC_efy80|UKO#QP}o(aEsbSN$DqsJsDJaneFsut+k2tiny?W;SL&v*CW zz=zw^S;B(m(2kex>mgZwnWMk<%Zh2Y>X*>VhvI?uM80JKlajpL@*bg6$m^d4*txDO zk7{Z^k8>P_+^PBMa1pT-764M7H|jx%Y7xIozcOJsDYB(axE$Y*csC2iG|j;_16B@SmQU(oT5Symy+}a7&RA9 zt{2yg6c@NAHzkHvU(#kb{ni3G-mp7SJi$t+kW_w`%5N-mlah);#C#v=vm&9URdU0o z|Lp_l?(p8fSe`exw&wgpnbz)0%#lgcqIa1L?&-_%<2SKf0u}iYB1Ye7LSytP))@YQ zWTI5zp9P8a&Qu^Y(oyi^V5Kl6l7XK)-n3Z4o#s-iMr5a9VPU>lxKW3EVd=5E`SJ8( za%%X)H;XeFxn1I>A^xp~I08aZ2+^5mn-;>)$~D7-j@+}YvX8kR{#ozwStf=NxI#sp zeP8+5m?g!vU$}wiwXo2Tk45+G7SAtNf55p4YAuE&{`8n8pcAZSB0w(Q+#B&VjdCJHghF6^zB#I~l&mbWf9)f4^s&v~X4O0tYkWg}?EbWL-m z-RE%opizZw#f7hgtBB?xbTa(dxn*VTE>0>1zk(<4pH{1I;6DruS_Hg*Yl=pS!d1dl z@2!6%=98tFlTcRrdnnj&3_tXVw*BG2;0x4ZDE;BtJNoF?68ipIfj=qNsXTkRGtx!L zBuRuvWriNSu4SY!rFQ zDob^E^m+oSbw6N{`ikDx6l3?0b5w!O!HUefq}w&BmPCg<)Ua|qon?~8gs7SWxA zT@>;dWY2RpnRS~Rl0MdG+TIJ(itlJg*{RRCZ%3nV%icRBV*#aSOo`U9nl1VB%NaFu zOL(~A(Pepv(Nux|#stmPYD`(S0~CUSQ8=(V6wK;9k2V{*@9wI{{FwH~Vou)ZlK}s< z8LxNB3;tG{D*fxKxIp6J?oFYB5D+Xg1n-mfwO&2;K?eQy*G~SL`1C7;}=#GZC6)<_iN#tXJj|BKr9O5 z@vVwYRnf~_3q#x0-hJ#y9}%*^`Qg(`@=HkV3q^I!K^DaWD{~8haH)Ok-^8(7gz({N zk{_fX2830(&y>t;2G`iW%-{1!12eIqOH2OX$oUia*F@kW&uXRtRO z^jslspLZzrZhbzuJe0gJ>KHYk@z5iuI}me7L$TAalsEZ>s@UFPDC5)E0v#=0d9_#f zv*5$g&Dc0m4v(RzI3@_kr1B-{wgUB1*(INpW7$4#BEEkRGXa4y;Ok`&rx`vPpJZ9x zbzEWs|B4L0gIq^r44(H42@mOzzU+pv-b`9{cU+R7Z@UqoC0C);&Pf?ey`ytJ#shWC zHNJi)dX8I66#Wh6Yj5-}O=?5wO?+#FL`4d7VxYxCn!8D(P2h@conhY6=?r^PLevbX z2hQwwdA|jHYds8@Ies=GHSLQWebJe`+~|*+2(|@8wlYoHWR3QcQgL=LA05otUPzn5 zkk+;%rv>9d-T%UU*qbNtE{wW$YZP(4&TCZ;l|1zt6JYQh2k#9642C_`6`zs~75gtB zA|oE~<1IqKF7i3X9HpE|J}>4+W2!N@heSRn3Cpkva%pDww>Ft2IP3Q*qc*O?daoq2 zR(jHWc)05Gv#lvQq2haEY6bq@`N6iYBndvkzI|R;`Jib|@Av!M(C*FAqbuJD&8^(8 z7j}9OrPH2v+AI?ed0~SbTS59w!k|@mdBkC{TS6!&*bL5_;P7$`2@_Eue?Z2 z8*HeI3YDYvY^-MgdL~y|q@Q4nbhDkni@e9rV_m7~BTfe=#dJ$Oo7lgB+sK0CSQCZW zt{d1CGKZ((p{Zo#opyRVKX;Mt%BsNMy3N5qIB&n`bjv9kM?X zmh7<9y8O9zcRvdZM|{z)#|_Ftw0D;FHdDFxUFm2toPvRA<*T2bT*vSni^lUuw*X+X zHWcd9E2-w#&V`$=`V8|Tz3_N&3Siw&7irjY0VzvHuszmnQb$Ih{;G6wur-~FZoW>u zU?WRHO&w{FUd;GVgX^m<9?mDRfY{z$a>U5(hcuWOfk-&!Hvt+ixgFIv0f6=`lE1c= zSQ3!Wl|%gRsjKqO)As8CIy_a*`1@c*_Uw%AWE;8zet~B&_DRp;tBrbVGQcZ$r*;;w z4Bvgpdy8n(Mtf{7IXiE$++E>sG|^i1LM*M|tDF8(XUZ@>T!w@cu~Xk4<$mhY3I5cI zC;u}(`E5F0D4r?fZU;wtOM{)e%6^K|VM!_dPcegI%o}cmor!f>@K;CBS7BtT!-Fje zMx)ySY{NF&!_s z4ETKmcgt&5p!Jt0_|y>1!8_?xp<&(1jN69JSgkWrU6f>K6-13qnfxC`)}$4;(c!2@f6HUhX1DC`Psm@I%lGnL;X*A&h7PF+*{>Nh26;NQ5&q& zJdN6q{6^twHGao7usK9# zGOVy1xUB9D6G}#lmoJ>#!lyZU<0<&i)QZ?m_B8Jam$m1j>$n|#Ph=~hN>Ym`@784H zj?nN4k_{`$PmifX5el7V`E8xtIx&ZPhGbxpEKvaiK?}Q)sxbCKm!5+3$l>>HH;Eff`$j5v=B98ji+$ zR(x+Xl)F(Nuw9N#+w&A1i*c6w0U90ax41NW=q+MUrYrY5!Kz17o#8R36K&e4xe%CS zCFO^J!IR6JgJUZJrTjC!-n_eM5gmp75vL{l+Bv_h1R}wyQ|g;>))GYMuAKaBq~O!N%g{bpZ5~V_tA}-1}gS z`edd;_qKEJ?b>~^B+CUaSO8r{PY8gw8I;`(=|pDhP8_Mk?b=Pg)yx6T!<~GL1wobF zlN|)Py!#H}8+3NZ^PkErL#6xUd?qI4ai3Pgn!Wp2tof`bWS3;u-71*u4v-NMd`SD# zM~{b3WFLcnxaH^gEmtUm7gc?)ju84g!}f)bGo4^3CKL5YBys(!hUFf`Ov^?GZ%wGy zvFzQ^Dj*I2aX(a-P1(CXa;bS`R}01nSYrZ4r<@jFL>u3wBPEW0O1;|En~=k990a6P zfWg_^828-7o2!uBYH{tzAQ*|e0?^2(11&BmoDAA8L@=ZYx`pSF1T8rEpqvL?3w^u6 zGEcn(^(>FkD|$?nkcb_suk8E%X)Nx)bPTnPXoFs0O9I2#{@_bfmYUcPvM^0gLRdq)-|lv^sznW#z7VRF$e z))8e~P}MHO6WAzW_%^-&{#_`mtfe`lC;9<6)t0D=;#T1XcN`p<3D0YPmyc zz2nxtFT<4l4MrYf;8&`yAtx{uE`@z(|B1 zZbLF3qzwCdP|Y>8!)^N)L<3f3uqBE1bB)&@yIe6fmu_%ULZq6KM|Ex$aPVySk4{P~ zsfvz?i7CYz(nUzR8SNcw(6`8#+qgGqHL@~VLy3`-*5tqM$r4AnQ&s&sbih59lVWYT zV$Jb5p>w1OjSQTd{i`X3(fIO8kovYJc!Q47mnsb%B*P9I+1S84tBlK|2GtVc?w6RT zl-Py0EV&TUWzwxnH~kY35z{?ZIeW?;k2>_ife>k2FE;)Wa^t<t2k3ssE9>pI@VGVA+qmB$o+*CNFn)es4#U?pLURQ^e?DsPu3v7-&1tp%Gw}b6 z3s>Pv7F}ymJJ%KX!TT7KQWy6oWKox^e>;`~!QN)^vtySxpMvxL|H5#QVD3*BDDpUn zNsqq1QrDIK%9CAy2g~vBKjhjI$3a`^{)afMLNM9W|GQrw0bAj|3TW}q%o>dKMcg=S z&&Ib$IMZYCE*FOu-oM=*edZ7xQ(b*`zt3N-pQilGKIc1(^-eZcI5-~bKeGA5`w@2Y za%lPZ(zA9a>w0&(G?9p28zc=HLIq;$(G&+n6w+H{9KCX#@nmVR5CpifVwQR(7b+ zI;5Kf>YwG8yZI%G!1@LVI?>t+6*>e8y@rA%53}8r_50(u-{V3z@q|% znd$|aiv7tSy?(>GzjeP0Wqmj{BVWaop#)ptb@JPKE|djfQz>yL7tJHp*rHJ#RV8fA z4q@FwK^u9#5XOm2?cmUIt*e1TmQ- z3+Bi70N&LAGb>ACmUzm=-kG1jyr-SV^wvi4yrywo>_bBf^Ut3h>yX;{Eu4nbne6P* z{ASBG9>)oPA0?|t=kC>VwZ}NPN>Tvk0^~7=rCYj7Oob%)B?QDvxavIZ*j_; zCLSkmXTtb%J}c3tne5}7;%=g2I3hpLnVa+m ztu7^bF~+K19hGk^JrvPlIa3{SIT@>wAv-5myC3jQWI@QvXtqvh2u`)Cum8Ie8+SUQ zn!@c{8SucrpM)MerI|Px!#@p;YasncGC0yfqU8^k@atM->5QegCA(kDGR(H{HGO4d zJQ{f$UNIt8G)OWs$Lk6r{ldl9zP+JVHhkh|xWCvxY<;myMaah@@rsEp#N2O{2yuC} zib;!$VJwE+83(n2FhkW~&mL{t$VbOAZxu;r-ZMe>xb^H_Y;({sVik7tklOoC8B8LCauE#DVR|2cJkUj{1J(k zQMIs;WhY;JM6D&ha`Nk4oS1s*cwqg+y}?GA)YMS=lPm4g+~jZpVzSv>JwtWN4_N$_ z3=seZkG8Z*YV8T?-4zH_m}N|b=vmxZshKkCF7p+ydwVZg6Bj?fVKxG?hx4Ki{n4FG zz7!a4ot`NTk*Udde#1iV-WK>JO$}kvvHRWIvL2j1rqpf0;jHR>&RE|8#mf3{JdnPhK z*T(-O&i4rIc`wBh&a3FBfre^)yy;fUAw{P2EQ)=n6p_}6T9 zJdL?x5p7~j8D|@GIF}YL37eR-N3H!_3RQy-X+=z2`q|1vnx2D50 zhXgr47a(~aiC(i5(`TK^#A@1N9Fl#cAz*IL;2s;HD$JH=;|)*{OPcuPi+r&Zd9;5V zBkCW{_)Lk4Iu&b|+jW(#dm;8Tx8%Dvz&er)_h=pnaW0ZFZA+TbljT&dbY$a@%gE0( zH@ar0u{MzNUE(~mb*`V>{CBqDH+fszL!E6T^|*JX((6OHQbZu0rsI}Xvb-6jgDQkb zBbW$Wux@~w(%GIpqG2^j3$ESMr0P|0IArRrilk1@8rQ#OJA~6z6GqUXt%qUhXQ;;7 zJq~wF%}{EqLV@aWor0BLR!;`Xpv9;NNA;EFy#`-u5k1pKlp6IM%H}z?j4|+yoPu6 zbJ0`aIgIMMEGS!`G}4xmp$N@aoVnb~;cxrDgHdU_jNmsJF}W&Gn4F)(AzO_%uu9th zrepG4HS!vvW*w6MB>>B>P0Rsr5uR(bur$y7m982#zZYj{=C7r#qfbVhWBCSM*``;i zg|PP>ZoC)MVIMWhglU3`@LJX8_3QQv=K$8Z)wqCCx2&F$(_8Ie)f#pY7O8H>wiB&V zw)g)m^ViK^0ek)0x^p&Eteqj?&E!`GipYw zDVG#wb_F3RN?i=QbR+P%>bz&8Gr_)V+`L+@QP+HP2JxMUWf+K|k*4ycVC|i41!t9Z z%v#h|F>&sZ86BrR7}WNR6St#FV9%|Anlv3fdjCKwsTY|qI#lPS%08C z5#AJ-IM$p&nNTp85Qt;}17vyJs}5|5DbNOk&l|Tlj&xB#Qva_C<=&gkdG>?y636rp z^WPtEx_HcK?owZLrEIy-?f5hegG&i=3BC+yj#KBw2G4Mhm>MHx7h%L2Pyu$a?M{L> zpAVbL5467`Nvbn9K!@2`YDG64I;dv5b~gJ&+2Ps`SNvp1c59#=Cw=K>amc^3MY1Cg zPitG&9SL#^bih;AEg*6Yxf$_&Hf9z$ljsyuni_l=6UsvnIPDg=R-O)&*`Ww z3>KwJoFM$Y&su1^CK}1M36hQ1zpJn1qSu_{5z?*}9WW#{V*#-Q|;_JAm2h zP}tOxLTdvMASL8JcxOl9GB1+#IurE{oEsWYk%>Q&{>8g6%)JMT>*vnAHLFzmG_W+& zTThIZ{pa>F(k#MadxS)1l`9E_c9o^zybJ+SmX!@0p9kj~x;YGzWKs|Yz4^0EhY9%E zIcT3GC<|${Sg!F-y<1TAD>bZNC-K^a9(p1z`w;OY34_Prw zk+YZ@+V>OLr?#&I*%`9D3(QvM!U?Z3{a4%sx)p~UgPrfVQHY5F4`%%-vu4sdUa=U! zH7J%|MHv$zXe|oNt7{sW%16)ZzWIW~oP$Juo=>k&wxaYefqq*i>#)i{Oy?APuA;pR zng^3G&T$wjg$VK&G7oR-NW7Q+CADn>-*~N>aME0yQ_ZP)&JC#cw8ulr&1r-bp+M6 z5gi({#<_wUbdT+9V(6V;rtS*^I!4|!KTlt8pXh(>0%u@LyR!%=&D+@rbwGrIL!T}~ zm-&@!;Z<9AF+)*?HhSFgD-s+{?etOY|FO1iqQy++CjlzqS)WAZ1n~LvS^qIJAQM`i zJ#qhIbI?jP{9lcZQk;(YRXP*$u&as>53QiJ3%TK-?`CZOtZ6vR2xB)ViiY;C9l;`yTus`0OuZHAXaaKJE0`X}o6`SH}Ak{-~r2PnuT& z4Lxx#7v~J8|6_2@1b5Gpq{%36?QAPpM#{tW;3eG6u`d0uN)_=+NV~($+kAX0O4~nNTA%7gbX7B^>M8AY;VqbWljlT;-h@X?NTI+ zAt9&r#;oWw!3@V&j{w+rMS?*6mLsuShlv1O8l_HiPQ>-8Pa{O_Eeb36H?h&W3w||F zibVAZ2y`LYD)%T>fv&Bxs(|CI}ik?Ru#{#P(8 zbt^X0hjEc+5HDn*2xxr!J#b^K5yu&(unq;C5Wp0EC5Kb+x8EzFvPI>I*Fth{{_wSW zTg<Uu&^?0+3z-5#RNC<i8Ajw?Bx&_soJoOqGFhCZD|C<{8EJE-q%??1E$E z3(RlV5m{hfAHu@s_Fld8A}Y~Rf3Cx=H9q&7rBSXp6Qq?g{G=?R{d{$@`yRy-0nWy- z*jOIAaNKf61}Xq9#?BZW@fx0SKl}ob=W34*Y;NA@EbUbl>3oYni~aFdS_l3FYYvmh zqM%m$a*>$2EVI)CSEGgCz|VP!$KbYNraqDY}YK&gVRHl7XODB5wjFkB*g&@=0sq?%@5U-z$0Ex+;g#(T8j_kE?fv^$s&@;#C`MJM>aslcr>)YmHepb}t3>kw<0E*vluD`w}Y zay&^RK(!-{HR5|wCq|HF7XNEl3mE)$&Y$~$5?FzqrHz1HoB76)sHer{wfPJA_FfBUFj#4r&mz& zer<#aH;x_0$b%zlL;z+Fd|C4_EnlC~Q9kAYk}>+09~877(H5xHr>S>cd>&qjJ|Dl> z_d$uPptd|c4XU3*+Hx{|@R{}V40aQFo87iIFP7c)vGVPZvF(6h(d2|ge3lxVCj@wEdC+H<@N zdDDpP$%OBvTX=c#gnXEGf#oq@ZwCA%Bxvd5$R|325~LbM#)eb$Z_h>#On*Q=mUwC-O+-MG?w?UK8w4Tyi#mw1Vmo~kaN|vQf^=+dkdr?Ff(U=~CpNFDN@Fo$&Ntw1` z)K+F@J@5(jJ%HZcj!fXudlgVq>VcSkFdTTU{EG-vwJ|WHfhK&zZn~p(NWViv zzM=Gd?UwREE3m6^b9ZPMWuP$N(e)F*VU`vFBHy33<3C(Ncg9yn%GuLNwq-wcTk(y^ zpNj?39RvqDQV#DRx882v>wAldmB>J*wtPfp_l?zj)wlAy+(@ePi&_-v92#DRxj`&+ zKO9DWH}y{yOoeqDcktd`nsql9HJ{lLH~CB;7ip`QI5@Bwxy6_*o!PPOzJyScjo;D+ z7_4&V44!atoUw+)i*;vlwcVQjmH#Duz_begB~T{cgD6B>=yV6t#USFMn1Aw=&Z7^x z6VVRJQbDhhaw`f$&epkObXD3Yk2-A)@EkhlfYEw>dbvq_=xs5nvW4K-e#teVnA58s zJ5gi1?bAptrV=L?WH!w!%)_2dJw`QLsh#>T^`WWXhv^sqpLkiGz`kbMsjS&*_p5L= zr&BeQFAHiKmLdFZ{GsH4)SB4+WwP<8#YvDcH+aX0pGotHqaaV++IXgG67eN2Kc7d- zX4hxhXWZjM0YGK^3ZTVF2GluHJV(d;q^qxg@#(ABKw!nrXlPtj5gVfxMSskO=ytg3 z-qj9(Kc{_p^u$(g;{=6F*@b1HEsn!kAc3M5!_OXxb~y9q!R3f)^a!nF!ethVH9Zz*BGiUOz;Y2)f2h7pBy+_CN6JZkh@&^~4d!v3 zyYT&uzB8UNvOHh{D{Rj<0^hv}ksRQ`&J~QkA;m51Tc{W{Bvd*4TP4Q4`ufFT6R^|? zjkNQ&WW}k@zAHL9FY>5x{r3S+uWe7HUSrHvG!wgyZ)UtUY*4er>?XMgxYu`7Tris6 z6k2qDSzNyyxIEy;S;Jh4df3B6wF-@NkMD2Q_H>S>*R7&rnA`)lHwMb2o#Iw zN|RK_1hDE(I4bpIw0dzUyjgVe9>Ek9&Gc<&Bx>Rd`x^O4i#128`Pyy4%H20ok2(T! z7Ff5H$58Ia^+NiHiRF%!Vld@A5U;Mb_E&K+di#d_Rjii2{`1omJ}ynH6(H7?UK8*f zD~FMdnC-8Z;rK<9VGhF(Gs;@izTWhXj{xk0V$14v28jWc-@Q$o6eTa$t<5DWlut;@ zoa4zceM*Fy-(k}o^jMS|-3sI7F7%@5kAfR5p@tI!q-~MuM1A)vyXQNbjg)$KC})f% zSU0r;ttcshD^HUyK^f;kua;hgu=ya1zb^~F{ArkVU4MA?M%^>t93-gR*9Ckn*Y0X9}YnqP_!x4er)c6otn8Wq#Ks+?^aJm6uAnrsJ|n zUZvKoXUu7}JHey`T)%WK)aVJuxOD5x+dq*km4tePN2u&-#{ zbuoI*HRdz1Uh?>{!`mRPHM?z{)P(Y$tXsjMb5w>f&FMMken)JsTn})U(H4Hw%}X34 z$-T)L#DWm!9Mq1%GFqdzL$s_rFL8sO_Lr6k7%;P!M5KX*K5@MUV1eqs8ff0F00RHH zu{zF|C-iF$v9Tc#2n|z7&jp!=HgaX?`4%0+cGQ4Y-R*YO z?icFY(QezV*-+6ypYc?AabG_=GX+G)GARth+SlGfSHlWu{KF@8o>T=2K6mXANDBCI z=fZbJE&JzHe0y&Zxb@tJgluNsU${fsI$GBArSJH1jOQjL1jDb2V)Kxk@}*(;k1m@F zX&OJ>J@VX%KadOWQWb_b2^T4_7*d%*Wi69d6E*d1h->4C*piRdrMMn9$} zC*@lsqhG4!Da`k0(OK56-CC_(eEoISa|v1%%6C@C6xfrS)+DNV%e^uC;e}WQ-Bsx; zPQyJ3Cmq>ks+VsY5QT~MK8~MxO*!z^DHSca&q#^Gp6e$w4~?^`2hK)Bx;i!s-`^Az z!l!@7CC}pm$BBXY4%ZX=3gm02MTw(+#>#}Kui4zjbILC)3vW<52nu53{n*)kZ}`R#EiQ-zJjm}eDf*kTS8guI)mSq6ipqKR{otg9y6P0DDQFe~ zKaYUI|7CQgtBS@&-2p`8E+Q+v&&i;wV(U1WjPf>rhsKC-7~fXo7az2wOUq|P}gz4WBup?4Nqha zPVRH$7^w7O9WrY?W7ph<8AAKthj>w! zhi#Zo3(rja)HyKeqgHNivp3y>p-IDR%Q7Z(fuiD2eEjT02PBfa=I*_7Z#Eo~h(Rnu zbD}bkXX|A&Jel%%U6t?nK0kaajTt0hr9`CX?WsB0iw)vk7+?BDjUX* zDr*C+HM^bhTie@d9hJFkl6k8PFFrhXb?}>Q&1XM{`lk+Gf51>AqwbHn5i*-;{1pup zdjDC6U=bK5_xHkVi}1bG%0;r``vaY4YS|KY07d0mS8|m)F2BB!>-z>nh2u4Oe!f}p zIBLT`C4atekehEv4fK-$Kl5)KeSutF-RqeEN~=5}0Uif20^iA|%7mCw{RT_8-qqV3 zyH6h`+fa(9^{>^yyjY>g#Pt2yKR&L%5GF>WuM7LWuCU_bvyM1kWHsaS6f??_P3`q# z>cNVag;CHj77)RoIvRy6iL@IOob0Gujov+QW!ZiI;l=U1!Y}18PgR6_&xZ@knADxj z5Eg@%vO$eRyL_WojNel~BbXCCEcngd1csgBYQ}Cf4+Rtkp0Uwg`E`3)oo!}|=87BG zQ39l(Ci+c*Tvo;kp#6?$`1uhotI1lIE2+avgmz!I_WgPrb(PFDy9Ld+qkK~ch3e%| zdMc)^q*0V75p_rxZDh=cf@z~dKW@P$<&;#a?%t{AffHE%wWN~(sQF*D@ZLsA9v()S z{=%}}qj`w2|L3W21jZ$*m;BYp>g`=9VhDkFY+WJAHcjVjSb%yFfzdWwx;Jsd##o@;2lK=Lh_gBwA1UB^XZ2ZFHm?6-bN8ec+KOM;v7m6!E%M4M z1MG#8<~rDvVdkr_UG3md^ZrNy=SrE{&Iz{I;mSta zKH4>2B;ix*ziXryciBBW{0))_x+&E!%ACc^2^PNPWXv6bQqFs}(HcGot+){rNzBAP z_{^&7Tx6b?Cv?D${{sxmSCfrAeNW1AW3*R?)l z)Kj&Dw>~4vi99Q0XP?PL8kiP{@tPa@)`q^WqUX~p^8&d2c{)*&9ZkuYBSb8f28&$C0^AGA~rkSllFiV|+inE9H?PlbF?m!FmjJG0{O?6Oh<< zQLu)f@ZUcXP-^RR=+WY!J5kaxpLBc5dRFcNhZ;weDW>L;7G#H)S6^&xJ1>F(0cE)#)-Yt-H=}rVyNtN;Rf+b zR{mtCkl6C0+7GK;`vwD=@xD8%s7y$i7XwqhSX<7gsu@JCO(9$k$XAu|n<_WL-B{D8 zQ(M2&>_qbYH`4Z|Ct&_^knQYjGp6*L`!`cuc!rlxK_^@p7Y9pQ+e>UYZ~ciSpI!Kx zaGz$DQk~DC#~KFWdBsj~kFIZ?PTF!J#P~gJRZ&~7mk_vcC{Ul;%yK1`!gA9(qwFX` zxz`_QhoBv&EI6{FMN}y#Rae-&xTzu?0(RMhT&is4iMf25N1o@0DYimGWms#QCA*Ag z2i-T-Il@;V{qM@I$Dp9&2l__;l+KQ7FUV!5D**!_0CPpKVPM~-uJlstTd7}T01=86 zrP~Q*;869>1S}f)!2@vX$D2)oU>3h{w89g?YJPh5sGY&#!X6SX+Vdlb=q`Tc)8WHO zfs!g)>d~VA^J5qx1^`yhBu%4(Zm7)O?bDX2r#r}eil zL9P$i?m64v`>Rj{8}CuqWHV0%vwX)3G=i8|{|EckMqt}F@JR%MCo!UvE;zbK(h1d8 zi@{}PWBu~^ip8it<7D|cEvJ5C3~HVDz(8X+xQ@L(yfXI7d)mw7%EYPc3ARYco)Amt zR!kd4- z(fAD=X@FcI@=3?_6XH4g8+x6GZnnu>c>uj?!?gFE-~An!`SxQBBRyZrrP4I{X(yNP z#r%r!;~We70LQ6{8h48*rnV0wVB9D4SyJ}mG$;OaDuCT6kPX=p!W8R`jq0?u2@*wltRMXgAZ^A%GL)6MNYGle^<$wMcenhwZL%xpq)U#vvD(bnMn& z0XeEn%nFd}DYn$^gWMWocK38e48_(wf$Qbfu8=R0-dYwFhOH{F^`se!7VDbub=Ix@ z8QoUPYARp7+`*9Oj{Ht@&EqAd`Oa$V!`bsW9wN=N-+=ut4X>xNvoi9zrOp=ML!8I(LQ1006 zC=-#kiNd#3*IjKjk0LbKKebwL$@S#;E^TJ`Q*&d%TUFBoweS!#jJT~~O{pDY)p!@q z4|haU%{=kgpx94PGckoSv9->vnjQ@FQ>*I{$H}+Cm){B%V7r8?ciI`uKXtQoThP%x z&mta#jGk;Uo+S2v4*>(mUJaE`X)B3w=DYS?U__d&i?n*%|Is)GP>Xww=zE#Yiyw^n zw?{~WiV{tQB=nr?wjJ1qmk*-B zS`0id#B-sfh^Wl~C`>!;>zxn0@N)QSAGxoUTKR*?iVDddP*Wc&?U41uyb6+$ZPW zd2SppozD-cK{B|x($}gb+&nul=p!^W$GY7Ir7l+?Z+CDXoBHwP4Vzw9_by+;A+Og> zSOW4FhabWE;z)b!w6d7+HI+c_jvhU*&g321j2~H*n!mnWzj0WGj>2JcDQ|^p}5- z(1J18#sPzS8}VvR{asnn&;1t@xM~HF$rt-#urY0nsO5kqn!U?BLbf+zDqOqmLz73t zz*y3SW`ewMk-~3;cNk;dSN6En*Q@0#&1(E+x!w{f=UA7vf@!H`yPKPv>KixUuq`a{ zE^;&q@NH&DM7+!GCtd`C*zPZclizaO~j(f9jYk^bD%x z3E=6D=3SQCI~BQf05yCmSPHlpBK`ZBfFR+Z&~<;UaX68!qx98(Kf%A{ZAC}^H+|yy zd@e2Z-<1FA-QAmi(&yM@zkiAol*q_;{8KzqgN_04PjRWzghaM~%m4ZGFGYd`oBaQq z%?G65ymba{PbX}Y(*Bp|-5+_X*=N}av-_rKgn0{*3d|JDlHLEuoz9a_ViOe=O{Z8Z ze?|CT3LxyVlG{g6{U`4fMgS5L68Z`6x6a5c4TXP3l!AW2ys_XD<|R!1`1GVn>w-ZO zKd@QyMI1hNF)3%SA1%}+Cnpyd{= z`@KTPUQp}5`Ts4B{l6~|r`8opflQ^26=gNG1e=4jmY}Ccbj~d68FuWDF4YZw*0ikA z9$EgJw0Qlcd(yG>U0LY*>o;_-Ot@cZ(89+U40)m<$8<@$XR9XTkuCvOa6mzBo&;T-JcuJhI zXHKS^;m&`td{IXGLjd=#F4KT(yxHXi-F-`8DL!1PCCQK{=I9hzM_^C5`qF8IS?63a z1zNrfLnWcSG6+~G*$JeG{D3)Z^m0z zE%Vp#<}}sY0xfD>f)24!B@w*bOzG2hZBKu8y2}vLrk$4geMhq}3Q(&5@SmUb?DuJu%pXvr-Iy%C5D?}=TcWf-ZVd8@!TwWj*vWm*>+FC-L1_p`?LxtP9 z+f@F#H{Ra4Ij6w;%U_{o&9S}WBZWy`d2%mb7S`VF@y$QIn&ikwBhs7gPq{^k+8rMe zc|3PeIopnqb*mRtz45S|GtU`1TrlHw}D7 z?;O^oIUnPL$;Q6o*_&(^YWG=z*e@|Kw1H0TJ5*GPJfMr%-VhEJ5-j~jzmHSnIWc}7 zQlKeDj*=0g@@U^wjQ;raRt5^!5!sCE>@dxV2`kssC@mH6Fh$5Oj)cAz@=e|905)A# ztjQH>i^ZANNZueWGh|n*j`8~~yX<~;y%oXavxyan+0kR+hDHRhkN~JB=iwUGUT$kD zh%2pDRDgn|=8{G%YJ*DR^_BZQj)DAO$b_j83>WmYwpoQItu=Bo+hC5%Z7SBBrf4gi zkhb;W$ea;M|DhFuDJZn*i-KK#MtS~(TrJPfr4Jw2g}4^3PhQ&fGxn}c1>@Js-PT4UViYD#h(LhwV46!rM(KuI)_6bR4*}ln6#9{IW~ghJw&FG?_+=oY8Kb3sgE)$>snUE%9(e| zZk0s_01q|gF(&|KcnB2;U~MNke^DiM^Lp_FyF_3u`P%I!b!C%68n(ye#ZUh&yu-nT zN=P^Ho@C?}t8CMO!4qbNJuWDDlkVwN@R91;?}qG#N>JM1l7ay zGL@MMJgb{p#x*a8!Zf?N4igctjc^lUhee)+tn^!GFte@7!QGpbE!HZ~$@Fy1f}NDC zemnRh&*LYBhd6RJ#ezE=zSBGKv-C=fySH<{n~bPXuqI8w=>BvOV)4eILO=j@PdJXn zrRzkECDe*PN4e1v7LJV8VKzIuub9gn6r06+=I(J) z{Yx7A$>Bubgglnf$^IcygeuqSvBJchp}i||mnf2gF9R`wmk{8aHs&#S@AN)NbeR79 zjKS~J0Lz7M)bu_=SDf|GN_ksSpJj7RxBJ&w&HUWNZp8-=2sQ8{?9=OVzPi-7f;@qg zCK=_;9v0@jRo(8}X2n$sp=fu|Dv>==wG|X~6i;M=XoZC|F_4R^=tc#L6qEWTjoLp-FF}eK@&LiJZ_-eO457F-%7R(Hi63 z>QecU^=hwzF_EcR#drVHZ;lP6F4PDlR(d}x0AMFh^eYlJXYxuJ56k&{P)rAZhNu?$LrkH!jO6=O+ zn(puK$-63ez=rJ_h$%5RXa@|&%yK($N$T2 zYtFu$tFHFTG3extb6HiyV6`lnQI%*TQ8d|wA*Y&qUfXt+aciYOOG4DUDy?iUId9D- zs-bv@w#EI8JVh*8qoc~}ckiO$uWK8ylKT7H6nZ$F-g(!@SjSC%c@ZBWhPS68Cx_uB zJcCuE+T=iG2Li301yL;3BgUiE3E<~VMvAN{ySQ*dA*LK;*kstKg-RKrL*@`;8OFJ% zW6#hqUWE9F;=xb2)PgUl@n}h~Xh{@M4%O}h%^gv%tQ9oOg|TL-LvO+CuNxW2sWPis zK17!jQMY(fjfgG2!id5guse~z9A*2YJcgu_eTf1{8qdaz_JwTREl_yvM1j|dcKb%yx!*tX5&L`vuiBaA}R;X^iE zio6Rb#*GV|Q=yF4$n$piJGGm=!F`|4Ac`hqk=$8bb_{+e9<{Z0{1tPY!~~zrv6wd) z9fKKK5k{3V2^@Gs8ZfLE&;}cW`Yd{IL-qCX9fF)}iE4>(jk+G0tI}N4e6z2UUe1r{ zlpF3(pD%yQ;y_}|BdR&bQ>`n#4U z?#YcxomF}H*B5)br*?E46y0dJma2YOUwp-=W@9WxwpDbHqiT7(u;)K0MaE&%^PTzq zWTT!->b)Q1{o*W!=UGQM4%mCX#^8>m=WVwl>n~estCgQ-mzee+M_#e&p>|GYYbblgtZoCJ!uv9o>Tgv90|)G=#qlGi<(8+L^r|_Vp88KnpbnWQvq6E zgFRFPc}onf;|o>lc#%#eMo%wb+R5KR1|7;jx~y>#JT+(C)71))$g8F~c7C8dai}hsvaq^Q$?3%THS}X~-?WfQAO}Ejs$0 z6F+*f=HcTF&(qTcTWew97}n;)y(;7#sJ1ri!wV$pJU%f;+@^KJ7!oeyfny{q52UJb z19{UOIZT6HTohJeojop>7g6e~7Gu$QclhWrkH~pOnIG4Rqa7J8=i8Z%8j0~-r)3dX z4Zbpsgby504G@xt*CYy_EH&{_8_QcG6{?)9^O0r#dlq0Ro!9*w_PB6qS(b7-nv-8( zF%5ZaDoD}z-W@*inKkCfWrI7yWaqU<7Eab!7(*=v$>vAgMZ0+eCR%n>1q)E_g*OSE z%qB>%Ep>?WBFv&0(+P1rL|<2*x9YPo+lq@!%q}5qf&K1zX)$Scb)$W-|H;QhPfK4{ z)>JacCY@TxU+b_`2DYM$&{L_$4_@eKnGS=xTwbX)sM2wC^8%O)(QEZwp>*+jJ zH8M%`f>7>IHz;mGa|Km1U|u><2)fn!wcYVI+jFcdEs436E7Gtqa0jjBn^i}%fmPd~ zRe4Cwmfs5(o53szOzCdW%D>>okhHs zVstZKhI(0PIA8q;C-``$LCiXI0Hi>Lv8mdnC@Q3KFu8M5Qe#6G6=)Xr;S|G_j#AyL?}({IjTM{It}Z@BXdZp1QMWifrz z4HQTG^Jl{8LgnWSZXkPkLXe%D0^Nt|QIkG&vaHDu9da4H@D~e}yIHCWu;sH>x#10HQa*u{Av7KzGQPiK3uc5tmuEs4;-)D zcF}$l_`Q_H(8^ueWR+xr9H2OAR_o=99`O|B323{JsO|gmd=^D259Iso5@uc6CUWF? zy&X?0vcKJwqcS0KQSj`f)zFd)Ulw;#ro6p{sfELesu3;C?6DcB4QBdHF+(95E?8_G z(0+A@z*=6nx(jCO5=!5G+v8Z4pZ~Ercne!sI+t^(M14!p5MLvUJp@gKmJFu>1JQ0= zonL{~VEH=Pi&ZYb+kiFOVq{kvFB-@fvh_|-u9AX%mZ_~*lGV6G6OBJ?Wt;x#D8MDF zaqHTN9li$`$S`wrTa_M=Vg2=uOUHZ4eUh_1(3jPGj%EX92Cp4-x#&UM)&6~w#) zQ$=w*EYCk6t2t!1wF$rCv5Oo)UIH(4WpJTniyXF`nteV_aiqqpx0y_YuK9&*#)NQL zQI2|BQws{yw`&Wq1f^ru4!uWE@Y;S7&R($2w_F&MSEj>a*Jnvbr)S5nwn((8OG?QOhh~;* zkfn-OTha6yt#{|VI<410HjldI&}m6Q8V!wLM}*E4t0Q5Z6&~dk(ZET08p{xrwGyJE@#o5u}o9; zP$@6Ch_-0n`Cwl9!hPFuy9|kx)&!585n|~hjkB}5_&pFJ=C@t>V`>5h=$u<9;kV^3 zkkyfth?!ib;QF?&R6)k}UL6r&!H_U^<2@5>A3wT8`LJ5QTAKX?d3cN`q2>AEVUaR7|3A~+nLZ?#AMK{%uokzMuf89 z3=XcvxAW&v-TkgKQc$$It{d3@IYlJSYPeQ9+q1RVzJdkRgU6{Eq`R^~bVtXl9P?~+ z%jVuTvZe6}6YJ!{IWX`!*QTOLU{>ldcr>aS32bDbKiyK7^gb{78AHy83!tHSKHo(& zSeEN^x%#7EmIjA(rz7jq_5FH*`=NE_ca zGV`~#>TQ4b&acH%iQ%H$D>E8wD*+pD=#fWa6A_8MN>JgnHRg&Lay*?{Y!T)^XVa+rFmcH0bdnCv;%#13ydAf`( zFJ}33SAt8Tr-RIP&q&`W@}cLo@2*zkk#OXSUEix?C5H5gD~B5RONdhGEIKg=?ih6% z33%3~T`7%8`5`FjEF8UhV_!1{QRo3ci)cI$Ji@0Ps)7m)*_rb zN~g;rutpu#GOcjuAf}{$_YS8!Ga1BFd~mLz$F}pyR6q3NwQ{51hN01q29Xcl3Wp4% z=qPCIc=vxCQ~&B`>*WYu3y&RTQVf94I$y-tPP{S#vn!>mC>YtB7Ov)9SIurKE4GYgrne!vY{_2ynF2ty z{Ra$d?%*Z|pJe6yT27HJA;S!n2Rz5k0S zz%d0WjMWJ6Hv^?Cf_FtzThS4mDp{$3Ho#FMxBmX@Qt5dVT<98-e#Ks}^O{<;E5C%b zK~S0oGkB!_zi`{#RW8HucJhy0Y?mvz)14Ar8W3@%U%puiuNV2~^B>=Oe9G1JE6~Ap z{`DK;-(3ITYwyKUW|1|v58z9Q034#SnK|7qpO#Zfl54oc@|@kzm%#zlS2VQ$-TA#l zHZnYV`K7pzJPivF0$~QV5qe?-h3l1~gQN<;uib+~1e_uK%0&wBsyo7mTfI@6GZ?rs zOL|NC@Y+%w>wo%`4SzYH`f^Y=`EY&uBkX`TAKm`9w=^mY@y8yZWpirL?)^5uWoc z2O;!ND*+{$-0#Ugceroa6c zy`8*CS@m#h!9U3Qk9X&<;(#d4a2$8T9_vc6KpF)@X@v1jXMaTwPlMYTkC*efCF#`7 z%>T6czXi8$d48~j2z+V}M4 zrR$oZr)mtE3Pv+#PP2kR^Mv3T^Z8Hhw93%gioOn!R=Kr%9)dvZf`A^?e&GkoJ_k}S z;dvKVbjOz^AL#xWCKz*${?29D`IYOF&n+pabK+~2BhI6rA4aKR#H(l2SA4}PX0C>X zq8Hd1t)9PU2717}j%I&+PfRLlx=sgZ21scdq4VR<_1ymhLT_nrW(r>v{K!v#11Q#I z#upZy;l7=2l&1JLLE_8^!yk{wq=67HDklx+WQZ1d-GvraRxC_EmIS~&ZI zutG(r-s1azdU#&uZ12;NJy`JLHJ*C={q0NN<_*tDH|RMUE~V(`;YnK*xGNNTYT`k2 zo11M0q7-z_HXBFsvq^TkY}HVnS5`HEbWIqqACVU|{s%pSi~G^WAZbe>2Z5-A!P4dr zW^Yk=M0tgiNX0%$EVs_;tROUI)Xha@!HJaAV+`&^5BF1oIRRan* zIG>;Ae~_c50cT&jNKy(J?UpdfoCgfE<^9}s$nGhYe)lzt3M;(EIO2g+?YOO8>Uu|$ zw?4G$**Q6$3c<>I`^jJm;T+4$V&kTsx(+zVvF3b|iPTo-RO__bG(&a$!DLr-v+lOk z&B%IaTQ6Q^bH`iu?p8-mn!$8#szjK}l;z~)zT}WS>_&FNBoAs+=v)njyT>jy z6*Jqu^@#nN5jeEuwB1ag(T=m7O8M44uUOEThY z^fpbmv8+CE1%t&4B1g@3;ca8`M!S||PVfwA-J=yBe!#)ai#RE~V;pw?kAD^V>GjGP zT^ca0ztK=Y4CYf?u^aZY%JsN9Kxy`gbTFsmo16;v<`{FVBP9k4nNLGa_F&Zj&;DIv zT3USFAZZ%&S4g6%pWlzdp-9IlybjU6W=yR4rzZ#DYvFU{-J~{OJgXzC(=O8J0#?MU zL$!5bg@h$J9QpSX?ewfBJ4?K|$G$?7g{33r$Nbr8J4+bx_Z+q)V}|Mg##PF(iA}vj z*e~IaFF-d5-**a2{y05{@-omyAOwb7|FGG9ZNIXce-oD{?Ihh3&9ABxLW1~pX^2(~ zIriPoMIhz2nJYg3N)aNY00{7LX;hRk+IWKzsyb7W9n)h0C- z<)X`X{b!HF<^)>qjxW7*NAeuMZAZ<0!k!5a*uQ#&0Di=7pf&y}d?FJF(=7 zv{p^e{R`-FSaZnY#ZmS{)GY2h_?&EIe+#J_dm-C9eHlT+?VNAFp0tv)|9i)Z+FZwn z-9^T#E#it<=SIC%htrb)0OVC2G5)b(-$U-I&@+PfplfBsdFyrO@GW@eUOE82%y$`g zY8nJr#p6BmJe3fuMx)ZwHjAjXDpP$sb?V>FmyzSx5p);uIeHQB{Ct}=Hox5GW=9b{ zFc*Q47uC`*M$BY1i}Q2oV#x`S?fECsP*tEbAA*5-RmNZ^%Hg1!jPuBCTE=1-?R6Dk z8DjJoF7EYY5VP?qpf*aZYBmn2ngQ;(f0rbYY~ho$ATQ z!Gl}NJ7KaD%tLP>$Pq3*z3~%6k=#jy(M8UHGsk;`XsDhd>PR#@L*0>Sc+31LQaMHe z+V}gIyba8lO$w&$Oug8){uId-a>b^!+u-gy=#hbLyO1*=tMo&qJ}L&KX&NR%Bq?Sz zB&oKVE8a9dx?TwI5ievGJhZ|eaYwFY>e84*eesgWx2Hc$LC3Q<7F-}8QZaDbI~$^n+Jq{LN|9D+ zyh(tDFP=w@>|Jf80U`pREs-eFcTHvdFe9I*{G><^3EId0R@x@)-I`kGbVqH68u$jHw+U>Hk1QIP9I456RzN1> zY@i`P*tdADa(kl8uhj3ZWs6tcLJiKG^gVr&I`YY}upTfz)4)WFfpqb1$M3P?8bg%7 zCMjp*1Q%m)cL*f&RdP*tBIpD+S4?uG0$C8{`Hi+DxnP)Du>2%R_ot{jFQFcuj&a~U z%)yu~S7VIQdVayt#!I{;;<;|rLICqZZY6qbHXA1D-Liu`ZBq=sz3dUk=Xjzv zQ5A~5kzbsW5R?-5B;bOb1rKq5;-K6=OOLy|wfJ^`;+O?1GmGWX&s1l@l6=od}- zG8Oz$xCK!=>2=bXY$3EYNeYEK`0HQAu|#q)z=3KHR7&eZ-FiE&2rNWNWXQ31y4b+W z8i#wuDM$YxrX}yg`F2~5O;Ow4LEm$4*LT8#?g#|lt#<^apYHQ)8 zIY55jK6w9rC0UJF-8)%1iP^YFDUM(9u%aR*goXN=pOJ(aXgY#6IIwX7qf1nQuY89I zvIk6o7P{R8n23)QldiveE^A3@?KddwM0Psa1K&r$1vBY^itG6F)q;X+knKIyP42_~ z0yo04z>&@~A~1goS$cgfk~M_7kQB&ednbCnB1^_66hdnzZ&t#|VSL|D{C^bpR#A04 z(c31HpuvK>TW}73kOT-8+@0X=?(XjH0YY%MgUi9)-TmOM!*9O%e`{u~xt^)3uA5%n z)w_D{+O_L_7+31iGGPNKmL2aYVL`Zm9lmNbdU3Wnbp7_r`%ChrwFDjmy4rP0LXd4$ zIO-V+pO<&hY<$gCJieAKoJb3mON0oGe=YXV@5>vT58GolP-I!q3T(wwU}Gr1%GPKz z*5xu9vcl)5k5*5!*ck4fh&Fk$=iiK*tatw_HlxI)+`zHIAH-Ql*-&YqZalq?Km3On zj$mHURiOmmb;2zvGdeZ>;&1_UHxT+K3wjm0?~7XkxQ)9HxMbhqVM@-RBnyjEwY`5MCrC#9k5nMm`@2OEa(#{A(Mq|r{U#}v=*+|4NRNsAGtK^Kxr9ObPY6BB z&w?bUUw@1bBaV(7fk*Z2i`c>fb&zRmr`eOzoA$3``^%QHci?n<9dqM@&yQ9{z(@Gnrit*4SD(nO&e#%e1Q( zf_WmJ5qFzrfAeBIaOYi@gekYGE_ z*A^2#z(UDapOa4^>lO!fEy(uzcQk;kOgHOk_UUb!i{)9f(~|?%J&2mdiaq(-owVt$ zr5@(vBm-!JJlUO+=5lpq%8TRF)|}#3C(bjdDZxKRUVhi(lesHpFsU$1IZ0TITwGptY5u2A5(>S(76OUZ_T! z8Rw$_n5`l5GisH|yd~+h(=eqdw7a=@7y%&i2hKS}$TE@-pf8IiY!way`T|8Cp>6Ie zJu;(1-lEbm#H%TTjR82%1Ci54*w^DV;YzZXd&)#geP}}m3o(eE0$UWJa=?j`(XA|Ycm3+B%|44Hb z${~W;tr>+(wi|)3T{;rF`WU1v&pB!_t@clu&DZ00CXMVrdNup7a}0Lkzm`XnK~{vb z;+@UiFO5lBQ3)jm4kyChTsQ=n%t3N+=4biRX@=BY+1)}owthYnfF>Q*v#i_E%vWeV z4&onbU*hUwL2Y0*2lP`-jucllI5vkKdAMYnv9F%ssXD7(=Mru03B)SEntu~~h&5(p z3TggWUE&coG%ul^>H0KFxJRRR3*_+k%77z)f3($@FmHise*iwQX@`l1W#fArK5ST| zZC7iMH_viEhF|7vAMB_8H8DE992VK*W9-w$Rq(8-Awspa!Tb$2RXw5j#KZhUd*kcN z+ty;LsD~&K+NVIEF}FBD{}dL_M(@1p&>a`i--l;r6^=5glYGY(QB^1mYt3BSNcW@6 zMUQd}>+X9Ouz}fg6;pv%gQu zUp23;vA+{t*TL@^WD_yB7}Q_8NiW~n?X{uqiXRM^`lW8+7J=CN zgb|n86>yoGxY))iFw!TsxA1BJ|KN7@?3L||&P{sN)9efLo0v5{)RteppOw;dS{3lV zW}U@`XV*qvyJ44eb|@2^#p@W7=BaCHaQ|zDGg;0xYhqn}INWB&{Uqhd&I_3($~O^Z z9{p`>d2HOYjZCm%mluZ%(`wK~AqJQ|OE z&!+%(^-opS2#7*x8H(qtMhi!H9y|?Otqj!J%^rx=_p_Fv*i79oqLd@OT1u92&#j@F zSZnhieFB6(pG+|&z7QNngYDuvY(5jZ9!wVeI>c^T2+NWIbzV_f4}iL|G2-f+*{&Of zgL0t<^>tJrlAWKfD76z!JJbVCzG?&fx-uHpETzvb3F$si7HMX4HSob5 zoRW_&ETOR^1zq-Vp=D`GpJnIEb$id=&+?o5aOF_mBXA93D5ecyMn%Q+KA?rn1eFqJ z1e_UfuwvJ4;(*24!SYjoy7;E+ML(DNQ_e7(_|4(bZ`Y?ujDiHf_}EioH=DO!mDPEy zF`ZtU663NJ{KamVMrn4(Y%1%Oz(^iAL2kdZHBu9^s>nJ#X57WPySS7+s;m5dugl!v z8?*^p^VbC|yq)HQ{1{O%iox%!_|dVGVWT+87Ry<{&C&Y5)e13B^)^A|@_TkCd9-c5 z^^w6JQ*rI-C_||mCfKPZ3Gsni7#n1PL0H^z;L1L)f}KL1cek7@??1cTCE}p_;TG}& z05K!{VzL4sf4%cjvKu!(y*Pq3@re%ZFCu}=+>^4j?lTS{&3ih?D>}MJ>O7nxrsKC! ze6hZM3{8(oOA8bVTF4;h5;zB!yaZTEJBs_2`0GYaacZ_B_VqOy#h`Z( zak%*|4;j^)4+#WcVQ*E_CGxjIQ-;&=Oi(9^dC%_2i@S*(=K7&(>pPFE{dryKYV{^Z zcPEjd9mXHQ zyD7enGUfu{+_Hn_JM{_TQ6(2go__!x3eYujw5?p<*4iK5VOwx5MYH4ahe!7u!L@4m zdR!fU9umi8A7o->nhI-I5}$@6+UB5>Q%LIOUy9b}wlFvS#WF6Ch!A}$hwJTGLfR1) zRndi*oT3A}yK75F93eU!^zdD0>Fy00vgvSZhxidKhM=0>ys%t$I+Nk{)3@PVk77=+ zz19i0^4n#)u6z>3z?{j-2?Spbk0jz*jp^?d%n5Dd^Vqz#?Yf8X-$TED-o$&)c;xTh ze+#vO0?V$u*TPSs$y-VuP~>hKu0C+<$i& zv+$G14s}g}1g4h}st+(*Ep$f6;;Ey7f$I z#L<>ovgG%2U+9%ak024oBh^0VK@43dGVO0v!rM^bIX;1=Mr=yl$aGuhbvn z7@t1r9M<7t5d@LL+Tm^w0wssF3?&%>Xv@25%}CJDxCu^gZ*W9s1(vlUS-G|Fwnf^WtD9zXY8ZqC@uSH8!6&Rry&ka3mm$&8AnmY(*+PJ)Q?dMl^3+T4=PUBr4w#MOU zq|JmrCn;P?V_jBhv4K_}R}sw*q!2-eJ^2w}gq zLg3;48&^<|md^h)!|rJIU7Gw;vL&f;YJ8m`HUqb@2kcK*LEf{$X)77VON`vakE5Y3 zDQkZ|_j^?7Cc979Q+fj#)O?5ZLtS+r8!66{2`LUI2XfsJpWiy$J<%fnEt3kpiQU7Q z5LLpmQ_#8jf5lXsoh&4|ebm=CFE%!(ttg)zcAN%T?hlO)^Ak6W)=qasm@B|z{(3eq z5`Xz;_BTI(o=z9)$M3Fme}UoqSp3h}=qSU-*B&If;Fi<&h4O|)OUQ(-rFFghVm7|$ zC@TwDXp}UuzN;s+N&z`%iYvtX=yFmBcW--GwP+DDy6*%&-sPwDZ>Cyu;9wA;tV3*H%~R~K=wdWvSWz@ zbb%gGbo<235uF4LDkCrY~ri zGnH1yB4Y%is}s9yE9&oK2aGT5@MDWHZKNJi9r{n2EV~u`1bj-#cRF*xCxa9i#>1(T zGM_&p4VnyE8{aG|>HP}ky%7KWQ;TlyuxM}8D+|6tv=SD3@oG#h`V{k6xHIk2wROH$2LS>*QETssQxhmmX3O1Ho5z35FE6dDfKf8; zy5F!wY3?8xT{QDHHvJteSe7F4v^@7moPS_cb%7Mtpuy}gN@e&oqI9&MXyt|XM9Cq0 z&nUkODVvxWP+%zhXmHI{dmck=3dWFTL*ugnt-pTRxygJmc~$HfZrsM74-5_2kn=QL zJ1Ofg@Yj_y9*)8j$SzDeQOpf`UqTES0_{X5&Lb zE1R=hA|mAII|1aqyJov1S|9rPa{@C~sMq^`EwpB&%2*n_&jGQqK#G|AzgM@?R|eQ z_I<1%`n7rD5UFxX`oRfqjTv5Wl@(^F;Z=~EQVfl()6c65c5s3vj|eTw$Hs0Mg|2WG zU*{V=eW{2CT(#Tx+cJ|Q$}@^XA>d-oij;jHx@M^|oZgL?Q_fV>UlWD#?J;@x+j8+L zKa&Q(>$i}WE_Y#Nfho_=la4oxBnXuv&$cxV=J?8G8X{F{%-o^L3)Te7C_>r;>5I$L ziQecx%N$@D(`qb~NhPXRPHvuWDh%-WXhQO_65sZKjNx+RR$lTlGWet{6Nb0(?5jS4 z<1Wm0yGEb8f63bIl3-dpR=7FeY@;?{=tAp8ou*Cr!Kxl^4)`l!*Q40?whynb`eTMN zW+7abbUKcu`mG4G7|v-Zj{i9D<1@)FA<0(t``MNhtvafToqa|_J9s>6dA;OtB+uuh zAIs{heWTJV#Cc%UZNc~H^NKo2?yrL(%p*(1{NKpe`$o!ec!hxv4q%^Y7}Rb2R~WSc z9QMcKUMj|WIU^HO%DAx%jbALTUD9l>TPB~{7_>8Ed=s?Cqifhw*1_cS`poi^i}sc-$8Blt7H;pa~ADH;gmHb0K^ znD<5k)QhFh0ww!vD%U718$P!9Q)vg%Ow)j%r}cU@b<&W~TWCfj+qbab5kcC+Ik1I> zq4duZwHnQ`)ufq-A+|sTTTwZhl;JaKoJedLQ&F_e8RUJ+4<#%N>yMzZ?ic3ZN>@_2 zIJ4e!kV|YCNcJ+kz%_}1l$8I2GHkIlp1D3D_;Vlr{K}vzJvGzl z7-X2DkciMK-h5?0uhCq0yxocahls;)K81bEom-)9ajMqzN@^c4>UB7LrF#2K)iIgR zfXmT*GmhH1!H{5SH{6Nq-#8~ku&kVNN&w-VJ$$zw$^j4Zeg5{LpaK&@WphiX$!fqK#CS6k($!uB^bljhPG@KJJy7xc`GC|ls4N#+>lPFtGKF`Zcym^%P$tz+nrbKcmIFH z%~g72M&-K|v5j#rYf|eGa-k-!jvK_AxOia<8dlo3DDaI{yRTL3CviENB;-Z5;!Tr@ zUUmJ_%A+b%uu4D*Cw!|l2!A$C$z^}-`$gj3atMHDQPgGF2OQqS6I}7koP87!hB_m$ z=5ch{^!ZfxnLIOAg;ZcjR+3yCw%*FcGz}3I8DK8!*%(y+~ zpS*mKpq#RP)!;3rW*}unUu)pajWzGyA;;yt@J5~K5Ii3=9C7D5ZJ23nVNjm{cn(Rv zxlf{MQ{8WBtOJWED`gBb;Qq#`^!R2Qt7fV6Kr8on*^-S&#|It7RCCTLpw*vRpN=Cs z$a1zKHtg03Qhc2mvZgxV8F^a7NvV>`pO5eQ@K66}p~bN2XuK_vcr#ud$a)#Bm^F=Q zYb4{gt$O8~T{n60fMit7%3%=@JOv^$UxH)WxW4p4$dlP9r0(=aZ2#`W3Ca z{-`(sC04|04c%alEH_Nn{{^Z?LBiPgYMp<5Oh^$_V56}?R2bER+Hm&2&UPOx`!^7- zK6#A05UrA{BU07^yoMK>_19m%PO=<)zCk+XdmOe0zS$76b7c(u-u>!}F}25oKp7tA ze@ttI+W!0XwhKY^yUJj?^eaZi*rXXy%e%@^#P5eP+siw`Yq#rYchKX2GF*dV7~b75 zr$4SdA_{U(!e!^UB@FVB)_W?^!5Gz;)z6u9R(uua3f|OGm81J($|HPz8fw9K!+h5* zyuVtbXU2|?zb*X>%WuU3qMt0_;bNfiu+M~P> z5&42LGZ`u?k_D!)=ypo?IWvKyo^t7>=UVNQ>QWHquFn+XLhh2u_irw0F8Qk8q8^U zR(%QAj@-`N+LJ#eDTl}yQ~7b$e^xk6bAF!@&c*IAtQ&2c#zgj43;I%O`$E>>lsd_8 zv~Lg{X{`HZup}o2$)-nRFr6L(<)$tBX-CtIeu{MCP36XOGM+Ad!|Kpv%^ZbckUD&g zioSyZ9eUcLBdd)X=S+pMafGq50JymW^HXmA#(yD5=}Eu_r2qB!N<=J~w$2;S=qZLTQo^P96 zgV%x+;Vp0p6H&(jvgzay9{$+uR&pZt#mHO^1D=@;Yxz3E&P9LjwT{yBOhR}3 ze3rvUfaTN$n;q#Hs?KP{5nvKuMs&?n=-!yo z7*Y_OMsrWO$dHgWIrm4)FdRhFj4rm}BbEZ1J&u)JmL(5* z*r_EoT6(BqEW1jnnGiMESjpYI7c^LQlH(T$Jbc_l_b~D8`^Wp<&C>>-O?k?w$LgQ> z--w@6MjbU}s2~?#6v1U2|LSXxos>eeaPs9Vso1}jEF*?wGlvag_!hP!X&O-#SCpl5 z9fcVVTP6F12_9n%?0WTFEznl0!g^omL=j;G;@aJ(iPNcHV${)?=LMK9q7R@Vy*gk0 zLTNv4S`cTzA8M~5RDapGnucjNU#E`okrDb00j?l=e`MW5Tn#U;hN862@FJ&Ps(cSx zfI3xeI*Bv~k7sK`P0R;)59AiF1G6mz>AQsq@f(&Mt|+@cH;Y2WT1{25gzRzWyEE-!RzX z<7#88;VH8VWkF%b3xTr_zHHX*g@IK7m){RzgJohxe?@0C^$hXXxf8LgCXC8TXp8@n zh-RQBoSXt)i*F@g@>`TY+Ou1JHtpsepDefCh$mcX1QLOYc3;spMfA6CP@gNNh?^hQ zCyu_l=H$M>XmR;QyJBJ%=6FSz<`4vHQQ|Tur8?@?CFbOGr%lNzIh_${l=$Y3;3w}L zD4lJEh7xA!-qPKVv24c6b?*1um6~x5JvuNWGl`@D`O9Op%uaQA{E5fK;Wr4T)dlIp zH-{1{y)~ftK-#hvWzCnvcGu`!k@XQ{gCEz%V%hLuCC=cZ08eJ zC~XWEFY-AoK4q-QJ_aMJ4|B}ZQtLA;&@r$)>zLRLalez@y|4AoTz8UFQ^yzOmISh8 zbcRUmwQa+2-U`r+KSmjj(~At5e@^QOpfVJNtubi$r&bBkf@ShVJ^=(A5-Q>Um?<~; zKv2Abku=#KEp{X+p2Va!`z4*7Uv*BE{?q$!B4Y~OZP@rBZS@MT%lP~uT=OLbSfZ`P z-Wacm{#~@1UqX|k{J)TJ-@zXl7t897#tgT;CZ(-F#cg|U9*Ox|x#lZAGj8VQiSh1~ ze(&WCt9+c^_p^^gnWwv>{{R&&ZQ(a0shxq&k*J$yPdFCRw4rThfcw59Ysjt1aX`3e znuvzL)M=HJ%7g?TD}noLklscji~LrM184fXA@|2;X-!(>qK&C zwO0r?vz^|sOE%#7u5iLZ$aIToQo$2uIzfwvOA~#!=UC8`YZUMwio!;FnO0h>5smuP zXJCB=ilO3=6948m!N~UekPwIb98`fAza$HOH#32HyEJeZ_=Mlz@1CSiU`W59WHw*D zRPGdZIuJa-m*e(?h=t3o`MGIpZz<6n11o6GpkGy0?B~zV=I{VFms2z5WylR=Cet;= zhm87vkPO?d;>E?tl;UlDrCT zK)@^8e(EtDT=o`G<=+xLH{)aGuzMcA)?cwXDLxx}RTY|{{ z=3Nl)?Pq_6p{DG^Hd>DV-zX7L*#8H3;{Su*ztqhU_*~x!%aM7#YuoW07FZ@+*b+s~86W@kta5*EFg_vVdHmKJ3hz*5Y6u=#{i=%7d~F6)kx~s4%2tdyVq0s+lImn7$(kk+Z&sn84-{7o$$i0 z97F&1(ocFp4@!6ZF?Gf>Bn5mN-!DGHj%nd%y|ZkWFlF*i0TS-shHbBuk|)&%(4xDD zyKD+sJ!uicEj9MvICeaFDMTYYc>;4IDOb})+bY6#+PLDYzmep)XZSN!%6zCvB%-Xm znz={12KlfYVSNlLf#&+So8Y2FNl_t{*wq8vn>?&=@jJ4OhunESX`{^ zsvSKOdtK~O`2x4vtRU& z2tqY$j67Tf0!9V?D3t&E;+}_p?(F>85#t`DJ=&hAbyW`VNPPC@!T>r`$=y+ox<5p{ z@3WqqT%R_zqR-EVHi{$#-x4?8A$^MW3M*n_SvtjFWAHN2iQ`RDx;-9S1RlJ6e5 zn2;yTwD!c_;G(2W8Rd6)yymI%7SE4;SgJSWo3gV> z;`pLGXa#+xinr8qI{XQYd87Pcz;CjeQ6wSa+im3|9Z+TKn-|1rtd@D#%E_Iva+utS z8HOU4mLjLUXp?p60U!31E~y)pzMPvfzHzxT%&mTKiB|^)$Lp(!Aw=!d7t&rrrwlS% zqmJ1cS74mWZCSg#VC4Za!0~%O0eG(IBJWBZD^p2Hpgs%|EZ>5L2HWupZ}+tLRS|6$ zP~ETM+QI%cz0E*~x+_4#{08rp<27^1VyM@vk;{;3nQO5T;{?k$Zb}cIyY?w7IBGP}oMNoJy+#arFx#i_>et zw|ZC?);hRq)tV0%ro`tmXnVUK5%4Y!>yu^XECIPqf^19IW&6=5RG)w$OfaA{*S|gp z<&+t^zPDVf4!)w7)nqKE@8NCzrT(4j?iXS1qkr;A%uN0J4Cv}uxflhl#BKzainuP2 zpnIpVZL_f6=rBn#>d2xCq|L%~PV@#dDU7EPb-2zhz>$SN`8Nau~PiZG)O0p`=%so$l;8>~fl1|8Q!&Bw2@!I-#z7$j;BOy(lk~ zGk7oKf8(~AkF;0HF|h;MDr50$iGJNP-MPWnYUoa?*Ad$JDBR3pT7~|+QQdKaz(nC} zJKmtzL~>AK6?`WEoYF4IyN)%oyC<`udYLLP6l%$qdpY$(E0vle)v zjLqNMIs;f7m=d8q@v=He7qD8^i;`16(L5VoGH1gEwN6P#uu~$OI0GAQ?2+}KqY1Hn>T3BN< zmr3{LLYRMYgm8jF;r44~gj>hs*&<-J*P%0x1^T=vj5#)h@tj@8?=A%55b~Qd{D6{w zn=6&1HTlU}2aq-K96hxsSB`vG<#!1KphsepP`0Duk3N<^tII=mi3%{#KU8`e#MJoQ zn&Y%eAUX&a6}1)9c-(p8hH6fg^{lEb%}DZjeX`8jIVn^@%{5|cB^t>giq7C(;uzc% zgE?$NSMF`Cy|d`pTy7~byEQkYvsn22RZiGrGEU*q<<^U|gQemLS|FUFT_~ zh)GtRmQ5>j3~INT<&vwUzsRUv;=H;Ghlm{^-&T&X4)DukXbpUolJml$Ndp5KF8B$Q z+gK}K2l-4Uq#Qb0{npOJHi==~KyrJ^6m1Z)5tAadoLvZR7y9?-ZZws)^^vh%a=*|f z&*XdTN&KvtoGh8B_TlZW2F&TlQOVfj-E#IOQ8%E?+ew@d{ z#yz-b)3C2(_|5CJU0+Qwm`o|QkCgXed-INvWJTvV3>`(P>{eOXy~f+-0GDy22%X!4 zQ6RI$(cI=rj?=1iIt>SQfUUA@YaU3sTqSUULd@5UHh~sxSNT|w2f&*wRf8wd1YAr9 zwB30%dbg(JW_(EKieozK#5UjYGg?%K9IDY<{K8boGrgCwAcRV2z#?0cy0k z0h>^~1H@N!=I$&ts2Jt8-P+*s06lEq#DQsDr$NgeB*V9D0TD?74|_J3=Hk)j`GJ@% zDwWDe!%KRK3M`(k@WcOtbRChIwI4s14P1UxN|N1O?pAAgaiS*go5Stq5mVQGBjl4C z!WJ20mUk=udleI^Diw~x@3pG%MMmDLKdVmF=h;d&9w4iK;ADbY50Q`tm`coMj9eBV zbN=beEEbec%XaKv!?u((zjf960_T*Jpr*dpX6iuy;i`)1kbYNvwWTlWb1`m(1oNuQ z3(yx;&&G^{%Ld58w{5x>z$=gE%UzK>zib{JfSo%VAp4Ep~p?(-}{rW;%lVQWMynyuTd;<$w ztXD<}Uyt07c#ZDsT!B(nQ;6GOiq zn14=)ysnK1Uy2#TdOxBKgbu8vi|JwEMhz(Kqp4yxd{5-Zc`pn`E>$0W8WHn#orOt@7-4H6}j!VPsyBaf-3%v-%8YR+fa(Xk!O)M}1iZ{xZ^ed{mmdlF#;4;}7Z?SqXaj>%ZeW1mZ-E-Dt? zgIlwqkYmz}YF9>YOrJMatB9n+(cTmQsVGa(Qe@_fsFQi1%Wq`Dm}D52Q83qa_sV&q zU3o@WL?!i>57%_l8M&Ne<$7+GQm+i^XDr|WQe?HoLhSfoL0z{+2j0^!eCn|fs=abb z-)}&qBd;dz4K|{?MHd&NHcTJSSJ_mB%v%FH+~CulSsH$wnLgQv@S*xms&WKFt-0<%O-KDYeGEg~zM(F~+QS z#Blr=LbnXg+#OTvLr|vniW&g#A}_2E903J9VCSl($6SRR(ANYItcIi*)iCkGGq{Z6 zXFf95u?uCmMEx;;lTEnWH|%ezTwhnzbU{p|7vIKhyM>caR@dlU5F7FII?NA6z5m1t zWJ#R@#3<~a3|u^W;W->zaawk8Y_sTOP8vLFP?%_`3xM6S88BdXNWbk}vebq@yhod3 z?1)^Cp(yb9c_R11FJBzX0_4o7VB3P@a!wbU z-6wd?hIPRSywCEWz{6EvsMrvj-$Nm8DR?=*wW|>LxL0~Zija8-oT zjU*`oBbvhRYI^Zj|FzEKa0`Uo>^ziYYFu22ITe2p&BPo$WSlKKK25a*l9Jl0_mqWh zr*XXSl}wqe;V)idA)Yf)&He6bQc*Kl=F(5BE7V`;YPY7Ps9m@eQ$(f4Gh2c{!-dS< zA8F(ppF$b13+l^k>(dB$5vtus^Y`*idHni1vQK(YiLrw>y(M;4%`ACAbByZR$gI@` z|C7tfXX}ekGiHZ2@B4GB@mjwB%>TKjWj2a`xU*1X-f)&RnR+qGs!$bAGiA=lZhZaxk@Jf2xin*Rz6ns7rKu zXiMYWH_C+cX6?yVof*E>!*YgsyOIz7$gC(TCq8r~4E=4bGCnB} zy_0a<8ORr7sC}?_nq Oll& Date: Wed, 19 Aug 2020 18:54:23 +0000 Subject: [PATCH 315/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c367217e7..d01488a7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-08-19] Version 6.4.6 +-------------------------- +**Library - Chore** +- [PR #929](https://github.com/sendgrid/sendgrid-python/pull/929): update GitHub branch references to use HEAD. Thanks to [@thinkingserious](https://github.com/thinkingserious)! + + [2020-08-05] Version 6.4.5 -------------------------- **Library - Docs** From bf2aee6b23ab486b86bae4a8121e629b4ef60b33 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 19 Aug 2020 19:45:02 +0000 Subject: [PATCH 316/462] Release 6.4.6 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 9bf999671..a8b77c27e 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.4.5' +__version__ = '6.4.6' From f9d0513aacfa8293920d472aa744524417b5c5f2 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Mon, 24 Aug 2020 09:57:48 -0500 Subject: [PATCH 317/462] docs: remove roadmap/milestone sections for CONTRIBUTING and README --- CONTRIBUTING.md | 3 --- README.md | 6 ------ README.rst | 8 -------- 3 files changed, 17 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 92d72897e..c2769176d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,9 +17,6 @@ All third party contributors acknowledge that any contributions they provide wil - [Creating a Pull Request](#creating-a-pull-request) - [Code Reviews](#code-reviews) - -We use [Milestones](https://github.com/sendgrid/sendgrid-python/milestones) to help define current roadmaps, please feel free to grab an issue from the current milestone. Please indicate that you have begun work on it to avoid collisions. Once a PR is made, community reviews, comments, suggestions, and additional PRs are welcomed and encouraged. - There are a few ways to contribute, which we'll enumerate below: ## Feature Request diff --git a/README.md b/README.md index d8049ca23..f6f8c76bc 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,6 @@ We appreciate your continued support, thank you! * [Usage](#usage) * [Use Cases](#use-cases) * [Announcements](#announcements) -* [Roadmap](#roadmap) * [How to Contribute](#contribute) * [Troubleshooting](#troubleshooting) * [About](#about) @@ -194,11 +193,6 @@ Please see our announcement regarding [breaking changes](https://github.com/send All updates to this library are documented in our [CHANGELOG](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 diff --git a/README.rst b/README.rst index 88d0fb280..cd2c6bbac 100644 --- a/README.rst +++ b/README.rst @@ -35,7 +35,6 @@ Table of Contents - `General Usage <#usage>`__ - `Processing Inbound Email <#processing-inbound-email>`__ - `Announcements <#announcements>`__ -- `Roadmap <#roadmap>`__ - `How to Contribute <#how-to-contribute>`__ - `Troubleshooting <#troubleshooting>`__ - `About <#about>`__ @@ -226,13 +225,6 @@ Announcements 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 ================= From fd8375601c7f6a76738f18a7ae194ccb2f720ae3 Mon Sep 17 00:00:00 2001 From: Elise Shanholtz Date: Fri, 28 Aug 2020 15:29:02 -0700 Subject: [PATCH 318/462] chore: move encrypted tokens to environment variables --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8c158b2b3..51b69d9d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,5 +38,4 @@ notifications: on_pull_requests: false on_success: never on_failure: change - rooms: - - secure: Yp7gJ6NPRPNgO77vwS0HynSdnU5LYlLlUNBEzcx+zy230UxuLLWcYZtIqsIqt4oZm45OwgJLBwoCMgmU2Jcj79rGyqWKYtUcLMLKgHVzSgxjm2outt2fxjXIJHIU60S3RCGofBJRkPwEMb7ibgwHYBEsH3wIeLrVVbWvimxka6A= + rooms: $SLACK_TOKEN From 9392eabd2d0f9d0eae4321448c4f91dc578cc08b Mon Sep 17 00:00:00 2001 From: Elise Shanholtz Date: Tue, 1 Sep 2020 13:23:18 -0700 Subject: [PATCH 319/462] Revert "chore: move encrypted tokens to environment variables" This reverts commit fd837560 --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 51b69d9d2..8c158b2b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,4 +38,5 @@ notifications: on_pull_requests: false on_success: never on_failure: change - rooms: $SLACK_TOKEN + rooms: + - secure: Yp7gJ6NPRPNgO77vwS0HynSdnU5LYlLlUNBEzcx+zy230UxuLLWcYZtIqsIqt4oZm45OwgJLBwoCMgmU2Jcj79rGyqWKYtUcLMLKgHVzSgxjm2outt2fxjXIJHIU60S3RCGofBJRkPwEMb7ibgwHYBEsH3wIeLrVVbWvimxka6A= From 6606e8de915e873ace7e01e195e9f464b20609f1 Mon Sep 17 00:00:00 2001 From: Arbitrage0 <24595000+Arbitrage0@users.noreply.github.com> Date: Thu, 10 Sep 2020 20:54:16 +0100 Subject: [PATCH 320/462] docs: correct attachment example (#936) --- examples/helpers/mail_example.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/helpers/mail_example.py b/examples/helpers/mail_example.py index de1648df9..700970110 100644 --- a/examples/helpers/mail_example.py +++ b/examples/helpers/mail_example.py @@ -83,10 +83,10 @@ def build_attachment1(): """Build attachment mock. Make sure your content is base64 encoded before passing into attachment.content. Another example: https://github.com/sendgrid/sendgrid-python/blob/HEAD/use_cases/attachment.md""" attachment = Attachment() - attachment.content = ("TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNl" + attachment.file_content = ("TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNl" "Y3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12") - attachment.type = "application/pdf" - attachment.filename = "balance_001.pdf" + attachment.file_type = "application/pdf" + attachment.file_name = "balance_001.pdf" attachment.disposition = "attachment" attachment.content_id = "Balance Sheet" return attachment @@ -95,9 +95,9 @@ def build_attachment1(): def build_attachment2(): """Build attachment mock.""" attachment = Attachment() - attachment.content = "BwdW" - attachment.type = "image/png" - attachment.filename = "banner.png" + attachment.file_content = "BwdW" + attachment.file_type = "image/png" + attachment.file_name = "banner.png" attachment.disposition = "inline" attachment.content_id = "Banner" return attachment @@ -227,19 +227,19 @@ def build_kitchen_sink(): ] message.attachment = Attachment(FileContent('base64 encoded content 1'), - FileType('application/pdf'), FileName('balance_001.pdf'), + FileType('application/pdf'), Disposition('attachment'), ContentId('Content ID 1')) message.attachment = [ Attachment(FileContent('base64 encoded content 2'), - FileType('image/png'), FileName('banner.png'), + FileType('image/png'), Disposition('inline'), ContentId('Content ID 2')), Attachment(FileContent('base64 encoded content 3'), - FileType('image/png'), FileName('banner2.png'), + FileType('image/png'), Disposition('inline'), ContentId('Content ID 3')) ] From 0c139aaeb8c188b5a06bb1c73ae80d59367b7e80 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Tue, 15 Sep 2020 15:00:28 -0500 Subject: [PATCH 321/462] docs: update the eventwebhook sample data --- test/test_eventwebhook.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/test/test_eventwebhook.py b/test/test_eventwebhook.py index 28f9ad282..eee1eabf9 100644 --- a/test/test_eventwebhook.py +++ b/test/test_eventwebhook.py @@ -7,14 +7,22 @@ class UnitTests(unittest.TestCase): @classmethod def setUpClass(cls): - cls.PUBLIC_KEY = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEDr2LjtURuePQzplybdC+u4CwrqDqBaWjcMMsTbhdbcwHBcepxo7yAQGhHPTnlvFYPAZFceEu/1FwCM/QmGUhA==' - cls.SIGNATURE = 'MEUCIQCtIHJeH93Y+qpYeWrySphQgpNGNr/U+UyUlBkU6n7RAwIgJTz2C+8a8xonZGi6BpSzoQsbVRamr2nlxFDWYNH2j/0=' - cls.TIMESTAMP = '1588788367' - cls.PAYLOAD = json.dumps({ - 'event': 'test_event', - 'category': 'example_payload', - 'message_id': 'message_id', - }, sort_keys=True, separators=(',', ':')) + cls.PUBLIC_KEY = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE83T4O/n84iotIvIW4mdBgQ/7dAfSmpqIM8kF9mN1flpVKS3GRqe62gw+2fNNRaINXvVpiglSI8eNEc6wEA3F+g==' + cls.SIGNATURE = 'MEUCIGHQVtGj+Y3LkG9fLcxf3qfI10QysgDWmMOVmxG0u6ZUAiEAyBiXDWzM+uOe5W0JuG+luQAbPIqHh89M15TluLtEZtM=' + cls.TIMESTAMP = '1600112502' + cls.PAYLOAD = json.dumps( + [ + { + 'email': 'hello@world.com', + 'event': 'dropped', + 'reason': 'Bounced Address', + 'sg_event_id': 'ZHJvcC0xMDk5NDkxOS1MUnpYbF9OSFN0T0doUTRrb2ZTbV9BLTA', + 'sg_message_id': 'LRzXl_NHStOGhQ4kofSm_A.filterdrecv-p3mdw1-756b745b58-kmzbl-18-5F5FC76C-9.0', + 'smtp-id': '', + 'timestamp': 1600112492, + } + ], sort_keys=True, separators=(',', ':') + ) + '\r\n' # Be sure to include the trailing carriage return and newline! def test_verify_valid_signature(self): ew = EventWebhook() From 6980cf8eb3978aa16b21d41bcd117043d64cf057 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 16 Sep 2020 23:29:55 +0000 Subject: [PATCH 322/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d01488a7b..3c8128f10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-09-16] Version 6.4.7 +-------------------------- +**Library - Docs** +- [PR #936](https://github.com/sendgrid/sendgrid-python/pull/936): correct attachment example. Thanks to [@Arbitrage0](https://github.com/Arbitrage0)! + + [2020-08-19] Version 6.4.6 -------------------------- **Library - Chore** From e796a3f94cfbbe6f5e94f2cb74b70e4ca3a1eef0 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 16 Sep 2020 23:34:15 +0000 Subject: [PATCH 323/462] Release 6.4.7 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index a8b77c27e..63521b92e 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.4.6' +__version__ = '6.4.7' From 397a57da6e09ce5058f79562e19d518fdd850244 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Mon, 21 Sep 2020 10:25:30 -0500 Subject: [PATCH 324/462] docs: update legacy/dynamic transactional template doc links --- use_cases/legacy_templates.md | 4 ++-- use_cases/transactional_templates.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/use_cases/legacy_templates.md b/use_cases/legacy_templates.md index c1c9204fa..c8188a257 100644 --- a/use_cases/legacy_templates.md +++ b/use_cases/legacy_templates.md @@ -1,6 +1,6 @@ # 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. +For this example, we assume you have created a [legacy transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html) in the UI or via the API. Following is the template content we used for testing. Template ID (replace with your own): @@ -113,4 +113,4 @@ except urllib.HTTPError as e: print(response.status_code) print(response.body) print(response.headers) -``` \ No newline at end of file +``` diff --git a/use_cases/transactional_templates.md b/use_cases/transactional_templates.md index 2a237b946..9ee4848c3 100644 --- a/use_cases/transactional_templates.md +++ b/use_cases/transactional_templates.md @@ -1,6 +1,6 @@ # Transactional Templates -For this example, we assume you have created a [transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html) in the UI or via the API. Following is the template content we used for testing. +For this example, we assume you have created a [dynamic transactional template](https://sendgrid.com/docs/ui/sending-email/how-to-send-an-email-with-dynamic-transactional-templates/) in the UI or via the API. Following is the template content we used for testing. Email Subject: From e5253a571c1537f51be3a9e16057187cb7386653 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Wed, 14 Oct 2020 15:53:28 -0500 Subject: [PATCH 325/462] chore: fix spelling typos --- FIRST_TIMERS.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/FIRST_TIMERS.md b/FIRST_TIMERS.md index f6406f783..528580c34 100644 --- a/FIRST_TIMERS.md +++ b/FIRST_TIMERS.md @@ -61,13 +61,13 @@ Before creating a pull request, make sure that you respect the repository's cons * [Node.js SDK](https://github.com/sendgrid/sendgrid-nodejs/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) * [Java SDK](https://github.com/sendgrid/sendgrid-java/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) * [Go SDK](https://github.com/sendgrid/sendgrid-go/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Python STMPAPI Client](https://github.com/sendgrid/smtpapi-python/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [PHP STMPAPI Client](https://github.com/sendgrid/smtpapi-php/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [C# STMPAPI Client](https://github.com/sendgrid/smtpapi-csharp/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Ruby STMPAPI Client](https://github.com/sendgrid/smtpapi-ruby/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Node.js STMPAPI Client](https://github.com/sendgrid/smtpapi-nodejs/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Java STMPAPI Client](https://github.com/sendgrid/smtpapi-java/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Go STMPAPI Client](https://github.com/sendgrid/smtpapi-go/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Python SMTPAPI Client](https://github.com/sendgrid/smtpapi-python/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [PHP SMTPAPI Client](https://github.com/sendgrid/smtpapi-php/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [C# SMTPAPI Client](https://github.com/sendgrid/smtpapi-csharp/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Ruby SMTPAPI Client](https://github.com/sendgrid/smtpapi-ruby/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Node.js SMTPAPI Client](https://github.com/sendgrid/smtpapi-nodejs/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Java SMTPAPI Client](https://github.com/sendgrid/smtpapi-java/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) +* [Go SMTPAPI Client](https://github.com/sendgrid/smtpapi-go/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) * [Python HTTP Client](https://github.com/sendgrid/python-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) * [PHP HTTP Client](https://github.com/sendgrid/php-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) * [C# HTTP Client](https://github.com/sendgrid/csharp-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) From 16497e920a9be4b01d712864180aee90ab3bf78e Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 15 Oct 2020 16:25:23 +0000 Subject: [PATCH 326/462] chore: update template files --- PULL_REQUEST_TEMPLATE.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index a86818029..46d80c650 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -19,13 +19,13 @@ Closes #2 A short description of what this PR does. ### Checklist -- [ ] I acknowledge that all my contributions will be made under the project's license +- [x] I acknowledge that all my contributions will be made under the project's license - [ ] I have made a material change to the repo (functionality, testing, spelling, grammar) -- [ ] I have read the [Contribution Guidelines](CONTRIBUTING.md) and my PR follows them +- [ ] I have read the [Contribution Guidelines](https://github.com/sendgrid/sendgrid-python/blob/main/CONTRIBUTING.md) and my PR follows them - [ ] I have titled the PR appropriately - [ ] I have updated my branch with the main branch - [ ] I have added tests that prove my fix is effective or that my feature works -- [ ] I have added necessary documentation about the functionality in the appropriate .md file +- [ ] I have added the necessary documentation about the functionality in the appropriate .md file - [ ] I have added inline documentation to the code I modified If you have questions, please file a [support ticket](https://twilio.com/help/contact), or create a GitHub Issue in this repository. From 9e52a66a61aeccbec7a773be1f8c28fd144994a2 Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 15 Oct 2020 16:35:17 +0000 Subject: [PATCH 327/462] chore: update template files --- PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 46d80c650..05c4d872c 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -28,4 +28,4 @@ A short description of what this PR does. - [ ] I have added the necessary documentation about the functionality in the appropriate .md file - [ ] I have added inline documentation to the code I modified -If you have questions, please file a [support ticket](https://twilio.com/help/contact), or create a GitHub Issue in this repository. +If you have questions, please file a [support ticket](https://support.sendgrid.com/hc/en-us), or create a GitHub Issue in this repository. From a453cec35d0de18b5735ff9648b62f8325da4e0e Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 15 Oct 2020 21:28:41 +0000 Subject: [PATCH 328/462] chore: update template files --- PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 05c4d872c..7c2789ae4 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -28,4 +28,4 @@ A short description of what this PR does. - [ ] I have added the necessary documentation about the functionality in the appropriate .md file - [ ] I have added inline documentation to the code I modified -If you have questions, please file a [support ticket](https://support.sendgrid.com/hc/en-us), or create a GitHub Issue in this repository. +If you have questions, please file a [support ticket](https://support.sendgrid.com), or create a GitHub Issue in this repository. From b42050cb84d6971b73f9cf35d5a996b6fdfd7201 Mon Sep 17 00:00:00 2001 From: Twilio Date: Tue, 27 Oct 2020 21:34:26 +0000 Subject: [PATCH 329/462] chore: update template files --- LICENSE.md => LICENSE | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename LICENSE.md => LICENSE (100%) diff --git a/LICENSE.md b/LICENSE similarity index 100% rename from LICENSE.md rename to LICENSE From b360223622418216f89a98278cfa1cde3e2a9ceb Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Tue, 27 Oct 2020 16:42:33 -0500 Subject: [PATCH 330/462] chore: update license references --- MANIFEST.in | 2 +- README.md | 4 ++-- README.rst | 4 ++-- test/test_project.py | 4 ++-- test/test_sendgrid.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index cd3c5f680..bf5b007ea 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include README.rst -include LICENSE.md +include LICENSE include app.json include Procfile include requirements.txt diff --git a/README.md b/README.md index f6f8c76bc..62c79bb31 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![codecov](https://img.shields.io/codecov/c/github/sendgrid/sendgrid-python/main.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.md) +[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Twitter Follow](https://img.shields.io/twitter/follow/sendgrid.svg?style=social&label=Follow)](https://twitter.com/sendgrid) [![GitHub contributors](https://img.shields.io/github/contributors/sendgrid/sendgrid-python.svg)](https://github.com/sendgrid/sendgrid-python/graphs/contributors) [![Open Source Helpers](https://www.codetriage.com/sendgrid/sendgrid-python/badges/users.svg)](https://www.codetriage.com/sendgrid/sendgrid-python) @@ -221,4 +221,4 @@ If you've instead found a bug in the library or would like new features added, g # License -[The MIT License (MIT)](LICENSE.md) +[The MIT License (MIT)](LICENSE) diff --git a/README.rst b/README.rst index cd2c6bbac..ba131179c 100644 --- a/README.rst +++ b/README.rst @@ -286,7 +286,7 @@ License .. _Improvements to the Codebase: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md#improvements-to-the-codebase .. _Review Pull Requests: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md#code-reviews .. _troubleshooting guide: https://github.com/sendgrid/sendgrid-python/blob/HEAD/TROUBLESHOOTING.md -.. _The MIT License (MIT): https://github.com/sendgrid/sendgrid-python/blob/HEAD/LICENSE.md +.. _The MIT License (MIT): https://github.com/sendgrid/sendgrid-python/blob/HEAD/LICENSE .. |Travis Badge| image:: https://travis-ci.org/sendgrid/sendgrid-python.svg?branch=main :target: https://travis-ci.org/sendgrid/sendgrid-python @@ -301,7 +301,7 @@ License .. |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.md + :target: ./LICENSE .. |Twitter Follow| image:: https://img.shields.io/twitter/follow/sendgrid.svg?style=social&label=Follow :target: https://twitter.com/sendgrid .. |GitHub contributors| image:: https://img.shields.io/github/contributors/sendgrid/sendgrid-python.svg diff --git a/test/test_project.py b/test/test_project.py index 79f27972d..e30049a3f 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -35,9 +35,9 @@ def test_contributing(self): def test_issue_template(self): self.assertTrue(os.path.isfile('./ISSUE_TEMPLATE.md')) - # ./LICENSE.md + # ./LICENSE def test_license(self): - self.assertTrue(os.path.isfile('./LICENSE.md')) + self.assertTrue(os.path.isfile('./LICENSE')) # ./PULL_REQUEST_TEMPLATE.md def test_pr_template(self): diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index eb9ebabb3..f6177a7d5 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -2297,7 +2297,7 @@ def test_whitelabel_links__link_id__subuser_post(self): self.assertEqual(response.status_code, 200) def test_license_year(self): - LICENSE_FILE = 'LICENSE.md' + LICENSE_FILE = 'LICENSE' copyright_line = '' with open(LICENSE_FILE, 'r') as f: for line in f: From f3d78389f2b088d061eebef8d027d706378d552d Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Fri, 30 Oct 2020 14:17:14 -0700 Subject: [PATCH 331/462] chore: update badge --- README.md | 2 +- README.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 62c79bb31..de23ef2f6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![SendGrid Logo](twilio_sendgrid_logo.png) -[![Travis Badge](https://travis-ci.org/sendgrid/sendgrid-python.svg?branch=main)](https://travis-ci.org/sendgrid/sendgrid-python) +[![Travis Badge](https://travis-ci.com/sendgrid/sendgrid-python.svg?branch=main)](https://travis-ci.com/sendgrid/sendgrid-python) [![codecov](https://img.shields.io/codecov/c/github/sendgrid/sendgrid-python/main.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) diff --git a/README.rst b/README.rst index ba131179c..a7a076c22 100644 --- a/README.rst +++ b/README.rst @@ -288,8 +288,8 @@ License .. _troubleshooting guide: https://github.com/sendgrid/sendgrid-python/blob/HEAD/TROUBLESHOOTING.md .. _The MIT License (MIT): https://github.com/sendgrid/sendgrid-python/blob/HEAD/LICENSE -.. |Travis Badge| image:: https://travis-ci.org/sendgrid/sendgrid-python.svg?branch=main - :target: https://travis-ci.org/sendgrid/sendgrid-python +.. |Travis Badge| image:: https://travis-ci.com/sendgrid/sendgrid-python.svg?branch=main + :target: https://travis-ci.com/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 From 3e40d3cc1014cfc153920bb8c489cfcbf0d3b3db Mon Sep 17 00:00:00 2001 From: Razvan Dimescu Date: Mon, 30 Nov 2020 21:57:12 +0200 Subject: [PATCH 332/462] docs: fixed typo in sendgrid/helpers/mail/file_content.py (#955) --- sendgrid/helpers/mail/file_content.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/helpers/mail/file_content.py b/sendgrid/helpers/mail/file_content.py index c5c0d6995..c1eb81fc6 100644 --- a/sendgrid/helpers/mail/file_content.py +++ b/sendgrid/helpers/mail/file_content.py @@ -31,7 +31,7 @@ def file_content(self, value): def get(self): """ - Get a JSON-ready representation of this FileContente. + Get a JSON-ready representation of this FileContent. :returns: This FileContent, ready for use in a request body. :rtype: string From 841088f0ef63c50d3ed51a74e82a172151a7016e Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 2 Dec 2020 19:55:02 +0000 Subject: [PATCH 333/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c8128f10..275cdcbc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2020-12-02] Version 6.4.8 +-------------------------- +**Library - Docs** +- [PR #955](https://github.com/sendgrid/sendgrid-python/pull/955): fixed typo in sendgrid/helpers/mail/file_content.py. Thanks to [@razvandimescu](https://github.com/razvandimescu)! + + [2020-09-16] Version 6.4.7 -------------------------- **Library - Docs** From 62315e699555fb78173edf2e2acbb510eab9aa18 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 2 Dec 2020 19:59:43 +0000 Subject: [PATCH 334/462] Release 6.4.8 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 63521b92e..bec64b5d2 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.4.7' +__version__ = '6.4.8' From 74293fd053e7d8da87e7714dba009aa3db3602d5 Mon Sep 17 00:00:00 2001 From: Michael Kennedy Date: Mon, 28 Dec 2020 13:40:53 -0800 Subject: [PATCH 335/462] docs: Sending HTML email example is broken (#962) --- use_cases/sending_html_content.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/use_cases/sending_html_content.md b/use_cases/sending_html_content.md index ba38b19aa..4a828e737 100644 --- a/use_cases/sending_html_content.md +++ b/use_cases/sending_html_content.md @@ -35,14 +35,13 @@ html_text = """ 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") +to_email = To("to_email@example.com") subject = Subject("Test Subject") html_content = HtmlContent(html_text) soup = BeautifulSoup(html_text) plain_text = soup.get_text() plain_text_content = Content("text/plain", plain_text) -mail.add_content(plain_content) message = Mail(from_email, to_email, subject, plain_text_content, html_content) @@ -54,4 +53,4 @@ try: except urllib.HTTPError as e: print(e.read()) exit() -``` \ No newline at end of file +``` From 6807622981ec37abb05c8c247a6a6fbbab2a0f9f Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 6 Jan 2021 18:18:37 +0000 Subject: [PATCH 336/462] chore: update template files --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 29aba592a..e5439a92d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (C) 2020, Twilio SendGrid, Inc. +Copyright (C) 2021, Twilio SendGrid, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From 39844d63dd9023adaee5da79b372b1aee8fbee0a Mon Sep 17 00:00:00 2001 From: arun tvs Date: Sat, 9 Jan 2021 03:31:27 +0530 Subject: [PATCH 337/462] feat: Support for AMP HTML Email (#945) --- sendgrid/helpers/mail/__init__.py | 1 + sendgrid/helpers/mail/amp_html_content.py | 59 +++++ sendgrid/helpers/mail/content.py | 6 +- sendgrid/helpers/mail/mail.py | 23 +- sendgrid/helpers/mail/mime_type.py | 1 + test/test_mail_helpers.py | 267 ++++++++++++++++++++++ use_cases/sending_amp_html_content.md | 102 +++++++++ 7 files changed, 454 insertions(+), 5 deletions(-) create mode 100644 sendgrid/helpers/mail/amp_html_content.py create mode 100644 use_cases/sending_amp_html_content.md diff --git a/sendgrid/helpers/mail/__init__.py b/sendgrid/helpers/mail/__init__.py index 15cc1cc7e..28d80ac18 100644 --- a/sendgrid/helpers/mail/__init__.py +++ b/sendgrid/helpers/mail/__init__.py @@ -27,6 +27,7 @@ from .groups_to_display import GroupsToDisplay from .header import Header from .html_content import HtmlContent +from .amp_html_content import AmpHtmlContent from .ip_pool_name import IpPoolName from .mail_settings import MailSettings from .mail import Mail diff --git a/sendgrid/helpers/mail/amp_html_content.py b/sendgrid/helpers/mail/amp_html_content.py new file mode 100644 index 000000000..1a282053f --- /dev/null +++ b/sendgrid/helpers/mail/amp_html_content.py @@ -0,0 +1,59 @@ +from .content import Content +from .validators import ValidateApiKey + + +class AmpHtmlContent(Content): + """AMP HTML content to be included in your email.""" + + def __init__(self, content): + """Create an AMP HTML Content with the specified MIME type and content. + + :param content: The AMP HTML content. + :type content: string + """ + self._content = None + self._validator = ValidateApiKey() + + if content is not None: + self.content = content + + @property + def mime_type(self): + """The MIME type for AMP HTML content. + + :rtype: string + """ + return "text/x-amp-html" + + @property + def content(self): + """The actual AMP HTML content. + + :rtype: string + """ + return self._content + + @content.setter + def content(self, value): + """The actual AMP HTML content. + + :param value: The actual AMP HTML content. + :type value: string + """ + self._validator.validate_message_dict(value) + self._content = value + + def get(self): + """ + Get a JSON-ready representation of this AmpContent. + + :returns: This AmpContent, ready for use in a request body. + :rtype: dict + """ + content = {} + if self.mime_type is not None: + content["type"] = self.mime_type + + if self.content is not None: + content["value"] = self.content + return content diff --git a/sendgrid/helpers/mail/content.py b/sendgrid/helpers/mail/content.py index 73bf9f64d..618eee917 100644 --- a/sendgrid/helpers/mail/content.py +++ b/sendgrid/helpers/mail/content.py @@ -29,7 +29,7 @@ def __init__(self, mime_type, content): @property def mime_type(self): """The MIME type of the content you are including in your email. - For example, "text/plain" or "text/html". + For example, "text/plain" or "text/html" or "text/x-amp-html". :rtype: string """ @@ -38,11 +38,11 @@ def mime_type(self): @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". + For example, "text/plain" or "text/html" or "text/x-amp-html". :param value: The MIME type of the content you are including in your email. - For example, "text/plain" or "text/html". + For example, "text/plain" or "text/html" or "text/x-amp-html". :type value: string """ self._mime_type = value diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index db2399310..0069a3f7d 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -27,6 +27,7 @@ def __init__( subject=None, plain_text_content=None, html_content=None, + amp_html_content=None, global_substitutions=None, is_multiple=False): """ @@ -43,6 +44,8 @@ def __init__( :type plain_text_content: string, optional :param html_content: The html body of the email :type html_content: string, optional + :param amp_html_content: The amp-html body of the email + :type amp_html_content: string, optional """ self._attachments = None self._categories = None @@ -71,6 +74,8 @@ def __init__( self.subject = subject if plain_text_content is not None: self.add_content(plain_text_content, MimeType.text) + if amp_html_content is not None: + self.add_content(amp_html_content, MimeType.amp) if html_content is not None: self.add_content(html_content, MimeType.html) @@ -725,9 +730,23 @@ def add_content(self, content, mime_type=None): """ 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": + # Content of mime type text/plain must always come first, followed by text/x-amp-html and then text/html + if content.mime_type == MimeType.text: self._contents = self._ensure_insert(content, self._contents) + elif content.mime_type == MimeType.amp: + if self._contents: + for _content in self._contents: + # this is written in the context that plain text content will always come earlier than the html content + if _content.mime_type == MimeType.text: + index = 1 + break + elif _content.mime_type == MimeType.html: + index = 0 + break + else: + index = 0 + self._contents = self._ensure_append( + content, self._contents, index=index) else: if self._contents: index = len(self._contents) diff --git a/sendgrid/helpers/mail/mime_type.py b/sendgrid/helpers/mail/mime_type.py index 0d0c9b3b3..a2f88c5af 100644 --- a/sendgrid/helpers/mail/mime_type.py +++ b/sendgrid/helpers/mail/mime_type.py @@ -3,3 +3,4 @@ class MimeType(object): """ text = "text/plain" html = "text/html" + amp = "text/x-amp-html" diff --git a/test/test_mail_helpers.py b/test/test_mail_helpers.py index 202d3948b..d0d09c9d3 100644 --- a/test/test_mail_helpers.py +++ b/test/test_mail_helpers.py @@ -17,6 +17,40 @@ Subject, Substitution, To, Cc, Bcc, TrackingSettings ) +# The below amp html email content is taken from [Google AMP Hello World Email](https://amp.dev/documentation/examples/introduction/hello_world_email/) +amp_html_content = '''

Hello!

''' + +response_content_with_all_three_mime_contents = json.dumps({ + "content": [ + { + "type": "text/plain", + "value": "and easy to do anywhere, even with Python" + }, + { + "type": "text/x-amp-html", + "value": amp_html_content + }, + { + "type": "text/html", + "value": "and easy to do anywhere, even with Python" + } + ], + "from": { + "email": "test+from@example.com", + "name": "Example From Name" + }, + "personalizations": [ + { + "to": [ + { + "email": "test+to@example.com", + "name": "Example To Name" + } + ] + } + ], + "subject": "Sending with SendGrid is Fun" +}) class UnitTests(unittest.TestCase): @@ -284,6 +318,239 @@ def test_multiple_emails_to_multiple_recipients(self): }''') ) + def test_single_email_with_all_three_email_contents_to_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + amp_html_content=AmpHtmlContent(amp_html_content), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python') + ) + + self.assertEqual( + message.get(), + json.loads(response_content_with_all_three_mime_contents) + ) + + def test_single_email_with_amp_and_html_contents_to_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun'), + amp_html_content=AmpHtmlContent(amp_html_content), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python') + ) + + response_content = json.dumps({ + "content": [ + { + "type": "text/x-amp-html", + "value": amp_html_content + }, + { + "type": "text/html", + "value": "and easy to do anywhere, even with Python" + } + ], + "from": { + "email": "test+from@example.com", + "name": "Example From Name" + }, + "personalizations": [ + { + "to": [ + { + "email": "test+to@example.com", + "name": "Example To Name" + } + ] + } + ], + "subject": "Sending with SendGrid is Fun" + }) + self.assertEqual( + message.get(), + json.loads(response_content) + ) + + def test_single_email_with_amp_and_plain_contents_to_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + amp_html_content=AmpHtmlContent(amp_html_content) + ) + + response_content = json.dumps({ + "content": [ + { + "type": "text/plain", + "value": "and easy to do anywhere, even with Python" + }, + { + "type": "text/x-amp-html", + "value": amp_html_content + } + ], + "from": { + "email": "test+from@example.com", + "name": "Example From Name" + }, + "personalizations": [ + { + "to": [ + { + "email": "test+to@example.com", + "name": "Example To Name" + } + ] + } + ], + "subject": "Sending with SendGrid is Fun" + }) + self.assertEqual( + message.get(), + json.loads(response_content) + ) + + ## Check ordering of MIME types in different variants - start + def test_single_email_with_all_three_contents_in_collapsed_order_of_plain_amp_html_content_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun') + ) + message.content = PlainTextContent( + 'and easy to do anywhere, even with Python') + message.content = AmpHtmlContent(amp_html_content) + message.content = HtmlContent( + 'and easy to do anywhere, even with Python') + + self.assertEqual( + message.get(), + json.loads(response_content_with_all_three_mime_contents) + ) + + def test_single_email_with_all_three_contents_in_collapsed_order_of_plain_html_amp_content_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun') + ) + message.content = PlainTextContent( + 'and easy to do anywhere, even with Python') + message.content = HtmlContent( + 'and easy to do anywhere, even with Python') + message.content = AmpHtmlContent(amp_html_content) + + self.assertEqual( + message.get(), + json.loads(response_content_with_all_three_mime_contents) + ) + + def test_single_email_with_all_three_contents_in_collapsed_order_of_html_plain_amp_content_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun') + ) + message.content = HtmlContent( + 'and easy to do anywhere, even with Python') + message.content = PlainTextContent( + 'and easy to do anywhere, even with Python') + message.content = AmpHtmlContent(amp_html_content) + + self.assertEqual( + message.get(), + json.loads(response_content_with_all_three_mime_contents) + ) + + def test_single_email_with_all_three_contents_in_collapsed_order_of_html_amp_plain_content_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun') + ) + message.content = HtmlContent( + 'and easy to do anywhere, even with Python') + message.content = AmpHtmlContent(amp_html_content) + message.content = PlainTextContent( + 'and easy to do anywhere, even with Python') + + self.assertEqual( + message.get(), + json.loads(response_content_with_all_three_mime_contents) + ) + + def test_single_email_with_all_three_contents_in_collapsed_order_of_amp_html_plain_content_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun') + ) + message.content = AmpHtmlContent(amp_html_content) + message.content = HtmlContent( + 'and easy to do anywhere, even with Python') + message.content = PlainTextContent( + 'and easy to do anywhere, even with Python') + + self.assertEqual( + message.get(), + json.loads(response_content_with_all_three_mime_contents) + ) + + def test_single_email_with_all_three_contents_in_collapsed_order_of_amp_plain_html_content_single_recipient(self): + from sendgrid.helpers.mail import (Mail, From, To, Subject, + PlainTextContent, HtmlContent, AmpHtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun') + ) + message.content = AmpHtmlContent(amp_html_content) + message.content = PlainTextContent( + 'and easy to do anywhere, even with Python') + message.content = HtmlContent( + 'and easy to do anywhere, even with Python') + + self.assertEqual( + message.get(), + json.loads(response_content_with_all_three_mime_contents) + ) + + ## end + def test_value_error_is_raised_on_to_emails_set_to_list_of_lists(self): from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) self.maxDiff = None diff --git a/use_cases/sending_amp_html_content.md b/use_cases/sending_amp_html_content.md new file mode 100644 index 000000000..616b52039 --- /dev/null +++ b/use_cases/sending_amp_html_content.md @@ -0,0 +1,102 @@ +# Sending AMP-HTML Email + +Following is an example on how to send an AMP HTML Email. +Currently, we require AMP HTML and any one of HTML or Plain Text content (preferrably both) for improved deliverability or fallback for AMP HTML Email for supporting older clients and showing alternate content after 30 days. + +For more information on AMP emails pls check the [official AMP email page](https://amp.dev/about/email/) + +```python +import os +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail + +# The below amp html email is taken from [Google AMP Hello World Email](https://amp.dev/documentation/examples/introduction/hello_world_email/) +amp_html_content = ''' + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Hello!

+ + + + + + + + +
+ + +''' + +message = Mail( + from_email='example@example.com', + to_emails='example@example.com', + subject='Sending with Twilio SendGrid is Fun', + html_content='and easy to do anywhere, even with Python', + amp_html_content=amp_html_content) +try: + sg = SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) + response = sg.send(message) + print(response.status_code) + print(response.body) + print(response.headers) +except Exception as e: + print(e.message) +``` \ No newline at end of file From 92ad033da0a7f843eb138469b026ef3578ff62ca Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 13 Jan 2021 20:20:17 +0000 Subject: [PATCH 338/462] [Librarian] Version Bump --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 275cdcbc9..eba273953 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Change Log All notable changes to this project will be documented in this file. +[2021-01-13] Version 6.5.0 +-------------------------- +**Library - Feature** +- [PR #945](https://github.com/sendgrid/sendgrid-python/pull/945): Support for AMP HTML Email. Thanks to [@modernwarfareuplink](https://github.com/modernwarfareuplink)! + +**Library - Docs** +- [PR #962](https://github.com/sendgrid/sendgrid-python/pull/962): Sending HTML email example is broken. Thanks to [@mikeckennedy](https://github.com/mikeckennedy)! + + [2020-12-02] Version 6.4.8 -------------------------- **Library - Docs** From bea36da8ecd535f22d266a3a7f2bd6377af7d7cf Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 13 Jan 2021 20:24:49 +0000 Subject: [PATCH 339/462] Release 6.5.0 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index bec64b5d2..4a09e5e87 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.4.8' +__version__ = '6.5.0' From fdf5c497767382879134175e21867c851905e792 Mon Sep 17 00:00:00 2001 From: John Calhoun Date: Fri, 29 Jan 2021 15:48:09 -0800 Subject: [PATCH 340/462] feat: remove duplicate emails ignoring case in Personalization (#924) Co-authored-by: childish-sambino Co-authored-by: Elmer Thomas Co-authored-by: Elise Shanholtz --- sendgrid/helpers/mail/personalization.py | 20 ++- test/test_mail_helpers.py | 218 +++++++++++++++++++++++ 2 files changed, 235 insertions(+), 3 deletions(-) diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py index 9239f9458..21a31c863 100644 --- a/sendgrid/helpers/mail/personalization.py +++ b/sendgrid/helpers/mail/personalization.py @@ -27,6 +27,20 @@ def add_email(self, email): self.add_bcc(email) return raise ValueError('Please use a To, Cc or Bcc object.') + + def _get_unique_recipients(self, recipients): + unique_recipients = [] + + for recipient in recipients: + recipient_email = recipient['email'].lower() if isinstance(recipient, dict) else recipient.email.lower() + if all( + unique_recipient['email'].lower() != recipient_email for unique_recipient in unique_recipients + ): + new_unique_recipient = recipient if isinstance(recipient, dict) else recipient.get() + unique_recipients.append(new_unique_recipient) + + return unique_recipients + @property def tos(self): @@ -34,7 +48,7 @@ def tos(self): :rtype: list(dict) """ - return self._tos + return self._get_unique_recipients(self._tos) @tos.setter def tos(self, value): @@ -69,7 +83,7 @@ def ccs(self): :rtype: list(dict) """ - return self._ccs + return self._get_unique_recipients(self._ccs) @ccs.setter def ccs(self, value): @@ -89,7 +103,7 @@ def bccs(self): :rtype: list(dict) """ - return self._bccs + return self._get_unique_recipients(self._bccs) @bccs.setter def bccs(self, value): diff --git a/test/test_mail_helpers.py b/test/test_mail_helpers.py index d0d09c9d3..752a9fd85 100644 --- a/test/test_mail_helpers.py +++ b/test/test_mail_helpers.py @@ -646,6 +646,224 @@ def test_error_is_not_raised_on_to_emails_includes_bcc_cc(self): html_content=HtmlContent( 'and easy to do anywhere, even with Python')) + def test_personalization_add_email_filters_out_duplicate_to_emails(self): + self.maxDiff = None + + p = Personalization() + to_email = To('test+to0@example.com', 'Example To Name 0') + p.add_email(to_email) + p.add_email(to_email) + + self.assertEqual([to_email.get()], p.tos) + + def test_personalization_add_email_filters_out_duplicate_to_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + to_email = To('test+to0@example.com', 'Example To Name 0') + to_email_with_caps = To('test+TO0@example.com', 'Example To Name 0') + p.add_email(to_email) + p.add_email(to_email_with_caps) + + self.assertEqual([to_email.get()], p.tos) + + def test_personalization_filters_out_duplicate_cc_emails(self): + self.maxDiff = None + + p = Personalization() + cc_email = Cc('test+cc0@example.com', 'Example Cc Name 0') + p.add_email(cc_email) + p.add_email(cc_email) + + self.assertEqual([cc_email.get()], p.ccs) + + def test_personalization_filters_out_duplicate_cc_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + cc_email = Cc('test+cc0@example.com', 'Example Cc Name 0') + cc_email_with_caps = Cc('test+CC0@example.com', 'Example Cc Name 0') + p.add_email(cc_email) + p.add_email(cc_email_with_caps) + + self.assertEqual([cc_email.get()], p.ccs) + + def test_personalization_filters_out_duplicate_bcc_emails(self): + self.maxDiff = None + + p = Personalization() + bcc_email = Bcc('test+bcc0@example.com', 'Example Bcc Name 0') + p.add_email(bcc_email) + p.add_email(bcc_email) + + self.assertEqual([bcc_email.get()], p.bccs) + + def test_personalization_filters_out_duplicate_bcc_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + bcc_email = Bcc('test+bcc0@example.com', 'Example Bcc Name 0') + bcc_email_with_caps = Bcc('test+BCC0@example.com', 'Example Bcc Name 0') + p.add_email(bcc_email) + p.add_email(bcc_email_with_caps) + + self.assertEqual([bcc_email.get()], p.bccs) + + def test_personalization_tos_setter_filters_out_duplicate_dict_emails(self): + self.maxDiff = None + + p = Personalization() + to_emails = [{ 'email': 'test+to0@example.com', 'name': 'Example To Name 0' }] * 2 + p.tos = to_emails + + self.assertEqual([to_emails[0]], p.tos) + + def test_personalization_tos_setter_filters_out_duplicate_dict_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + to_email = { 'email': 'test+to0@example.com', 'name': 'Example To Name 0' } + to_email_with_caps = { 'email': 'test+TO0@example.com', 'name': 'Example To Name 0' } + to_emails = [to_email, to_email_with_caps] + p.tos = to_emails + + self.assertEqual([to_email], p.tos) + + def test_personalization_tos_setter_filters_out_duplicate_to_emails(self): + self.maxDiff = None + + p = Personalization() + to_emails = [To('test+to0@example.com', 'Example To Name 0')] * 2 + p.tos = to_emails + + self.assertEqual([to_emails[0].get()], p.tos) + + + def test_personalization_tos_setter_filters_out_duplicate_to_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + to_email = To('test+to0@example.com', 'Example To Name 0') + to_email_with_caps = To('test+TO0@example.com', 'Example To Name 0') + to_emails = [to_email, to_email_with_caps] + p.tos = to_emails + + self.assertEqual([to_email.get()], p.tos) + + def test_personalization_ccs_setter_filters_out_duplicate_dict_emails(self): + self.maxDiff = None + + p = Personalization() + cc_emails = [{ 'email': 'test+cc0@example.com', 'name': 'Example Cc Name 0' }] * 2 + p.ccs = cc_emails + + self.assertEqual([cc_emails[0]], p.ccs) + + def test_personalization_ccs_setter_filters_out_duplicate_dict_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + cc_email = { 'email': 'test+cc0@example.com', 'name': 'Example Cc Name 0' } + cc_email_with_caps = { 'email': 'test+CC0@example.com', 'name': 'Example Cc Name 0' } + cc_emails = [cc_email, cc_email_with_caps] + p.ccs = cc_emails + + self.assertEqual([cc_email], p.ccs) + + def test_personalization_ccs_setter_filters_out_duplicate_cc_emails(self): + self.maxDiff = None + + p = Personalization() + cc_emails = [Cc('test+cc0@example.com', 'Example Cc Name 0')] * 2 + p.ccs = cc_emails + + self.assertEqual([cc_emails[0].get()], p.ccs) + + def test_personalization_ccs_setter_filters_out_duplicate_cc_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + cc_email = Cc('test+cc0@example.com', 'Example Cc Name 0') + cc_email_with_caps = Cc('test+CC0@example.com', 'Example Cc Name 0') + p.ccs = [cc_email, cc_email_with_caps] + + self.assertEqual([cc_email.get()], p.ccs) + + def test_personalization_bccs_setter_filters_out_duplicate_dict_emails(self): + self.maxDiff = None + + p = Personalization() + bcc_emails = [{ 'email': 'test+bcc0@example.com', 'name': 'Example Bcc Name 0' }] * 2 + p.bccs = bcc_emails + + self.assertEqual([bcc_emails[0]], p.bccs) + + def test_personalization_bccs_setter_filters_out_duplicate_dict_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + bcc_email = { 'email': 'test+bcc0@example.com', 'name': 'Example Bcc Name 0' } + bcc_email_with_caps = { 'email': 'test+BCC0@example.com', 'name': 'Example Bcc Name 0' } + bcc_emails = [bcc_email, bcc_email_with_caps] + p.bccs = bcc_emails + + self.assertEqual([bcc_email], p.bccs) + + def test_personalization_bccs_setter_filters_out_duplicate_bcc_emails(self): + self.maxDiff = None + + p = Personalization() + bcc_emails = [Bcc('test+bcc0@example.com', 'Example Bcc Name 0')] * 2 + p.bccs = bcc_emails + + self.assertEqual([bcc_emails[0].get()], p.bccs) + + def test_personalization_bccs_setter_filters_out_duplicate_bcc_emails_ignoring_case(self): + self.maxDiff = None + + p = Personalization() + bcc_email = Bcc('test+bcc0@example.com', 'Example Bcc Name 0') + bcc_email_with_caps = Bcc('test+BCC0@example.com', 'Example Bcc Name 0') + p.bccs = [bcc_email, bcc_email_with_caps] + + self.assertEqual([bcc_email.get()], p.bccs) + + def test_personalization_add_to_filters_out_duplicate_to_emails(self): + self.maxDiff = None + + p = Personalization() + to_email = To('test+to0@example.com', 'Example To Name 0') + p.add_to(to_email) + p.add_to(to_email) + + expected = [to_email.get()] + + self.assertEqual(expected, p.tos) + + def test_personalization_add_bcc_filters_out_duplicate_bcc_emails(self): + self.maxDiff = None + + p = Personalization() + bcc_email = Bcc('test+to0@example.com', 'Example To Name 0') + p.add_bcc(bcc_email) + p.add_bcc(bcc_email) + + expected = [bcc_email.get()] + + self.assertEqual(expected, p.bccs) + + def test_personalization_add_cc_filters_out_duplicate_cc_emails(self): + self.maxDiff = None + + p = Personalization() + cc_email = Cc('test+to0@example.com', 'Example To Name 0') + p.add_cc(cc_email) + p.add_cc(cc_email) + + expected = [cc_email.get()] + + self.assertEqual(expected, p.ccs) + def test_dynamic_template_data(self): self.maxDiff = None From eb3014bdcb3de287c6155f37502a5fb80ed01716 Mon Sep 17 00:00:00 2001 From: Abhijeet Kasurde Date: Sat, 6 Feb 2021 06:57:42 +0530 Subject: [PATCH 341/462] docs: Use correct pip installation command (#964) --- TROUBLESHOOTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 589aa2e6a..b9a68b8c0 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -72,7 +72,7 @@ Using pip: ```bash pip uninstall sendgrid -pip install sendgrid=1.6.22 +pip install sendgrid==1.6.22 ``` Download: From 3f97a7fed7b48d7cbe3b80db81abf5bb170bf102 Mon Sep 17 00:00:00 2001 From: Ben Lopatin Date: Fri, 5 Feb 2021 20:29:19 -0500 Subject: [PATCH 342/462] fix: replace names in BatchId docstrings (#971) --- sendgrid/helpers/mail/batch_id.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sendgrid/helpers/mail/batch_id.py b/sendgrid/helpers/mail/batch_id.py index de9960ca2..a4c0f8e9d 100644 --- a/sendgrid/helpers/mail/batch_id.py +++ b/sendgrid/helpers/mail/batch_id.py @@ -18,7 +18,7 @@ def __init__(self, batch_id=None): @property def batch_id(self): - """A unix timestamp. + """The batch ID. :rtype: string """ @@ -26,7 +26,7 @@ def batch_id(self): @batch_id.setter def batch_id(self, value): - """A unix timestamp. + """The batch ID. :param value: Batch Id :type value: string @@ -42,7 +42,7 @@ def __str__(self): def get(self): """ - Get a JSON-ready representation of this SendAt object. + Get a JSON-ready representation of this BatchId object. :returns: The BatchId, ready for use in a request body. :rtype: string From 7821d42f683b44e51c7285c9dc47ee94fe3ee8f4 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 10 Feb 2021 22:56:08 +0000 Subject: [PATCH 343/462] [Librarian] Version Bump --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eba273953..b9344367e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ # Change Log All notable changes to this project will be documented in this file. +[2021-02-10] Version 6.6.0 +-------------------------- +**Library - Docs** +- [PR #964](https://github.com/sendgrid/sendgrid-python/pull/964): Use correct pip installation command. Thanks to [@Akasurde](https://github.com/Akasurde)! + +**Library - Fix** +- [PR #971](https://github.com/sendgrid/sendgrid-python/pull/971): replace names in BatchId docstrings. Thanks to [@bennylope](https://github.com/bennylope)! + +**Library - Feature** +- [PR #924](https://github.com/sendgrid/sendgrid-python/pull/924): remove duplicate emails ignoring case in Personalization. Thanks to [@DougCal](https://github.com/DougCal)! + + [2021-01-13] Version 6.5.0 -------------------------- **Library - Feature** From 4606b8557eb30e5972dfcf1b2e00498c75179ae5 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 10 Feb 2021 23:13:00 +0000 Subject: [PATCH 344/462] Release 6.6.0 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 4a09e5e87..ce16fc2fb 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.5.0' +__version__ = '6.6.0' From 423f2d07766f59507c3c19e289ac346ad6978ba7 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Mon, 8 Mar 2021 23:24:31 -0800 Subject: [PATCH 345/462] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index de23ef2f6..880589c81 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,8 @@ Please see [our helper](sendgrid/helpers/inbound) for utilizing our Inbound Pars # Announcements +Our Developer Experience team is conducting planned maintenance from 03/09/2021 until 03/11/2021. Our next release is scheduled for 03/15/2021. + 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](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. From 3e718fb689d75bfc6c509aa88d7f39214e8bed91 Mon Sep 17 00:00:00 2001 From: Twilio Date: Fri, 12 Mar 2021 20:35:37 +0000 Subject: [PATCH 346/462] chore: update template files --- .github/ISSUE_TEMPLATE/config.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..afcba3446 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,10 @@ +contact_links: + - name: Twilio SendGrid Support + url: https://support.sendgrid.com + about: Get Support + - name: Stack Overflow + url: https://stackoverflow.com/questions/tagged/sendgrid-python+or+sendgrid+python + about: Ask questions on Stack Overflow + - name: Documentation + url: https://sendgrid.com/docs/for-developers/ + about: View Reference Documentation From 38b0ad658a369694471e62808b4e7e3b80520899 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Mon, 15 Mar 2021 12:48:21 -0700 Subject: [PATCH 347/462] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 880589c81..de23ef2f6 100644 --- a/README.md +++ b/README.md @@ -189,8 +189,6 @@ Please see [our helper](sendgrid/helpers/inbound) for utilizing our Inbound Pars # Announcements -Our Developer Experience team is conducting planned maintenance from 03/09/2021 until 03/11/2021. Our next release is scheduled for 03/15/2021. - 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](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. From 16eb0c33c97f8ca293fbc0d8965afd87c0d2f99a Mon Sep 17 00:00:00 2001 From: Alex Narayanan <67122654+anarayanan604@users.noreply.github.com> Date: Fri, 9 Apr 2021 19:02:50 -0400 Subject: [PATCH 348/462] feat: add v3 bypass filters (#983) --- sendgrid/helpers/mail/__init__.py | 3 + .../helpers/mail/bypass_bounce_management.py | 48 ++++++++++ .../helpers/mail/bypass_spam_management.py | 47 ++++++++++ .../mail/bypass_unsubscribe_management.py | 49 +++++++++++ sendgrid/helpers/mail/mail_settings.py | 87 +++++++++++++++++++ test/test_mail_helpers.py | 54 +++++++++++- use_cases/kitchen_sink.md | 6 +- 7 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 sendgrid/helpers/mail/bypass_bounce_management.py create mode 100644 sendgrid/helpers/mail/bypass_spam_management.py create mode 100644 sendgrid/helpers/mail/bypass_unsubscribe_management.py diff --git a/sendgrid/helpers/mail/__init__.py b/sendgrid/helpers/mail/__init__.py index 28d80ac18..358f2d912 100644 --- a/sendgrid/helpers/mail/__init__.py +++ b/sendgrid/helpers/mail/__init__.py @@ -4,7 +4,10 @@ from .bcc_email import Bcc from .bcc_settings import BccSettings from .bcc_settings_email import BccSettingsEmail +from .bypass_bounce_management import BypassBounceManagement from .bypass_list_management import BypassListManagement +from .bypass_spam_management import BypassSpamManagement +from .bypass_unsubscribe_management import BypassUnsubscribeManagement from .category import Category from .cc_email import Cc from .click_tracking import ClickTracking diff --git a/sendgrid/helpers/mail/bypass_bounce_management.py b/sendgrid/helpers/mail/bypass_bounce_management.py new file mode 100644 index 000000000..b0a35105c --- /dev/null +++ b/sendgrid/helpers/mail/bypass_bounce_management.py @@ -0,0 +1,48 @@ +class BypassBounceManagement(object): + """Setting for Bypass Bounce Management + + + Allows you to bypass the bounce list to ensure that the email is delivered to recipients. + Spam report and unsubscribe lists will still be checked; addresses on these other lists + will not receive the message. This filter cannot be combined with the bypass_list_management filter. + """ + + def __init__(self, enable=None): + """Create a BypassBounceManagement. + + :param enable: Whether emails should bypass bounce management. + :type enable: boolean, optional + """ + self._enable = None + + if enable is not None: + self.enable = enable + + @property + def enable(self): + """Indicates if this setting is enabled. + + :rtype: boolean + """ + return self._enable + + @enable.setter + def enable(self, value): + """Indicates if this setting is enabled. + + :param value: Indicates if this setting is enabled. + :type value: boolean + """ + self._enable = value + + def get(self): + """ + Get a JSON-ready representation of this BypassBounceManagement. + + :returns: This BypassBounceManagement, ready for use in a request body. + :rtype: dict + """ + bypass_bounce_management = {} + if self.enable is not None: + bypass_bounce_management["enable"] = self.enable + return bypass_bounce_management diff --git a/sendgrid/helpers/mail/bypass_spam_management.py b/sendgrid/helpers/mail/bypass_spam_management.py new file mode 100644 index 000000000..9b2552eb9 --- /dev/null +++ b/sendgrid/helpers/mail/bypass_spam_management.py @@ -0,0 +1,47 @@ +class BypassSpamManagement(object): + """Setting for Bypass Spam Management + + Allows you to bypass the spam report list to ensure that the email is delivered to recipients. + Bounce and unsubscribe lists will still be checked; addresses on these other lists will not + receive the message. This filter cannot be combined with the bypass_list_management filter. + """ + + def __init__(self, enable=None): + """Create a BypassSpamManagement. + + :param enable: Whether emails should bypass spam management. + :type enable: boolean, optional + """ + self._enable = None + + if enable is not None: + self.enable = enable + + @property + def enable(self): + """Indicates if this setting is enabled. + + :rtype: boolean + """ + return self._enable + + @enable.setter + def enable(self, value): + """Indicates if this setting is enabled. + + :param value: Indicates if this setting is enabled. + :type value: boolean + """ + self._enable = value + + def get(self): + """ + Get a JSON-ready representation of this BypassSpamManagement. + + :returns: This BypassSpamManagement, ready for use in a request body. + :rtype: dict + """ + bypass_spam_management = {} + if self.enable is not None: + bypass_spam_management["enable"] = self.enable + return bypass_spam_management diff --git a/sendgrid/helpers/mail/bypass_unsubscribe_management.py b/sendgrid/helpers/mail/bypass_unsubscribe_management.py new file mode 100644 index 000000000..4867fac22 --- /dev/null +++ b/sendgrid/helpers/mail/bypass_unsubscribe_management.py @@ -0,0 +1,49 @@ +class BypassUnsubscribeManagement(object): + """Setting for Bypass Unsubscribe Management + + + Allows you to bypass the global unsubscribe list to ensure that the email is delivered to recipients. + Bounce and spam report lists will still be checked; addresses on these other lists will not receive + the message. This filter applies only to global unsubscribes and will not bypass group unsubscribes. + This filter cannot be combined with the bypass_list_management filter. + """ + + def __init__(self, enable=None): + """Create a BypassUnsubscribeManagement. + + :param enable: Whether emails should bypass unsubscribe management. + :type enable: boolean, optional + """ + self._enable = None + + if enable is not None: + self.enable = enable + + @property + def enable(self): + """Indicates if this setting is enabled. + + :rtype: boolean + """ + return self._enable + + @enable.setter + def enable(self, value): + """Indicates if this setting is enabled. + + :param value: Indicates if this setting is enabled. + :type value: boolean + """ + self._enable = value + + def get(self): + """ + Get a JSON-ready representation of this BypassUnsubscribeManagement. + + :returns: This BypassUnsubscribeManagement, ready for use in a request body. + :rtype: dict + """ + bypass_unsubscribe_management = {} + if self.enable is not None: + bypass_unsubscribe_management["enable"] = self.enable + return bypass_unsubscribe_management diff --git a/sendgrid/helpers/mail/mail_settings.py b/sendgrid/helpers/mail/mail_settings.py index 45b7db77f..78499ac30 100644 --- a/sendgrid/helpers/mail/mail_settings.py +++ b/sendgrid/helpers/mail/mail_settings.py @@ -3,7 +3,10 @@ class MailSettings(object): def __init__(self, bcc_settings=None, + bypass_bounce_management=None, bypass_list_management=None, + bypass_spam_management=None, + bypass_unsubscribe_management=None, footer_settings=None, sandbox_mode=None, spam_check=None): @@ -11,9 +14,18 @@ def __init__(self, :param bcc_settings: The BCC Settings of this MailSettings :type bcc_settings: BCCSettings, optional + :param bypass_bounce_management: Whether this MailSettings bypasses bounce management. + Should not be combined with bypass_list_management. + :type bypass_list_management: BypassBounceManagement, optional :param bypass_list_management: Whether this MailSettings bypasses list management :type bypass_list_management: BypassListManagement, optional + :param bypass_spam_management: Whether this MailSettings bypasses spam management. + Should not be combined with bypass_list_management. + :type bypass_list_management: BypassSpamManagement, optional + :param bypass_unsubscribe_management: Whether this MailSettings bypasses unsubscribe management. + Should not be combined with bypass_list_management. + :type bypass_list_management: BypassUnsubscribeManagement, optional :param footer_settings: The default footer specified by this MailSettings :type footer_settings: FooterSettings, optional @@ -24,7 +36,10 @@ def __init__(self, :type spam_check: SpamCheck, optional """ self._bcc_settings = None + self._bypass_bounce_management = None self._bypass_list_management = None + self._bypass_spam_management = None + self._bypass_unsubscribe_management = None self._footer_settings = None self._sandbox_mode = None self._spam_check = None @@ -32,9 +47,18 @@ def __init__(self, if bcc_settings is not None: self.bcc_settings = bcc_settings + if bypass_bounce_management is not None: + self.bypass_bounce_management = bypass_bounce_management + if bypass_list_management is not None: self.bypass_list_management = bypass_list_management + if bypass_spam_management is not None: + self.bypass_spam_management = bypass_spam_management + + if bypass_unsubscribe_management is not None: + self.bypass_unsubscribe_management = bypass_unsubscribe_management + if footer_settings is not None: self.footer_settings = footer_settings @@ -61,6 +85,23 @@ def bcc_settings(self, value): """ self._bcc_settings = value + @property + def bypass_bounce_management(self): + """Whether this MailSettings bypasses bounce management. + + :rtype: BypassBounceManagement + """ + return self._bypass_bounce_management + + @bypass_bounce_management.setter + def bypass_bounce_management(self, value): + """Whether this MailSettings bypasses bounce management. + + :param value: Whether this MailSettings bypasses bounce management. + :type value: BypassBounceManagement + """ + self._bypass_bounce_management = value + @property def bypass_list_management(self): """Whether this MailSettings bypasses list management. @@ -78,6 +119,40 @@ def bypass_list_management(self, value): """ self._bypass_list_management = value + @property + def bypass_spam_management(self): + """Whether this MailSettings bypasses spam management. + + :rtype: BypassSpamManagement + """ + return self._bypass_spam_management + + @bypass_spam_management.setter + def bypass_spam_management(self, value): + """Whether this MailSettings bypasses spam management. + + :param value: Whether this MailSettings bypasses spam management. + :type value: BypassSpamManagement + """ + self._bypass_spam_management = value + + @property + def bypass_unsubscribe_management(self): + """Whether this MailSettings bypasses unsubscribe management. + + :rtype: BypassUnsubscribeManagement + """ + return self._bypass_unsubscribe_management + + @bypass_unsubscribe_management.setter + def bypass_unsubscribe_management(self, value): + """Whether this MailSettings bypasses unsubscribe management. + + :param value: Whether this MailSettings bypasses unsubscribe management. + :type value: BypassUnsubscribeManagement + """ + self._bypass_unsubscribe_management = value + @property def footer_settings(self): """The default footer specified by this MailSettings. @@ -141,10 +216,22 @@ def get(self): if self.bcc_settings is not None: mail_settings["bcc"] = self.bcc_settings.get() + if self.bypass_bounce_management is not None: + mail_settings[ + "bypass_bounce_management"] = self.bypass_bounce_management.get() + if self.bypass_list_management is not None: mail_settings[ "bypass_list_management"] = self.bypass_list_management.get() + if self.bypass_spam_management is not None: + mail_settings[ + "bypass_spam_management"] = self.bypass_spam_management.get() + + if self.bypass_unsubscribe_management is not None: + mail_settings[ + "bypass_unsubscribe_management"] = self.bypass_unsubscribe_management.get() + if self.footer_settings is not None: mail_settings["footer"] = self.footer_settings.get() diff --git a/test/test_mail_helpers.py b/test/test_mail_helpers.py index 752a9fd85..57a4ba880 100644 --- a/test/test_mail_helpers.py +++ b/test/test_mail_helpers.py @@ -935,7 +935,8 @@ def test_kitchen_sink(self): FileContent, FileType, Disposition, ContentId, TemplateId, Section, ReplyTo, Category, BatchId, Asm, GroupId, GroupsToDisplay, IpPoolName, MailSettings, BccSettings, BccSettingsEmail, - BypassListManagement, FooterSettings, FooterText, + BypassBounceManagement, BypassListManagement, BypassSpamManagement, + BypassUnsubscribeManagement, FooterSettings, FooterText, FooterHtml, SandBoxMode, SpamCheck, SpamThreshold, SpamUrl, TrackingSettings, ClickTracking, SubscriptionTracking, SubscriptionText, SubscriptionHtml, SubscriptionSubstitutionTag, @@ -1116,7 +1117,10 @@ def test_kitchen_sink(self): mail_settings = MailSettings() mail_settings.bcc_settings = BccSettings( False, BccSettingsEmail("bcc@twilio.com")) + mail_settings.bypass_bounce_management = BypassBounceManagement(False) mail_settings.bypass_list_management = BypassListManagement(False) + mail_settings.bypass_spam_management = BypassSpamManagement(False) + mail_settings.bypass_unsubscribe_management = BypassUnsubscribeManagement(False) mail_settings.footer_settings = FooterSettings( True, FooterText("w00t"), FooterHtml("w00t!")) mail_settings.sandbox_mode = SandBoxMode(True) @@ -1223,9 +1227,18 @@ def test_kitchen_sink(self): "email": "bcc@twilio.com", "enable": false }, + "bypass_bounce_management": { + "enable": false + }, "bypass_list_management": { "enable": false }, + "bypass_spam_management": { + "enable": false + }, + "bypass_unsubscribe_management": { + "enable": false + }, "footer": { "enable": true, "html": "w00t!", @@ -1613,3 +1626,42 @@ def test_disable_tracking(self): tracking_settings.get(), {'click_tracking': {'enable': False, 'enable_text': False}} ) + + def test_bypass_list_management(self): + from sendgrid.helpers.mail import (MailSettings, BypassListManagement) + mail_settings = MailSettings() + mail_settings.bypass_list_management = BypassListManagement(True) + + self.assertEqual( + mail_settings.get(), + { + "bypass_list_management": { + "enable": True + }, + }, + ) + + def test_v3_bypass_filters(self): + from sendgrid.helpers.mail import ( + MailSettings, BypassBounceManagement, + BypassSpamManagement, BypassUnsubscribeManagement + ) + mail_settings = MailSettings() + mail_settings.bypass_bounce_management = BypassBounceManagement(True) + mail_settings.bypass_spam_management = BypassSpamManagement(True) + mail_settings.bypass_unsubscribe_management = BypassUnsubscribeManagement(True) + + self.assertEqual( + mail_settings.get(), + { + "bypass_bounce_management": { + "enable": True + }, + "bypass_spam_management": { + "enable": True + }, + "bypass_unsubscribe_management": { + "enable": True + }, + }, + ) diff --git a/use_cases/kitchen_sink.md b/use_cases/kitchen_sink.md index 68c9e057e..c0a301117 100644 --- a/use_cases/kitchen_sink.md +++ b/use_cases/kitchen_sink.md @@ -8,7 +8,8 @@ from sendgrid.helpers.mail import ( FileContent, FileType, Disposition, ContentId, TemplateId, Section, ReplyTo, Category, BatchId, Asm, GroupId, GroupsToDisplay, IpPoolName, MailSettings, BccSettings, BccSettingsEmail, - BypassListManagement, FooterSettings, FooterText, + BypassBounceManagement, BypassListManagement, BypassSpamManagement, + BypassUnsubscribeManagement, FooterSettings, FooterText, FooterHtml, SandBoxMode, SpamCheck, SpamThreshold, SpamUrl, TrackingSettings, ClickTracking, SubscriptionTracking, SubscriptionText, SubscriptionHtml, SubscriptionSubstitutionTag, @@ -185,7 +186,10 @@ mail_settings = MailSettings() mail_settings.bcc_settings = BccSettings( False, BccSettingsEmail("bcc@twilio.com")) +mail_settings.bypass_bounce_management = BypassBounceManagement(False) mail_settings.bypass_list_management = BypassListManagement(False) +mail_settings.bypass_spam_management = BypassSpamManagement(False) +mail_settings.bypass_unsubscribe_management = BypassUnsubscribeManagement(False) mail_settings.footer_settings = FooterSettings( True, FooterText("w00t"), From cf0924c35c37bbec8e5ca39e963a55f54f0eec11 Mon Sep 17 00:00:00 2001 From: Huong Minh Luu Date: Tue, 20 Apr 2021 07:39:15 +0930 Subject: [PATCH 349/462] docs: Update to_emails type (#986) --- sendgrid/helpers/mail/mail.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 0069a3f7d..ba21f7891 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -216,8 +216,8 @@ def to(self): 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 to_emails: The email addresses of all recipients + :type to_emails: To, str, tuple, list(str), list(tuple), list(To) :param global_substitutions: A dict of substitutions for all recipients :type global_substitutions: dict :param is_multiple: Create a new personalization for each recipient From f7adbc1e83c08fbd4411fa2978b326e51549551b Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 21 Apr 2021 18:45:57 +0000 Subject: [PATCH 350/462] [Librarian] Version Bump --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9344367e..648cecfeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Change Log All notable changes to this project will be documented in this file. +[2021-04-21] Version 6.7.0 +-------------------------- +**Library - Docs** +- [PR #986](https://github.com/sendgrid/sendgrid-python/pull/986): Update to_emails type. Thanks to [@PyGeek03](https://github.com/PyGeek03)! + +**Library - Feature** +- [PR #983](https://github.com/sendgrid/sendgrid-python/pull/983): add v3 bypass filters. Thanks to [@anarayanan604](https://github.com/anarayanan604)! + + [2021-02-10] Version 6.6.0 -------------------------- **Library - Docs** From aa39f715a061f0de993811faea0adb8223657d01 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 21 Apr 2021 18:53:46 +0000 Subject: [PATCH 351/462] Release 6.7.0 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index ce16fc2fb..bc09034be 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.6.0' +__version__ = '6.7.0' From 9c1b88e2f11ee2950d5be98df598c28908672a99 Mon Sep 17 00:00:00 2001 From: Elise Shanholtz Date: Fri, 23 Apr 2021 11:10:11 -0700 Subject: [PATCH 352/462] chore: rotate key --- .travis.yml | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8c158b2b3..ea9a8fe0a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,37 +1,37 @@ -dist: xenial # required for Python >= 3.7 +dist: xenial language: python cache: pip services: - - docker +- docker env: matrix: - - version=2.7 - - version=3.5 - - version=3.6 - - version=3.7 - - version=3.8 + - version=2.7 + - version=3.5 + - version=3.6 + - version=3.7 + - version=3.8 global: - - CC_TEST_REPORTER_ID=$TRAVIS_CODE_CLIMATE_TOKEN + - CC_TEST_REPORTER_ID=$TRAVIS_CODE_CLIMATE_TOKEN before_script: - - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - - chmod +x ./cc-test-reporter - - ./cc-test-reporter before-build +- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 + > ./cc-test-reporter +- chmod +x ./cc-test-reporter +- "./cc-test-reporter before-build" script: - - make test-docker +- make test-docker after_script: - - make test-install - - . venv/bin/activate; codecov - - ./cc-test-reporter after-build --exit-code $? +- make test-install +- ". venv/bin/activate; codecov" +- "./cc-test-reporter after-build --exit-code $?" deploy: provider: pypi - user: "__token__" - password: $PYPI_TOKEN + user: __token__ + password: "$PYPI_TOKEN" skip_cleanup: true distributions: sdist bdist_wheel on: tags: true - condition: $version = 3.6 - + condition: "$version = 3.6" notifications: slack: if: branch = main @@ -39,4 +39,4 @@ notifications: on_success: never on_failure: change rooms: - - secure: Yp7gJ6NPRPNgO77vwS0HynSdnU5LYlLlUNBEzcx+zy230UxuLLWcYZtIqsIqt4oZm45OwgJLBwoCMgmU2Jcj79rGyqWKYtUcLMLKgHVzSgxjm2outt2fxjXIJHIU60S3RCGofBJRkPwEMb7ibgwHYBEsH3wIeLrVVbWvimxka6A= + secure: damw3UZJAjoUy2Wsf9/DWT5XHIJ4DcRucS/sLPVEyynSRqhzJlxGL7gLQ2fdtMNDY+1fs4UhzYzpUIdu+Tz2mSdZlv1kRY5zIWwJ5JK+9PACt5wVXpKN794JGKcDXOh64Bqrd3ofXkyecI2OyTVNdcTu370K/Tlz3xhHvdBqpU0= From 2ac1dcbb86fb2e46ffba70a6038637ff4291d3ed Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Mon, 14 Jun 2021 18:08:14 -0700 Subject: [PATCH 353/462] Update Slack token --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ea9a8fe0a..bb2a49911 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,4 +39,4 @@ notifications: on_success: never on_failure: change rooms: - secure: damw3UZJAjoUy2Wsf9/DWT5XHIJ4DcRucS/sLPVEyynSRqhzJlxGL7gLQ2fdtMNDY+1fs4UhzYzpUIdu+Tz2mSdZlv1kRY5zIWwJ5JK+9PACt5wVXpKN794JGKcDXOh64Bqrd3ofXkyecI2OyTVNdcTu370K/Tlz3xhHvdBqpU0= + secure: n7ZtDd7AvPsw7Wd6fOCCWYiakWpCnYs1QqXpiozF9Rh1Z90XcrQp72utPFyl81jJp7zlgYWXSfHqmnUpbOCWd04WTsC4dkAY6dd/ThARV4kRkxONX3nlbRESOeZIWNXNOeSR1pg6sd9H7xwIDGmmN2arnFRNiQAD0y5li0yxAfQ= From 23e696bd021d44ccc158b62a0edbbf3214bc426b Mon Sep 17 00:00:00 2001 From: Jennifer Mah <42650198+JenniferMah@users.noreply.github.com> Date: Mon, 14 Jun 2021 19:35:02 -0700 Subject: [PATCH 354/462] chore: remove logic adding quotes to names containing , and ; (#994) --- sendgrid/helpers/mail/email.py | 19 ------------------- test/test_email.py | 14 -------------- 2 files changed, 33 deletions(-) diff --git a/sendgrid/helpers/mail/email.py b/sendgrid/helpers/mail/email.py index ba3a98848..aeab26afa 100644 --- a/sendgrid/helpers/mail/email.py +++ b/sendgrid/helpers/mail/email.py @@ -3,20 +3,6 @@ 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 - try: basestring = basestring except NameError: @@ -91,11 +77,6 @@ def name(self, value): if not (value is None or isinstance(value, basestring)): 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 diff --git a/test/test_email.py b/test/test_email.py index eb50374aa..9db060705 100644 --- a/test/test_email.py +++ b/test/test_email.py @@ -66,17 +66,3 @@ 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 + '"') - - def test_add_unicode_name_with_comma(self): - email = Email() - name = u"Name, Some" - email.name = name - - self.assertEqual(email.name, u'"' + name + u'"') From be104f1ff1c2b6220cfa1b3ef7375c1b4d0fa118 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Mon, 14 Jun 2021 22:29:00 -0700 Subject: [PATCH 355/462] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bb2a49911..a4c7241db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,4 +39,4 @@ notifications: on_success: never on_failure: change rooms: - secure: n7ZtDd7AvPsw7Wd6fOCCWYiakWpCnYs1QqXpiozF9Rh1Z90XcrQp72utPFyl81jJp7zlgYWXSfHqmnUpbOCWd04WTsC4dkAY6dd/ThARV4kRkxONX3nlbRESOeZIWNXNOeSR1pg6sd9H7xwIDGmmN2arnFRNiQAD0y5li0yxAfQ= + secure: p48e21acIxzCs/vFcrSGvcv3jk/PnLcI8BaFpek7KE6lFUi+lDOlg3tNJlqBwQh2BKuoFdd4+x5uNcfpm3cl32usPA2K4e5EX6su+yJcrtt18kAt+p9AjrqFqxgKiq3gKCF/Bh8+r+yk8wMgS+WU8Bg2z6cwUqAoy5OcFwkvYu0wbDkFtEwWljXZbWejfEGD5OEq/4aZzM0GNl3DRdVcU7l4p0A3xPLIUJDSjKQ4J3GZSZE64YqHH1ANJergcX6mmMGVIQEHzgAXXBcLanzxTQfySgrrVMJz/xZh4lRJ/EMxMDj9LXFjOgQxJfo5qgPfhgc+s1hFajS0ykcJZW0Y7DnJz42Bjw4HnQayxEoB4/2HBD2Osggkd6mshe86QNzi1Xjd/V+Bs/RfuFHiU63AuAn0F1VHuOyrFu55MDCaJTg5RoWigP3k8cIlIMPAYygdxwB++FwcMiEdnoV9Coh4Lx6d6UNctGUOM22Dlnchn0CXbIb6/jqJA0atM9RvP3O0tcgD1xcN6IfiF55QAkd3E3K1aC+9pvy8UnD6biLf3k6YvaVrO/9ds+KbAhvQhnTnmhc++yAOdb24pbFQQVUYbn6/9nUkFs0Qw5yNP4Smp8dLOL/m9iwSwIqfclVY8GgYzjdDvib7NwWuB2IaHvbNPCsNx7PRtT81RNASoxc/+aM= From 33598f74b64c8da91867bdf5f2b9bf1a74057de8 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 16 Jun 2021 20:55:24 +0000 Subject: [PATCH 356/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 648cecfeb..90674eee0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2021-06-16] Version 6.7.1 +-------------------------- +**Library - Chore** +- [PR #994](https://github.com/sendgrid/sendgrid-python/pull/994): remove logic adding quotes to names containing , and ;. Thanks to [@JenniferMah](https://github.com/JenniferMah)! + + [2021-04-21] Version 6.7.0 -------------------------- **Library - Docs** From 8d9d1f0df0072755a8bb4a71b67c70a3bc6fd072 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 16 Jun 2021 21:05:58 +0000 Subject: [PATCH 357/462] Release 6.7.1 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index bc09034be..157048dac 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.7.0' +__version__ = '6.7.1' From 44dbf5c27da9f47291b5a1fa656b711cce418f53 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 16 Jun 2021 16:27:21 -0700 Subject: [PATCH 358/462] update slack token --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a4c7241db..35f9fdc2a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,4 +39,4 @@ notifications: on_success: never on_failure: change rooms: - secure: p48e21acIxzCs/vFcrSGvcv3jk/PnLcI8BaFpek7KE6lFUi+lDOlg3tNJlqBwQh2BKuoFdd4+x5uNcfpm3cl32usPA2K4e5EX6su+yJcrtt18kAt+p9AjrqFqxgKiq3gKCF/Bh8+r+yk8wMgS+WU8Bg2z6cwUqAoy5OcFwkvYu0wbDkFtEwWljXZbWejfEGD5OEq/4aZzM0GNl3DRdVcU7l4p0A3xPLIUJDSjKQ4J3GZSZE64YqHH1ANJergcX6mmMGVIQEHzgAXXBcLanzxTQfySgrrVMJz/xZh4lRJ/EMxMDj9LXFjOgQxJfo5qgPfhgc+s1hFajS0ykcJZW0Y7DnJz42Bjw4HnQayxEoB4/2HBD2Osggkd6mshe86QNzi1Xjd/V+Bs/RfuFHiU63AuAn0F1VHuOyrFu55MDCaJTg5RoWigP3k8cIlIMPAYygdxwB++FwcMiEdnoV9Coh4Lx6d6UNctGUOM22Dlnchn0CXbIb6/jqJA0atM9RvP3O0tcgD1xcN6IfiF55QAkd3E3K1aC+9pvy8UnD6biLf3k6YvaVrO/9ds+KbAhvQhnTnmhc++yAOdb24pbFQQVUYbn6/9nUkFs0Qw5yNP4Smp8dLOL/m9iwSwIqfclVY8GgYzjdDvib7NwWuB2IaHvbNPCsNx7PRtT81RNASoxc/+aM= + secure: CTG+XvHwzZlUuc8Gdq96+sK9w1FbkRCBg0yiDGtXFVK8fWFPQEwxLBWQ+yL7vzkS1RMc+Ib7ML4tKU2CMlVxgATOT7RetOioFw56RyV/fd+9eon1PONuz+d5HTIHm64GMhKSxvC4TQbUA4m4+kRX8bCsCfUpTz90HpTm5nSXZ/o= From 5eb3aaef6e96cd0469f3f1ffc334a42e67c3bf8e Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 16 Jun 2021 16:39:31 -0700 Subject: [PATCH 359/462] chore: add docker credentials to travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index a4c7241db..a9a470a26 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ before_script: - chmod +x ./cc-test-reporter - "./cc-test-reporter before-build" script: +- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin - make test-docker after_script: - make test-install From 5a31c75568a574b984e0f8aa76a5e8700d23ab0e Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 16 Jun 2021 16:44:30 -0700 Subject: [PATCH 360/462] update slack token --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3d55a26d7..02c163356 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,4 +40,4 @@ notifications: on_success: never on_failure: change rooms: - secure: CTG+XvHwzZlUuc8Gdq96+sK9w1FbkRCBg0yiDGtXFVK8fWFPQEwxLBWQ+yL7vzkS1RMc+Ib7ML4tKU2CMlVxgATOT7RetOioFw56RyV/fd+9eon1PONuz+d5HTIHm64GMhKSxvC4TQbUA4m4+kRX8bCsCfUpTz90HpTm5nSXZ/o= + secure: B0SJHc9Syyf5HOl21abg/Uj/Gp7EusCOly/2JZzUUHCWtxC8C9pWfGf2e674R4vdeJ3FmTKz/1jJZ96vzV0z+XUpT2Fnn6URi4kjI8C0XNTs8la+bz5riSM4TokYOv0HGbL/r0OmHraodCxuX1rpkcYX+FD1dwcGC70eEB6NIu4= From c905e05a80fea6cd06189522d3f760cffd6c769d Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 21 Jul 2021 15:27:34 -0700 Subject: [PATCH 361/462] Remove newsletter badge --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index de23ef2f6..8b089aeb3 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![Travis Badge](https://travis-ci.com/sendgrid/sendgrid-python.svg?branch=main)](https://travis-ci.com/sendgrid/sendgrid-python) [![codecov](https://img.shields.io/codecov/c/github/sendgrid/sendgrid-python/main.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) [![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) From 25c3de49aea06a02b66ab2645583f52bc3c2881a Mon Sep 17 00:00:00 2001 From: Jennifer Mah Date: Thu, 29 Jul 2021 14:29:36 -0700 Subject: [PATCH 362/462] chore: remove docker credentials for PRs --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 02c163356..2341f8631 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,9 @@ before_script: - chmod +x ./cc-test-reporter - "./cc-test-reporter before-build" script: -- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin +- if [[ "$TRAVIS_BRANCH" == "main" || "$TRAVIS_BRANCH" == "travis" ]] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then + echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin + fi - make test-docker after_script: - make test-install From c99e8ea481bdc2c036f8fe9971c732ef3cb598a2 Mon Sep 17 00:00:00 2001 From: Jennifer Mah Date: Thu, 29 Jul 2021 14:33:09 -0700 Subject: [PATCH 363/462] chore: revert removal of docker credentials for PRs --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2341f8631..02c163356 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,9 +18,7 @@ before_script: - chmod +x ./cc-test-reporter - "./cc-test-reporter before-build" script: -- if [[ "$TRAVIS_BRANCH" == "main" || "$TRAVIS_BRANCH" == "travis" ]] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then - echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin - fi +- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin - make test-docker after_script: - make test-install From 4047acd5e131b3942308f7c09fb5577b9bf6aa15 Mon Sep 17 00:00:00 2001 From: Jennifer Mah Date: Thu, 29 Jul 2021 14:46:56 -0700 Subject: [PATCH 364/462] chore: remove docker credentials for PRs --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 02c163356..76606df83 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,9 @@ before_script: - chmod +x ./cc-test-reporter - "./cc-test-reporter before-build" script: -- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin +- if [[ "$TRAVIS_BRANCH" == "main" || "$TRAVIS_BRANCH" == "travis" ]] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then + echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin; + fi - make test-docker after_script: - make test-install From 272274ae26943bf0d7433843dab914ab9fcb4993 Mon Sep 17 00:00:00 2001 From: vindarel Date: Fri, 30 Jul 2021 17:11:45 +0200 Subject: [PATCH 365/462] feat: add reply_to to helpers.Mail (#999) * Add reply_to to helpers.Mail Otherwise, we must create the Mail and then set the reply_to thanks to its setter. (and we have to dig the code to find out). * fix: tests: add missing import Co-authored-by: Jennifer Mah <42650198+JenniferMah@users.noreply.github.com> --- sendgrid/helpers/mail/mail.py | 7 +++++++ test/test_mail_helpers.py | 7 ++++++- use_cases/send_a_single_email_to_a_single_recipient.md | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index ba21f7891..ba04a2b5b 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -24,6 +24,7 @@ def __init__( self, from_email=None, to_emails=None, + reply_to=None, subject=None, plain_text_content=None, html_content=None, @@ -40,6 +41,8 @@ def __init__( :param to_emails: The email address of the recipient :type to_emails: To, str, tuple, list(str), list(tuple), list(To), optional + :param reply_to: The email address to reply to + :type reply_to: ReplyTo, 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 @@ -79,6 +82,10 @@ def __init__( if html_content is not None: self.add_content(html_content, MimeType.html) + # Optional + if reply_to is not None: + self.reply_to = reply_to + def __str__(self): """A JSON-ready string representation of this Mail object. diff --git a/test/test_mail_helpers.py b/test/test_mail_helpers.py index 57a4ba880..49c58d05b 100644 --- a/test/test_mail_helpers.py +++ b/test/test_mail_helpers.py @@ -94,12 +94,13 @@ def test_batch_id(self): # 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, + from sendgrid.helpers.mail import (Mail, From, To, ReplyTo, 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'), + reply_to=ReplyTo('test+reply_to@example.com', 'Example Reply To Name'), subject=Subject('Sending with SendGrid is Fun'), plain_text_content=PlainTextContent( 'and easy to do anywhere, even with Python'), @@ -123,6 +124,10 @@ def test_single_email_to_a_single_recipient(self): "email": "test+from@example.com", "name": "Example From Name" }, + "reply_to": { + "email": "test+reply_to@example.com", + "name": "Example Reply To Name" + }, "personalizations": [ { "to": [ 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 index 8a2364285..c469c3bd6 100644 --- a/use_cases/send_a_single_email_to_a_single_recipient.md +++ b/use_cases/send_a_single_email_to_a_single_recipient.md @@ -6,6 +6,7 @@ from sendgrid.helpers.mail import Mail message = Mail( from_email='from_email@example.com', to_emails='to@example.com', + reply_to='reply_to@example.com', subject='Sending with Twilio SendGrid is Fun', html_content='and easy to do anywhere, even with Python') try: From c0da90440f25c82648b0fda0d9e62f02ec8a55e8 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 11 Aug 2021 16:45:38 +0000 Subject: [PATCH 366/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90674eee0..ad87a42d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2021-08-11] Version 6.8.0 +-------------------------- +**Library - Feature** +- [PR #999](https://github.com/sendgrid/sendgrid-python/pull/999): add reply_to to helpers.Mail. Thanks to [@vindarel](https://github.com/vindarel)! + + [2021-06-16] Version 6.7.1 -------------------------- **Library - Chore** From 08f0670aa2a5d05cfe981e3584dcd491d469b26d Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 11 Aug 2021 16:52:36 +0000 Subject: [PATCH 367/462] Release 6.8.0 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 157048dac..ff52e2e6c 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.7.1' +__version__ = '6.8.0' From 62d43ed0b27c8f7eec6dad8fe3880b81be7b1d2c Mon Sep 17 00:00:00 2001 From: Shwetha Radhakrishna Date: Wed, 18 Aug 2021 12:57:37 -0500 Subject: [PATCH 368/462] chore: revert reply_to prop add in mail (#1003) Co-authored-by: Shwetha Radhakrishna --- sendgrid/helpers/mail/mail.py | 7 ------- test/test_mail_helpers.py | 9 ++------- use_cases/send_a_single_email_to_a_single_recipient.md | 1 - 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index ba04a2b5b..ba21f7891 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -24,7 +24,6 @@ def __init__( self, from_email=None, to_emails=None, - reply_to=None, subject=None, plain_text_content=None, html_content=None, @@ -41,8 +40,6 @@ def __init__( :param to_emails: The email address of the recipient :type to_emails: To, str, tuple, list(str), list(tuple), list(To), optional - :param reply_to: The email address to reply to - :type reply_to: ReplyTo, 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 @@ -82,10 +79,6 @@ def __init__( if html_content is not None: self.add_content(html_content, MimeType.html) - # Optional - if reply_to is not None: - self.reply_to = reply_to - def __str__(self): """A JSON-ready string representation of this Mail object. diff --git a/test/test_mail_helpers.py b/test/test_mail_helpers.py index 49c58d05b..1598c607b 100644 --- a/test/test_mail_helpers.py +++ b/test/test_mail_helpers.py @@ -94,13 +94,12 @@ def test_batch_id(self): # 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, ReplyTo, Subject, + 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'), - reply_to=ReplyTo('test+reply_to@example.com', 'Example Reply To Name'), subject=Subject('Sending with SendGrid is Fun'), plain_text_content=PlainTextContent( 'and easy to do anywhere, even with Python'), @@ -124,10 +123,6 @@ def test_single_email_to_a_single_recipient(self): "email": "test+from@example.com", "name": "Example From Name" }, - "reply_to": { - "email": "test+reply_to@example.com", - "name": "Example Reply To Name" - }, "personalizations": [ { "to": [ @@ -660,7 +655,7 @@ def test_personalization_add_email_filters_out_duplicate_to_emails(self): p.add_email(to_email) self.assertEqual([to_email.get()], p.tos) - + def test_personalization_add_email_filters_out_duplicate_to_emails_ignoring_case(self): self.maxDiff = None 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 index c469c3bd6..8a2364285 100644 --- a/use_cases/send_a_single_email_to_a_single_recipient.md +++ b/use_cases/send_a_single_email_to_a_single_recipient.md @@ -6,7 +6,6 @@ from sendgrid.helpers.mail import Mail message = Mail( from_email='from_email@example.com', to_emails='to@example.com', - reply_to='reply_to@example.com', subject='Sending with Twilio SendGrid is Fun', html_content='and easy to do anywhere, even with Python') try: From 395f86d2feb04a00d510b4107e543edaf0506d80 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 25 Aug 2021 18:43:36 +0000 Subject: [PATCH 369/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad87a42d9..7ae148d6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2021-08-25] Version 6.8.1 +-------------------------- +**Library - Chore** +- [PR #1003](https://github.com/sendgrid/sendgrid-python/pull/1003): get rid of reply_to in mail helper. Thanks to [@shwetha-manvinkurke](https://github.com/shwetha-manvinkurke)! + + [2021-08-11] Version 6.8.0 -------------------------- **Library - Feature** From e40816321149fa360de297eab49979c3e522c825 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 25 Aug 2021 18:48:28 +0000 Subject: [PATCH 370/462] Release 6.8.1 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index ff52e2e6c..c5a38a4f5 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.8.0' +__version__ = '6.8.1' From a9a80675937ff3b9d8d9719e95a0b87cb42929ae Mon Sep 17 00:00:00 2001 From: Shwetha Radhakrishna Date: Fri, 10 Sep 2021 12:05:00 -0500 Subject: [PATCH 371/462] chore: add tests for v3.9 (#1007) --- .travis.yml | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 76606df83..46f3601b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ env: - version=3.6 - version=3.7 - version=3.8 + - version=3.9 global: - CC_TEST_REPORTER_ID=$TRAVIS_CODE_CLIMATE_TOKEN before_script: diff --git a/README.md b/README.md index 8b089aeb3..8ce206abb 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ We appreciate your continued support, thank you! ## Prerequisites -- Python version 2.7, 3.5, 3.6, 3.7, or 3.8 +- Python version 2.7+ - The SendGrid service, starting at the [free level](https://sendgrid.com/free?source=sendgrid-python) ## Setup Environment Variables From 14a07cd85a51d4c26d9fe828bb829dca5e2ad401 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 22 Sep 2021 21:05:13 +0000 Subject: [PATCH 372/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ae148d6d..f4712dc13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2021-09-22] Version 6.8.2 +-------------------------- +**Library - Chore** +- [PR #1007](https://github.com/sendgrid/sendgrid-python/pull/1007): test against v3.9. Thanks to [@shwetha-manvinkurke](https://github.com/shwetha-manvinkurke)! + + [2021-08-25] Version 6.8.1 -------------------------- **Library - Chore** From c22b7f6ca97a750ac68042d706ffd34af6fde429 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 22 Sep 2021 21:13:09 +0000 Subject: [PATCH 373/462] Release 6.8.2 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index c5a38a4f5..94859745c 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.8.1' +__version__ = '6.8.2' From 0d00f0dfe98a8c442c66f891f6fca18234418606 Mon Sep 17 00:00:00 2001 From: Shwetha Radhakrishna Date: Fri, 8 Oct 2021 06:51:53 -0700 Subject: [PATCH 374/462] docs: improve signed event webhook validation docs (#1013) --- TROUBLESHOOTING.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index b9a68b8c0..0a7f54c69 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -15,6 +15,7 @@ If you can't find a solution below, please open an [issue](https://github.com/se * [Version Convention](#versions) * [Viewing the Request Body](#request-body) * [Error Handling](#error-handling) +* [Verifying Event Webhooks](#signed-webhooks) ## Environment Variables and Your Twilio SendGrid API Key @@ -117,3 +118,14 @@ You can do this right before you call `response = sg.client.mail.send.post(reque # Error Handling Please review [our use_cases](use_cases/README.md) for examples of error handling. + + +## Signed Webhook Verification + +Twilio SendGrid's Event Webhook will notify a URL via HTTP POST with information about events that occur as your mail is processed. [This](https://docs.sendgrid.com/for-developers/tracking-events/getting-started-event-webhook-security-features) article covers all you need to know to secure the Event Webhook, allowing you to verify that incoming requests originate from Twilio SendGrid. The sendgrid-python library can help you verify these Signed Event Webhooks. + +You can find the usage example [here](examples/helpers/eventwebhook/eventwebhook_example.py) and the tests [here](test/test_eventwebhook.py). +If you are still having trouble getting the validation to work, follow the following instructions: +- Be sure to use the *raw* payload for validation +- Be sure to include a trailing carriage return and newline in your payload +- In case of multi-event webhooks, make sure you include the trailing newline and carriage return after *each* event From 77ae5f5c7ae69a49d9814d96ccc418a31c3271cd Mon Sep 17 00:00:00 2001 From: Elise Shanholtz Date: Mon, 11 Oct 2021 11:31:14 -0700 Subject: [PATCH 375/462] chore: pin starkbank-ecdsa version (#1015) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ff1ba3c35..37e68e9d9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,4 @@ PyYAML>=4.2b1 python-http-client>=3.2.1 six==1.11.0 pytest==3.8.2 -starkbank-ecdsa>=1.0.0 +starkbank-ecdsa>=1.0.0,<2.0.0 From 01b72926140af023bfa2546d09dbb3b9be126a90 Mon Sep 17 00:00:00 2001 From: Elise Shanholtz Date: Mon, 11 Oct 2021 13:41:24 -0700 Subject: [PATCH 376/462] chore: pin starkbank-ecdsa version (#1016) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b3eeec82d..8ec7329c8 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ def getRequires(): deps = [ 'python_http_client>=3.2.1', - 'starkbank-ecdsa>=1.0.0' + 'starkbank-ecdsa>=1.0.0,<2.0.0' ] return deps From 3fdf07930123f494088ab276441d538ac11ef9fa Mon Sep 17 00:00:00 2001 From: Twilio Date: Mon, 18 Oct 2021 18:31:15 +0000 Subject: [PATCH 377/462] [Librarian] Version Bump --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4712dc13..53d21c256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ # Change Log All notable changes to this project will be documented in this file. +[2021-10-18] Version 6.8.3 +-------------------------- +**Library - Chore** +- [PR #1016](https://github.com/sendgrid/sendgrid-python/pull/1016): pin starkbank-ecdsa version. Thanks to [@eshanholtz](https://github.com/eshanholtz)! +- [PR #1015](https://github.com/sendgrid/sendgrid-python/pull/1015): pin starkbank-ecdsa version. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + +**Library - Docs** +- [PR #1013](https://github.com/sendgrid/sendgrid-python/pull/1013): improve signed event webhook validation docs. Thanks to [@shwetha-manvinkurke](https://github.com/shwetha-manvinkurke)! + + [2021-09-22] Version 6.8.2 -------------------------- **Library - Chore** From 2e12f1cc86f7fc59d1fd0cea0030d0213aee493f Mon Sep 17 00:00:00 2001 From: Twilio Date: Mon, 18 Oct 2021 18:35:05 +0000 Subject: [PATCH 378/462] Release 6.8.3 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 94859745c..b45da21b6 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.8.2' +__version__ = '6.8.3' From a97e83a15dffe41cd87f52316fe04502314aec39 Mon Sep 17 00:00:00 2001 From: Bilal Boussayoud Date: Mon, 25 Oct 2021 15:07:33 -0600 Subject: [PATCH 379/462] feat: allow personalization of the From name and email for each recipient (#1020) * feat: allow personalization of the From name and email for each recipient --- examples/helpers/README.md | 12 ++-- examples/helpers/mail_example.py | 65 ++++++++++++++++--- sendgrid/helpers/mail/personalization.py | 21 +++++- test/test_mail_helpers.py | 9 +++ use_cases/README.md | 1 + .../send_multiple_emails_personalizations.md | 32 +++++++++ 6 files changed, 126 insertions(+), 14 deletions(-) create mode 100644 use_cases/send_multiple_emails_personalizations.md diff --git a/examples/helpers/README.md b/examples/helpers/README.md index df1746b52..8d7594d44 100644 --- a/examples/helpers/README.md +++ b/examples/helpers/README.md @@ -28,12 +28,16 @@ For more information on parameters and usage, see [here](../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. +The personalization helper can be used to create personalizations and customize various aspects of an email. See example [here](mail_example.py) in `build_multiple_emails_personalized()`, and refer [here](https://docs.sendgrid.com/for-developers/sending-email/personalizations) for more documentation. ``` mock_personalization = Personalization() - for to_addr in personalization['to_list']: - mock_personalization.add_to(to_addr) + + for to_addr in personalization['to_list']: + mock_personalization.add_to(to_addr) + + mock_personalization.set_from(from_addr) + mock_personalization.add_cc(cc_addr) + # etc... ``` ### Creating Attachments diff --git a/examples/helpers/mail_example.py b/examples/helpers/mail_example.py index 700970110..384181501 100644 --- a/examples/helpers/mail_example.py +++ b/examples/helpers/mail_example.py @@ -13,7 +13,7 @@ def build_hello_email(): from sendgrid import SendGridAPIClient from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException - message = Mail(from_email=From('from@example.com.com', 'Example From Name'), + message = Mail(from_email=From('from@example.com', 'Example From Name'), to_emails=To('to@example.com', 'Example To Name'), subject=Subject('Sending with SendGrid is Fun'), plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), @@ -26,25 +26,30 @@ def build_hello_email(): except SendGridException as e: print(e.message) - for cc_addr in personalization['cc_list']: + mock_personalization = Personalization() + personalization_dict = get_mock_personalization_dict() + + for cc_addr in personalization_dict['cc_list']: mock_personalization.add_to(cc_addr) - for bcc_addr in personalization['bcc_list']: + for bcc_addr in personalization_dict['bcc_list']: mock_personalization.add_bcc(bcc_addr) - for header in personalization['headers']: + for header in personalization_dict['headers']: mock_personalization.add_header(header) - for substitution in personalization['substitutions']: + for substitution in personalization_dict['substitutions']: mock_personalization.add_substitution(substitution) - for arg in personalization['custom_args']: + for arg in personalization_dict['custom_args']: mock_personalization.add_custom_arg(arg) - mock_personalization.subject = personalization['subject'] - mock_personalization.send_at = personalization['send_at'] - return mock_personalization + mock_personalization.subject = personalization_dict['subject'] + mock_personalization.send_at = personalization_dict['send_at'] + + message.add_personalization(mock_personalization) + return message def get_mock_personalization_dict(): """Get a dict of personalization mock.""" @@ -78,6 +83,36 @@ def get_mock_personalization_dict(): mock_pers['send_at'] = 1443636843 return mock_pers +def build_multiple_emails_personalized(): + import json + from sendgrid.helpers.mail import Mail, From, To, Cc, Bcc, Subject, PlainTextContent, \ + HtmlContent, SendGridException, Personalization + + # Note that the domain for all From email addresses must match + message = Mail(from_email=From('from@example.com', 'Example From Name'), + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), + html_content=HtmlContent('and easy to do anywhere, even with Python')) + + mock_personalization = Personalization() + mock_personalization.add_to(To('test@example.com', 'Example User 1')) + mock_personalization.add_cc(Cc('test1@example.com', 'Example User 2')) + message.add_personalization(mock_personalization) + + mock_personalization_2 = Personalization() + mock_personalization_2.add_to(To('test2@example.com', 'Example User 3')) + mock_personalization_2.set_from(From('from@example.com', 'Example From Name 2')) + mock_personalization_2.add_bcc(Bcc('test3@example.com', 'Example User 4')) + message.add_personalization(mock_personalization_2) + + try: + print(json.dumps(message.get(), sort_keys=True, indent=4)) + return message.get() + + except SendGridException as e: + print(e.message) + + return message def build_attachment1(): """Build attachment mock. Make sure your content is base64 encoded before passing into attachment.content. @@ -308,6 +343,15 @@ def build_kitchen_sink(): return message +def send_multiple_emails_personalized(): + # Assumes you set your environment variable: + # https://github.com/sendgrid/sendgrid-python/blob/HEAD/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key + message = build_multiple_emails_personalized() + sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message=message) + print(response.status_code) + print(response.body) + print(response.headers) def send_hello_email(): # Assumes you set your environment variable: @@ -334,5 +378,8 @@ def send_kitchen_sink(): ## this will actually send an email # send_hello_email() +## this will send multiple emails +# send_multiple_emails_personalized() + ## this will only send an email if you set SandBox Mode to False # send_kitchen_sink() diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py index 21a31c863..a4e1c1de4 100644 --- a/sendgrid/helpers/mail/personalization.py +++ b/sendgrid/helpers/mail/personalization.py @@ -6,6 +6,7 @@ class Personalization(object): def __init__(self): """Create an empty Personalization and initialize member variables.""" self._tos = [] + self._from_email = None self._ccs = [] self._bccs = [] self._subject = None @@ -26,7 +27,10 @@ def add_email(self, email): if email_type.__name__ == 'Bcc': self.add_bcc(email) return - raise ValueError('Please use a To, Cc or Bcc object.') + if email_type.__name__ == 'From': + self.from_email = email + return + raise ValueError('Please use a To, From, Cc or Bcc object.') def _get_unique_recipients(self, recipients): unique_recipients = [] @@ -77,6 +81,17 @@ def add_to(self, email): self._tos.append(email.get()) + @property + def from_email(self): + return self._from_email + + @from_email.setter + def from_email(self, value): + self._from_email = value + + def set_from(self, email): + self._from_email = email.get() + @property def ccs(self): """A list of recipients who will receive copies of this email. @@ -236,6 +251,10 @@ def get(self): if value: personalization[key[:-1]] = value + from_value = getattr(self, 'from_email') + if from_value: + personalization['from'] = from_value + for key in ['subject', 'send_at', 'dynamic_template_data']: value = getattr(self, key) if value: diff --git a/test/test_mail_helpers.py b/test/test_mail_helpers.py index 1598c607b..a7d08d890 100644 --- a/test/test_mail_helpers.py +++ b/test/test_mail_helpers.py @@ -667,6 +667,15 @@ def test_personalization_add_email_filters_out_duplicate_to_emails_ignoring_case self.assertEqual([to_email.get()], p.tos) + def test_personalization_set_from_email(self): + self.maxDiff = None + + p = Personalization() + from_email = From('test+from@example.com', 'Example From') + p.set_from(from_email) + + self.assertEqual(from_email.get(), p.from_email) + def test_personalization_filters_out_duplicate_cc_emails(self): self.maxDiff = None diff --git a/use_cases/README.md b/use_cases/README.md index a91f1f5a4..f9fe2470e 100644 --- a/use_cases/README.md +++ b/use_cases/README.md @@ -8,6 +8,7 @@ This directory provides examples for specific use cases of this library. Please * [Send a Single Email to a Single Recipient](send_a_single_email_to_a_single_recipient.md) * [Send a Single Email to Multiple Recipients](send_a_single_email_to_multiple_recipients.md) * [Send Multiple Emails to Multiple Recipients](send_multiple_emails_to_multiple_recipients.md) +* [Send Multiple Emails with Personalizations](send_multiple_emails_personalizations.md) * [Kitchen Sink - an example with all settings used](kitchen_sink.md) * [Transactional Templates](transactional_templates.md) * [Attachments](attachment.md) diff --git a/use_cases/send_multiple_emails_personalizations.md b/use_cases/send_multiple_emails_personalizations.md new file mode 100644 index 000000000..e8a7e2eec --- /dev/null +++ b/use_cases/send_multiple_emails_personalizations.md @@ -0,0 +1,32 @@ +```python +import os +import json +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail, Personalization, From, To, Cc, Bcc + +# Note that the domain for all From email addresses must match +message = Mail( + from_email=('from@example.com', 'Example From Name'), + subject='Sending with Twilio SendGrid is Fun', + html_content='and easy to do anywhere, even with Python') + +personalization1 = Personalization() +personalization1.add_email(To('test0@example.com', 'Example Name 0')) +personalization1.add_email(Cc('test1@example.com', 'Example Name 1')) +message.add_personalization(personalization1) + +personalization2 = Personalization() +personalization2.add_email(To('test2@example.com', 'Example Name 2')) +personalization2.add_email(Bcc('test3@example.com', 'Example Name 3')) +personalization2.add_email(From('from2@example.com', 'Example From Name 2')) +message.add_personalization(personalization2) + +try: + sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message) + print(response.status_code) + print(response.body) + print(response.headers) +except Exception as e: + print(e.message) +``` \ No newline at end of file From 5e9b0ff061a3cb888f7089473b7039432b748f37 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 3 Nov 2021 18:51:55 +0000 Subject: [PATCH 380/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53d21c256..8004be4db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2021-11-03] Version 6.9.0 +-------------------------- +**Library - Feature** +- [PR #1020](https://github.com/sendgrid/sendgrid-python/pull/1020): allow personalization of the From name and email for each recipient. Thanks to [@beebzz](https://github.com/beebzz)! + + [2021-10-18] Version 6.8.3 -------------------------- **Library - Chore** From ab040cb25696a900224789cb2f97bbf5193fd307 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 3 Nov 2021 18:55:47 +0000 Subject: [PATCH 381/462] Release 6.9.0 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index b45da21b6..a51875d3a 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.8.3' +__version__ = '6.9.0' From e27a2b1b7c1102710f10fe6674e6b93c64431666 Mon Sep 17 00:00:00 2001 From: hellno Date: Tue, 9 Nov 2021 23:59:21 +0100 Subject: [PATCH 382/462] chore: fix vulnerability in starbank-ecdsa dependency (#1022) Co-authored-by: Jennifer Mah --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 37e68e9d9..04c3b87e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,4 @@ PyYAML>=4.2b1 python-http-client>=3.2.1 six==1.11.0 pytest==3.8.2 -starkbank-ecdsa>=1.0.0,<2.0.0 +starkbank-ecdsa>=2.0.1 diff --git a/setup.py b/setup.py index 8ec7329c8..365ebb50b 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ def getRequires(): deps = [ 'python_http_client>=3.2.1', - 'starkbank-ecdsa>=1.0.0,<2.0.0' + 'starkbank-ecdsa>=2.0.1' ] return deps From 05557b665dd6edfa41e1290c36dfa9fd76be2762 Mon Sep 17 00:00:00 2001 From: Jennifer Mah Date: Tue, 9 Nov 2021 16:32:32 -0800 Subject: [PATCH 383/462] fix: fix event webhook for updated starbank-ecdsa dependency --- sendgrid/helpers/eventwebhook/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/helpers/eventwebhook/__init__.py b/sendgrid/helpers/eventwebhook/__init__.py index a44eb5b89..c1ec7d1c8 100644 --- a/sendgrid/helpers/eventwebhook/__init__.py +++ b/sendgrid/helpers/eventwebhook/__init__.py @@ -27,7 +27,7 @@ def convert_public_key_to_ecdsa(self, public_key): :return: public key using the ECDSA algorithm :rtype PublicKey """ - return PublicKey.fromPem(public_key) + return PublicKey.fromPem('\n-----BEGIN PUBLIC KEY-----\n'+public_key+'\n-----END PUBLIC KEY-----\n') def verify_signature(self, payload, signature, timestamp, public_key=None): """ From a3cbe32bd270db4fc2d81b92f73baf6ddab113a7 Mon Sep 17 00:00:00 2001 From: Jennifer Mah <42650198+JenniferMah@users.noreply.github.com> Date: Tue, 9 Nov 2021 17:25:30 -0800 Subject: [PATCH 384/462] Chore: pin more-itertools version --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 04c3b87e8..b2ba4d1be 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ python-http-client>=3.2.1 six==1.11.0 pytest==3.8.2 starkbank-ecdsa>=2.0.1 +more-itertools==5.0.0 From f5d6a3837d67d4ed479d4509c14822ad95730291 Mon Sep 17 00:00:00 2001 From: Jennifer Mah <42650198+JenniferMah@users.noreply.github.com> Date: Mon, 15 Nov 2021 16:58:16 -0800 Subject: [PATCH 385/462] docs: fix event webhook documentation --- use_cases/email_stats.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/use_cases/email_stats.md b/use_cases/email_stats.md index c40ccb882..10e265721 100644 --- a/use_cases/email_stats.md +++ b/use_cases/email_stats.md @@ -2,4 +2,4 @@ You can find documentation for how to view your email statistics via the UI [here](https://app.sendgrid.com/statistics) and via API [here](../USAGE.md#stats). -Alternatively, we can post events to a URL of your choice via our [Event Webhook](https://sendgrid.com/docs/API_Reference/Webhooks/event.html) about events that occur as Twilio SendGrid processes your email. \ No newline at end of file +Alternatively, we can post events to a URL of your choice via our [Event Webhook](https://docs.sendgrid.com/for-developers/tracking-events/event) about events that occur as Twilio SendGrid processes your email. From 26009631c6490b2e38f6daf7febe3fe7182699f6 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 17 Nov 2021 19:24:12 +0000 Subject: [PATCH 386/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8004be4db..35318143e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2021-11-17] Version 6.9.1 +-------------------------- +**Library - Chore** +- [PR #1022](https://github.com/sendgrid/sendgrid-python/pull/1022): fix vulnerability in starbank-ecdsa dependency. Thanks to [@hellno](https://github.com/hellno)! + + [2021-11-03] Version 6.9.0 -------------------------- **Library - Feature** From 1565945d8f9025946ac8a79821ab07444db68e41 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 17 Nov 2021 19:28:16 +0000 Subject: [PATCH 387/462] Release 6.9.1 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index a51875d3a..ae7f14541 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.9.0' +__version__ = '6.9.1' From 744ca8cf1d7c0ae8fe29c86f18ad89718c99b830 Mon Sep 17 00:00:00 2001 From: Jennifer Mah <42650198+JenniferMah@users.noreply.github.com> Date: Wed, 1 Dec 2021 10:36:41 -0800 Subject: [PATCH 388/462] chore: migrate to GitHub Actions (#1027) --- .codeclimate.yml | 3 --- .github/workflows/release.yml | 48 +++++++++++++++++++++++++++++++++ .github/workflows/tests.yml | 50 +++++++++++++++++++++++++++++++++++ .travis.yml | 46 -------------------------------- Makefile | 1 + README.md | 3 +-- README.rst | 6 ++--- requirements.txt | 2 +- test/test_project.py | 8 ------ test/test_sendgrid.py | 4 +-- 10 files changed, 106 insertions(+), 65 deletions(-) delete mode 100644 .codeclimate.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/tests.yml delete mode 100644 .travis.yml diff --git a/.codeclimate.yml b/.codeclimate.yml deleted file mode 100644 index ef7e1cb56..000000000 --- a/.codeclimate.yml +++ /dev/null @@ -1,3 +0,0 @@ -plugins: - pep8: - enabled: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..46df6809d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,48 @@ +name: Publish Python distributions +on: + push: + tags: + - '*' + workflow_dispatch: + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout sendgrid-python + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.6' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_TOKEN }} + + notify-on-failure: + name: Slack notify on failure + if: ${{ failure() }} + needs: [ release ] + runs-on: ubuntu-latest + steps: + - uses: rtCamp/action-slack-notify@v2 + env: + SLACK_COLOR: 'danger' + SLACK_ICON_EMOJI: ':github:' + SLACK_MESSAGE: ${{ format('Failed to release {1}{3} {0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id, ':') }} + SLACK_TITLE: Release Failure + SLACK_USERNAME: GitHub Actions + SLACK_MSG_AUTHOR: twilio-dx + SLACK_FOOTER: Posted automatically using GitHub Actions + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + MSG_MINIMAL: true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..fe2015a6e --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,50 @@ +name: Run Tests +on: + push: + branches: [ '*' ] + pull_request: + branches: [ main ] + schedule: + # Run automatically at 8AM PST Monday-Friday + - cron: '0 15 * * 1-5' + workflow_dispatch: + +jobs: + tests: + name: Run Tests + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + matrix: + python-version: [ '2.7', '3.5', '3.6', '3.7', '3.8', '3.9' ] + steps: + - name: Checkout sendgrid-python + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Build & Test + run: make test-docker test-install + + notify-on-failure: + name: Slack notify on failure + if: ${{ failure() && github.ref == 'refs/heads/main' && github.event_name != 'pull_request' }} + needs: [ tests ] + runs-on: ubuntu-latest + steps: + - uses: rtCamp/action-slack-notify@v2 + env: + SLACK_COLOR: 'danger' + SLACK_ICON_EMOJI: ':github:' + SLACK_MESSAGE: ${{ format('Failed running build on {1}{3} {0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id, ':') }} + SLACK_TITLE: Build Failure + SLACK_USERNAME: GitHub Actions + SLACK_MSG_AUTHOR: twilio-dx + SLACK_FOOTER: Posted automatically using GitHub Actions + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + MSG_MINIMAL: true diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 46f3601b3..000000000 --- a/.travis.yml +++ /dev/null @@ -1,46 +0,0 @@ -dist: xenial -language: python -cache: pip -services: -- docker -env: - matrix: - - version=2.7 - - version=3.5 - - version=3.6 - - version=3.7 - - version=3.8 - - version=3.9 - global: - - CC_TEST_REPORTER_ID=$TRAVIS_CODE_CLIMATE_TOKEN -before_script: -- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 - > ./cc-test-reporter -- chmod +x ./cc-test-reporter -- "./cc-test-reporter before-build" -script: -- if [[ "$TRAVIS_BRANCH" == "main" || "$TRAVIS_BRANCH" == "travis" ]] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then - echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin; - fi -- make test-docker -after_script: -- make test-install -- ". venv/bin/activate; codecov" -- "./cc-test-reporter after-build --exit-code $?" -deploy: - provider: pypi - user: __token__ - password: "$PYPI_TOKEN" - skip_cleanup: true - distributions: sdist bdist_wheel - on: - tags: true - condition: "$version = 3.6" -notifications: - slack: - if: branch = main - on_pull_requests: false - on_success: never - on_failure: change - rooms: - secure: B0SJHc9Syyf5HOl21abg/Uj/Gp7EusCOly/2JZzUUHCWtxC8C9pWfGf2e674R4vdeJ3FmTKz/1jJZ96vzV0z+XUpT2Fnn6URi4kjI8C0XNTs8la+bz5riSM4TokYOv0HGbL/r0OmHraodCxuX1rpkcYX+FD1dwcGC70eEB6NIu4= diff --git a/Makefile b/Makefile index 620a25993..bb22db4df 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ venv: clean @python --version || (echo "Python is not installed, please install Python 2 or Python 3"; exit 1); + pip install virtualenv virtualenv --python=python venv install: venv diff --git a/README.md b/README.md index 8ce206abb..e2208293d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ ![SendGrid Logo](twilio_sendgrid_logo.png) -[![Travis Badge](https://travis-ci.com/sendgrid/sendgrid-python.svg?branch=main)](https://travis-ci.com/sendgrid/sendgrid-python) -[![codecov](https://img.shields.io/codecov/c/github/sendgrid/sendgrid-python/main.svg?style=flat-square&label=Codecov+Coverage)](https://codecov.io/gh/sendgrid/sendgrid-python) +[![Tests](https://github.com/sendgrid/sendgrid-python/actions/workflows/tests.yml/badge.svg)](https://github.com/sendgrid/sendgrid-python/actions/workflows/tests.yml) [![Docker Badge](https://img.shields.io/docker/automated/sendgrid/sendgrid-python.svg)](https://hub.docker.com/r/sendgrid/sendgrid-python/) [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Twitter Follow](https://img.shields.io/twitter/follow/sendgrid.svg?style=social&label=Follow)](https://twitter.com/sendgrid) diff --git a/README.rst b/README.rst index a7a076c22..59f693f0f 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ -|Travis Badge| |codecov| |Python Versions| |PyPI Version| |Docker Badge| |Email Notifications Badge| |MIT licensed| |Twitter Follow| |GitHub contributors| |Open Source Helpers| +|Tests Badge| |codecov| |Python Versions| |PyPI Version| |Docker Badge| |Email Notifications Badge| |MIT licensed| |Twitter Follow| |GitHub contributors| |Open Source Helpers| **This library allows you to quickly and easily use the Twilio SendGrid Web API v3 via Python.** @@ -288,8 +288,8 @@ License .. _troubleshooting guide: https://github.com/sendgrid/sendgrid-python/blob/HEAD/TROUBLESHOOTING.md .. _The MIT License (MIT): https://github.com/sendgrid/sendgrid-python/blob/HEAD/LICENSE -.. |Travis Badge| image:: https://travis-ci.com/sendgrid/sendgrid-python.svg?branch=main - :target: https://travis-ci.com/sendgrid/sendgrid-python +.. |Tests Badge| image:: https://github.com/sendgrid/sendgrid-python/actions/workflows/test.yml/badge.svg + :target: https://github.com/sendgrid/sendgrid-python/actions/workflows/test.yml .. |Python Versions| image:: https://img.shields.io/pypi/pyversions/sendgrid.svg :target: https://pypi.org/project/sendgrid/ .. |PyPI Version| image:: https://img.shields.io/pypi/v/sendgrid.svg diff --git a/requirements.txt b/requirements.txt index b2ba4d1be..f4b4ba105 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Flask==1.0.2 +Flask==1.1.2 PyYAML>=4.2b1 python-http-client>=3.2.1 six==1.11.0 diff --git a/test/test_project.py b/test/test_project.py index e30049a3f..c78293dff 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -11,14 +11,6 @@ def test_env(self): def test_gitignore(self): self.assertTrue(os.path.isfile('./.gitignore')) - # ./.travis.yml - def test_travis(self): - self.assertTrue(os.path.isfile('./.travis.yml')) - - # ./.codeclimate.yml - def test_codeclimate(self): - self.assertTrue(os.path.isfile('./.codeclimate.yml')) - # ./CHANGELOG.md def test_changelog(self): self.assertTrue(os.path.isfile('./CHANGELOG.md')) diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index f6177a7d5..0c63851eb 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -1649,14 +1649,14 @@ def test_suppression_invalid_emails__email__delete(self): def test_suppression_spam_report__email__get(self): email = "test_url_param" headers = {'X-Mock': 200} - response = self.sg.client.suppression.spam_report._( + response = self.sg.client.suppression.spam_reports._( email).get(request_headers=headers) self.assertEqual(response.status_code, 200) def test_suppression_spam_report__email__delete(self): email = "test_url_param" headers = {'X-Mock': 204} - response = self.sg.client.suppression.spam_report._( + response = self.sg.client.suppression.spam_reports._( email).delete(request_headers=headers) self.assertEqual(response.status_code, 204) From 807b08bb6dff27e84e454f19f31b2a15a9aa3c9e Mon Sep 17 00:00:00 2001 From: Jennifer Mah Date: Wed, 1 Dec 2021 10:49:09 -0800 Subject: [PATCH 389/462] fix: add Python distribution to release GH action --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 46df6809d..2c59e9075 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,6 +22,7 @@ jobs: run: | python -m pip install --upgrade pip pip install build + python setup.py sdist bdist_wheel - name: Publish package to PyPI uses: pypa/gh-action-pypi-publish@release/v1 From cec16a4c8941bd778a3c2a432e058d0b7df53dc4 Mon Sep 17 00:00:00 2001 From: Jennifer Mah <42650198+JenniferMah@users.noreply.github.com> Date: Wed, 1 Dec 2021 11:21:17 -0800 Subject: [PATCH 390/462] chore: add wheel install for GH action release --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2c59e9075..65ef25a39 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,6 +22,7 @@ jobs: run: | python -m pip install --upgrade pip pip install build + pip install wheel python setup.py sdist bdist_wheel - name: Publish package to PyPI From e00c300aeb3d1031aca96463cab11b4f541c1642 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 1 Dec 2021 21:10:29 +0000 Subject: [PATCH 391/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35318143e..4f102be33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2021-12-01] Version 6.9.2 +-------------------------- +**Library - Chore** +- [PR #1027](https://github.com/sendgrid/sendgrid-python/pull/1027): migrate to GitHub Actions. Thanks to [@JenniferMah](https://github.com/JenniferMah)! + + [2021-11-17] Version 6.9.1 -------------------------- **Library - Chore** From ed0b3ff73dfcb3b8188199106b7730f7b1d69f16 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 1 Dec 2021 21:13:05 +0000 Subject: [PATCH 392/462] Release 6.9.2 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index ae7f14541..b8fc882cc 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.9.1' +__version__ = '6.9.2' From 1dcc378bb90ed0d043c9dcfd239eb4c0b5501a81 Mon Sep 17 00:00:00 2001 From: childish-sambino Date: Tue, 14 Dec 2021 09:02:41 -0600 Subject: [PATCH 393/462] test: split up unit and integ tests (#1029) --- Makefile | 3 ++- test/integ/__init__.py | 0 test/{ => integ}/test_sendgrid.py | 0 test/unit/__init__.py | 0 test/{ => unit}/test_app.py | 0 test/{ => unit}/test_config.py | 0 test/{ => unit}/test_email.py | 0 test/{ => unit}/test_eventwebhook.py | 0 test/{ => unit}/test_inbound_send.py | 0 test/{ => unit}/test_mail_helpers.py | 0 test/{ => unit}/test_parse.py | 0 test/{ => unit}/test_project.py | 0 test/{ => unit}/test_spam_check.py | 0 test/{ => unit}/test_stats.py | 0 test/{ => unit}/test_twilio_email.py | 0 test/{ => unit}/test_unassigned.py | 0 16 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 test/integ/__init__.py rename test/{ => integ}/test_sendgrid.py (100%) create mode 100644 test/unit/__init__.py rename test/{ => unit}/test_app.py (100%) rename test/{ => unit}/test_config.py (100%) rename test/{ => unit}/test_email.py (100%) rename test/{ => unit}/test_eventwebhook.py (100%) rename test/{ => unit}/test_inbound_send.py (100%) rename test/{ => unit}/test_mail_helpers.py (100%) rename test/{ => unit}/test_parse.py (100%) rename test/{ => unit}/test_project.py (100%) rename test/{ => unit}/test_spam_check.py (100%) rename test/{ => unit}/test_stats.py (100%) rename test/{ => unit}/test_twilio_email.py (100%) rename test/{ => unit}/test_unassigned.py (100%) diff --git a/Makefile b/Makefile index bb22db4df..fb61004ce 100644 --- a/Makefile +++ b/Makefile @@ -13,9 +13,10 @@ test-install: install . venv/bin/activate; pip install -r test/requirements.txt test: test-install + . venv/bin/activate; coverage run -m unittest discover -s test/unit test-integ: test - . venv/bin/activate; coverage run -m unittest discover + . venv/bin/activate; coverage run -m unittest discover -s test/integ version ?= latest test-docker: diff --git a/test/integ/__init__.py b/test/integ/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/test_sendgrid.py b/test/integ/test_sendgrid.py similarity index 100% rename from test/test_sendgrid.py rename to test/integ/test_sendgrid.py diff --git a/test/unit/__init__.py b/test/unit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/test_app.py b/test/unit/test_app.py similarity index 100% rename from test/test_app.py rename to test/unit/test_app.py diff --git a/test/test_config.py b/test/unit/test_config.py similarity index 100% rename from test/test_config.py rename to test/unit/test_config.py diff --git a/test/test_email.py b/test/unit/test_email.py similarity index 100% rename from test/test_email.py rename to test/unit/test_email.py diff --git a/test/test_eventwebhook.py b/test/unit/test_eventwebhook.py similarity index 100% rename from test/test_eventwebhook.py rename to test/unit/test_eventwebhook.py diff --git a/test/test_inbound_send.py b/test/unit/test_inbound_send.py similarity index 100% rename from test/test_inbound_send.py rename to test/unit/test_inbound_send.py diff --git a/test/test_mail_helpers.py b/test/unit/test_mail_helpers.py similarity index 100% rename from test/test_mail_helpers.py rename to test/unit/test_mail_helpers.py diff --git a/test/test_parse.py b/test/unit/test_parse.py similarity index 100% rename from test/test_parse.py rename to test/unit/test_parse.py diff --git a/test/test_project.py b/test/unit/test_project.py similarity index 100% rename from test/test_project.py rename to test/unit/test_project.py diff --git a/test/test_spam_check.py b/test/unit/test_spam_check.py similarity index 100% rename from test/test_spam_check.py rename to test/unit/test_spam_check.py diff --git a/test/test_stats.py b/test/unit/test_stats.py similarity index 100% rename from test/test_stats.py rename to test/unit/test_stats.py diff --git a/test/test_twilio_email.py b/test/unit/test_twilio_email.py similarity index 100% rename from test/test_twilio_email.py rename to test/unit/test_twilio_email.py diff --git a/test/test_unassigned.py b/test/unit/test_unassigned.py similarity index 100% rename from test/test_unassigned.py rename to test/unit/test_unassigned.py From 54fb4035865bee25dea8bbbcd7ea0d9a9fa43235 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 15 Dec 2021 19:39:08 +0000 Subject: [PATCH 394/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f102be33..d4f521a85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2021-12-15] Version 6.9.3 +-------------------------- +**Library - Test** +- [PR #1029](https://github.com/sendgrid/sendgrid-python/pull/1029): split up unit and integ tests. Thanks to [@childish-sambino](https://github.com/childish-sambino)! + + [2021-12-01] Version 6.9.2 -------------------------- **Library - Chore** From 1c7493cbadaff93a9b1dd918c63b22e129719f17 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 15 Dec 2021 19:41:42 +0000 Subject: [PATCH 395/462] Release 6.9.3 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index b8fc882cc..9d3fb5211 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.9.2' +__version__ = '6.9.3' From 76c5f46c103533a1c0d8d857e62f3af915cd4a89 Mon Sep 17 00:00:00 2001 From: Jennifer Mah <42650198+JenniferMah@users.noreply.github.com> Date: Wed, 5 Jan 2022 08:27:25 -0700 Subject: [PATCH 396/462] chore: update license --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index e5439a92d..5db04ff6d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (C) 2021, Twilio SendGrid, Inc. +Copyright (C) 2022, Twilio SendGrid, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From b729f401dc9c15393cc0e3036a025deff3d711c6 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 5 Jan 2022 14:48:25 -0800 Subject: [PATCH 397/462] docs: remove leading spaces on error handling example (#1032) --- use_cases/error_handling.md | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/use_cases/error_handling.md b/use_cases/error_handling.md index 187703b60..d0bdf0945 100644 --- a/use_cases/error_handling.md +++ b/use_cases/error_handling.md @@ -6,24 +6,24 @@ Please see [here](https://github.com/sendgrid/python-http-client/blob/HEAD/pytho There are also email specific exceptions located [here](../sendgrid/helpers/mail/exceptions.py) ```python - import os - from sendgrid import SendGridAPIClient - from sendgrid.helpers.mail import (From, To, Subject, PlainTextContent, HtmlContent, Mail) - from python_http_client import exceptions +import os +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import (From, To, Subject, PlainTextContent, HtmlContent, Mail) +from python_http_client import exceptions - sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) - from_email = From("help@twilio.com") - to_email = To("ethomas@twilio.com") - subject = Subject("Sending with Twilio SendGrid is Fun") - plain_text_content = PlainTextContent("and easy to do anywhere, even with Python") - html_content = HtmlContent("and easy to do anywhere, even with Python") - message = Mail(from_email, to_email, subject, plain_text_content, html_content) - try: - response = sendgrid_client.send(message) - print(response.status_code) - print(response.body) - print(response.headers) - except exceptions.BadRequestsError as e: - print(e.body) - exit() +sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) +from_email = From("help@twilio.com") +to_email = To("ethomas@twilio.com") +subject = Subject("Sending with Twilio SendGrid is Fun") +plain_text_content = PlainTextContent("and easy to do anywhere, even with Python") +html_content = HtmlContent("and easy to do anywhere, even with Python") +message = Mail(from_email, to_email, subject, plain_text_content, html_content) +try: + response = sendgrid_client.send(message) + print(response.status_code) + print(response.body) + print(response.headers) +except exceptions.BadRequestsError as e: + print(e.body) + exit() ``` From df13b78b0cdcb410b4516f6761c4d3138edd4b2d Mon Sep 17 00:00:00 2001 From: Karthikeyan Singaravelan Date: Sat, 8 Jan 2022 02:49:23 +0530 Subject: [PATCH 398/462] chore: Remove unused import from distutils (#1031) Co-authored-by: Jennifer Mah --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 365ebb50b..e1d6600d6 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,5 @@ import io import os -from distutils.file_util import copy_file from setuptools import setup, find_packages From ba7135ea3c41785efc1f1177faa3b287a02f721c Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 12 Jan 2022 20:32:31 +0000 Subject: [PATCH 399/462] [Librarian] Version Bump --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4f521a85..817ee5611 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Change Log All notable changes to this project will be documented in this file. +[2022-01-12] Version 6.9.4 +-------------------------- +**Library - Chore** +- [PR #1031](https://github.com/sendgrid/sendgrid-python/pull/1031): Remove unused import from distutils. Thanks to [@tirkarthi](https://github.com/tirkarthi)! + +**Library - Docs** +- [PR #1032](https://github.com/sendgrid/sendgrid-python/pull/1032): remove leading spaces on error handling example. Thanks to [@thinkingserious](https://github.com/thinkingserious)! + + [2021-12-15] Version 6.9.3 -------------------------- **Library - Test** From dc78d9ebf635d8e5e9d20439d51c6d9f3965b204 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 12 Jan 2022 20:34:59 +0000 Subject: [PATCH 400/462] Release 6.9.4 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 9d3fb5211..55b51e8b2 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.9.3' +__version__ = '6.9.4' From d7f81db48bdeb6ff14b263604ce3d6036284a232 Mon Sep 17 00:00:00 2001 From: Jack Slingerland Date: Thu, 20 Jan 2022 20:04:47 -0500 Subject: [PATCH 401/462] docs: Removing unused json import (#1036) --- use_cases/transactional_templates.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/use_cases/transactional_templates.md b/use_cases/transactional_templates.md index 9ee4848c3..460fd65ee 100644 --- a/use_cases/transactional_templates.md +++ b/use_cases/transactional_templates.md @@ -28,7 +28,6 @@ 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 @@ -86,7 +85,6 @@ 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 From d742ada8e6aaee77ab2f4f731f7099372255948b Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 26 Jan 2022 19:23:42 +0000 Subject: [PATCH 402/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 817ee5611..5e7d5c0be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2022-01-26] Version 6.9.5 +-------------------------- +**Library - Docs** +- [PR #1036](https://github.com/sendgrid/sendgrid-python/pull/1036): Removing unused json import. Thanks to [@vital101](https://github.com/vital101)! + + [2022-01-12] Version 6.9.4 -------------------------- **Library - Chore** From 3b17a349cc45eeb796d038722cd4a33ed4dea3ed Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 26 Jan 2022 19:26:42 +0000 Subject: [PATCH 403/462] Release 6.9.5 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 55b51e8b2..5b3835adf 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.9.4' +__version__ = '6.9.5' From bbd1319d5719728de5908b0fc650a5083ed2dcac Mon Sep 17 00:00:00 2001 From: Hunga1 Date: Thu, 3 Feb 2022 13:26:45 -0700 Subject: [PATCH 404/462] chore: merge test and deploy workflows (#1039) --- .github/workflows/release.yml | 50 ------------------- .github/workflows/test-and-deploy.yml | 71 +++++++++++++++++++++++++++ .github/workflows/tests.yml | 50 ------------------- README.md | 2 +- 4 files changed, 72 insertions(+), 101 deletions(-) delete mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/test-and-deploy.yml delete mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 65ef25a39..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Publish Python distributions -on: - push: - tags: - - '*' - workflow_dispatch: - -jobs: - release: - name: Release - runs-on: ubuntu-latest - steps: - - name: Checkout sendgrid-python - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.6' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build - pip install wheel - python setup.py sdist bdist_wheel - - - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_TOKEN }} - - notify-on-failure: - name: Slack notify on failure - if: ${{ failure() }} - needs: [ release ] - runs-on: ubuntu-latest - steps: - - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_COLOR: 'danger' - SLACK_ICON_EMOJI: ':github:' - SLACK_MESSAGE: ${{ format('Failed to release {1}{3} {0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id, ':') }} - SLACK_TITLE: Release Failure - SLACK_USERNAME: GitHub Actions - SLACK_MSG_AUTHOR: twilio-dx - SLACK_FOOTER: Posted automatically using GitHub Actions - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} - MSG_MINIMAL: true diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml new file mode 100644 index 000000000..de9233e0a --- /dev/null +++ b/.github/workflows/test-and-deploy.yml @@ -0,0 +1,71 @@ +name: Test and Deploy +on: + push: + branches: [ '*' ] + tags: [ '*' ] + pull_request: + branches: [ main ] + schedule: + # Run automatically at 8AM PST Monday-Friday + - cron: '0 15 * * 1-5' + workflow_dispatch: + +jobs: + test: + name: Test + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + matrix: + python-version: [ '2.7', '3.5', '3.6', '3.7', '3.8', '3.9' ] + steps: + - name: Checkout sendgrid-python + uses: actions/checkout@v2 + + - name: Build & Test + run: make test-docker version=${{ matrix.python-version }} + + deploy: + name: Deploy + if: success() && github.ref_type == 'tag' + needs: [ test ] + runs-on: ubuntu-latest + steps: + - name: Checkout sendgrid-python + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.6' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + pip install wheel + python setup.py sdist bdist_wheel + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_TOKEN }} + + notify-on-failure: + name: Slack notify on failure + if: failure() && github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref_type == 'tag') + needs: [ test, deploy ] + runs-on: ubuntu-latest + steps: + - uses: rtCamp/action-slack-notify@v2 + env: + SLACK_COLOR: failure + SLACK_ICON_EMOJI: ':github:' + SLACK_MESSAGE: ${{ format('Test *{0}*, Deploy *{1}*, {2}/{3}/actions/runs/{4}', needs.test.result, needs.deploy.result, github.server_url, github.repository, github.run_id) }} + SLACK_TITLE: Action Failure - ${{ github.repository }} + SLACK_USERNAME: GitHub Actions + SLACK_MSG_AUTHOR: twilio-dx + SLACK_FOOTER: Posted automatically using GitHub Actions + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + MSG_MINIMAL: true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index fe2015a6e..000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Run Tests -on: - push: - branches: [ '*' ] - pull_request: - branches: [ main ] - schedule: - # Run automatically at 8AM PST Monday-Friday - - cron: '0 15 * * 1-5' - workflow_dispatch: - -jobs: - tests: - name: Run Tests - runs-on: ubuntu-latest - timeout-minutes: 20 - strategy: - matrix: - python-version: [ '2.7', '3.5', '3.6', '3.7', '3.8', '3.9' ] - steps: - - name: Checkout sendgrid-python - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Build & Test - run: make test-docker test-install - - notify-on-failure: - name: Slack notify on failure - if: ${{ failure() && github.ref == 'refs/heads/main' && github.event_name != 'pull_request' }} - needs: [ tests ] - runs-on: ubuntu-latest - steps: - - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_COLOR: 'danger' - SLACK_ICON_EMOJI: ':github:' - SLACK_MESSAGE: ${{ format('Failed running build on {1}{3} {0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id, ':') }} - SLACK_TITLE: Build Failure - SLACK_USERNAME: GitHub Actions - SLACK_MSG_AUTHOR: twilio-dx - SLACK_FOOTER: Posted automatically using GitHub Actions - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} - MSG_MINIMAL: true diff --git a/README.md b/README.md index e2208293d..a653e38f9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![SendGrid Logo](twilio_sendgrid_logo.png) -[![Tests](https://github.com/sendgrid/sendgrid-python/actions/workflows/tests.yml/badge.svg)](https://github.com/sendgrid/sendgrid-python/actions/workflows/tests.yml) +[![BuildStatus](https://github.com/sendgrid/sendgrid-python/actions/workflows/test-and-deploy.yml/badge.svg)](https://github.com/sendgrid/sendgrid-python/actions/workflows/test-and-deploy.yml) [![Docker Badge](https://img.shields.io/docker/automated/sendgrid/sendgrid-python.svg)](https://hub.docker.com/r/sendgrid/sendgrid-python/) [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Twitter Follow](https://img.shields.io/twitter/follow/sendgrid.svg?style=social&label=Follow)](https://twitter.com/sendgrid) From 0c7a723f0b7c19a5fb5535c2d006d87191f355f9 Mon Sep 17 00:00:00 2001 From: Shwetha Radhakrishna Date: Thu, 3 Feb 2022 16:59:38 -0600 Subject: [PATCH 405/462] chore: add gh release to workflow (#1041) --- .github/workflows/test-and-deploy.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml index de9233e0a..f061a5096 100644 --- a/.github/workflows/test-and-deploy.yml +++ b/.github/workflows/test-and-deploy.yml @@ -46,6 +46,13 @@ jobs: pip install wheel python setup.py sdist bdist_wheel + - name: Create GitHub Release + uses: sendgrid/dx-automator/actions/release@main + with: + footer: '**[pypi](https://pypi.org/project/sendgrid/${version})**' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Publish package to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: From 412ca9914eec30866a44f1c01f008df3c0a95f28 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Mon, 7 Feb 2022 12:50:41 -0600 Subject: [PATCH 406/462] fix: only do a Docker Login if the secrets are available --- .github/workflows/test-and-deploy.yml | 65 +++++++++++++++------------ 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml index f061a5096..7ce3240c7 100644 --- a/.github/workflows/test-and-deploy.yml +++ b/.github/workflows/test-and-deploy.yml @@ -18,46 +18,55 @@ jobs: strategy: matrix: python-version: [ '2.7', '3.5', '3.6', '3.7', '3.8', '3.9' ] + env: + DOCKER_LOGIN: ${{ secrets.DOCKER_USERNAME && secrets.DOCKER_AUTH_TOKEN }} steps: - name: Checkout sendgrid-python uses: actions/checkout@v2 + - name: Login to Docker Hub + if: env.DOCKER_LOGIN + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_AUTH_TOKEN }} + - name: Build & Test run: make test-docker version=${{ matrix.python-version }} deploy: - name: Deploy - if: success() && github.ref_type == 'tag' - needs: [ test ] - runs-on: ubuntu-latest - steps: - - name: Checkout sendgrid-python - uses: actions/checkout@v2 + name: Deploy + if: success() && github.ref_type == 'tag' + needs: [ test ] + runs-on: ubuntu-latest + steps: + - name: Checkout sendgrid-python + uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.6' + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.6' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build - pip install wheel - python setup.py sdist bdist_wheel + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + pip install wheel + python setup.py sdist bdist_wheel - - name: Create GitHub Release - uses: sendgrid/dx-automator/actions/release@main - with: - footer: '**[pypi](https://pypi.org/project/sendgrid/${version})**' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Create GitHub Release + uses: sendgrid/dx-automator/actions/release@main + with: + footer: '**[pypi](https://pypi.org/project/sendgrid/${version})**' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_TOKEN }} + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_TOKEN }} notify-on-failure: name: Slack notify on failure From 807a2c97dcc9fc92764f97c73b6086ab2426c4f5 Mon Sep 17 00:00:00 2001 From: childish-sambino Date: Tue, 8 Feb 2022 09:14:53 -0600 Subject: [PATCH 407/462] chore: upgrade supported language versions (#1043) --- .github/workflows/test-and-deploy.yml | 4 ++-- setup.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml index 7ce3240c7..842277933 100644 --- a/.github/workflows/test-and-deploy.yml +++ b/.github/workflows/test-and-deploy.yml @@ -17,7 +17,7 @@ jobs: timeout-minutes: 20 strategy: matrix: - python-version: [ '2.7', '3.5', '3.6', '3.7', '3.8', '3.9' ] + python-version: [ '2.7', '3.5', '3.6', '3.7', '3.8', '3.9', '3.10' ] env: DOCKER_LOGIN: ${{ secrets.DOCKER_USERNAME && secrets.DOCKER_AUTH_TOKEN }} steps: @@ -46,7 +46,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: '3.6' + python-version: '3.10' - name: Install dependencies run: | diff --git a/setup.py b/setup.py index e1d6600d6..7e84802f4 100644 --- a/setup.py +++ b/setup.py @@ -38,5 +38,8 @@ def getRequires(): 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', ] ) From b85c8fed2ffeff2084d005c164bd13851f245db7 Mon Sep 17 00:00:00 2001 From: childish-sambino Date: Wed, 9 Feb 2022 10:56:42 -0600 Subject: [PATCH 408/462] chore: drop pytest which was not being used (#1044) --- requirements.txt | 1 - test/unit/test_unassigned.py | 8 ++------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index f4b4ba105..0c34aafd4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,5 @@ Flask==1.1.2 PyYAML>=4.2b1 python-http-client>=3.2.1 six==1.11.0 -pytest==3.8.2 starkbank-ecdsa>=2.0.1 more-itertools==5.0.0 diff --git a/test/unit/test_unassigned.py b/test/unit/test_unassigned.py index 6054447d8..08ab943bb 100644 --- a/test/unit/test_unassigned.py +++ b/test/unit/test_unassigned.py @@ -1,9 +1,7 @@ import json -import pytest from sendgrid.helpers.endpoints.ip.unassigned import unassigned - ret_json = '''[ { "ip": "167.89.21.3", "pools": [ @@ -69,8 +67,7 @@ def make_data(): def test_unassigned_ip_json(): - - data = make_data() + data = make_data() as_json = True calculated = unassigned(get_all_ip(), as_json=as_json) @@ -81,8 +78,7 @@ def test_unassigned_ip_json(): def test_unassigned_ip_obj(): - - data = make_data() + data = make_data() as_json = False calculated = unassigned(get_all_ip(), as_json=as_json) From 78615fcd2db479d2d06580fe35739539d68a285a Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 9 Feb 2022 14:49:27 -0800 Subject: [PATCH 409/462] [Librarian] Version Bump --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e7d5c0be..f009359c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Change Log All notable changes to this project will be documented in this file. +[2022-02-09] Version 6.9.6 +-------------------------- +**Library - Chore** +- [PR #1044](https://github.com/sendgrid/sendgrid-python/pull/1044): drop pytest which was not being used. Thanks to [@childish-sambino](https://github.com/childish-sambino)! +- [PR #1043](https://github.com/sendgrid/sendgrid-python/pull/1043): upgrade supported language versions. Thanks to [@childish-sambino](https://github.com/childish-sambino)! +- [PR #1041](https://github.com/sendgrid/sendgrid-python/pull/1041): add gh release to workflow. Thanks to [@shwetha-manvinkurke](https://github.com/shwetha-manvinkurke)! +- [PR #1039](https://github.com/sendgrid/sendgrid-python/pull/1039): merge test and deploy workflows. Thanks to [@Hunga1](https://github.com/Hunga1)! + + [2022-01-26] Version 6.9.5 -------------------------- **Library - Docs** From f91a2162add712658b9f5e201f06c51a842cbd0a Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 9 Feb 2022 14:49:28 -0800 Subject: [PATCH 410/462] Release 6.9.6 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 5b3835adf..eea67ac10 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.9.5' +__version__ = '6.9.6' From 082e3c57c7da4649018b36550f8512c4d99d0fd8 Mon Sep 17 00:00:00 2001 From: Elise Shanholtz Date: Mon, 28 Feb 2022 15:42:42 -0800 Subject: [PATCH 411/462] chore: fix flask dependency test issues (#1050) --- test/requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/requirements.txt b/test/requirements.txt index 6cb2e96d2..17302b22e 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,6 +1,8 @@ pyyaml -flask +Flask==1.1.4 six coverage codecov mock +itsdangerous==1.1.0 +markupsafe==1.1.1 From 0b774384fb721196b12870d108f7bafe7c8c6b40 Mon Sep 17 00:00:00 2001 From: Elise Shanholtz Date: Tue, 1 Mar 2022 09:53:43 -0800 Subject: [PATCH 412/462] chore: push Datadog Release Metric upon deploy success (#1049) --- .github/workflows/test-and-deploy.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml index 842277933..ac0bca0a8 100644 --- a/.github/workflows/test-and-deploy.yml +++ b/.github/workflows/test-and-deploy.yml @@ -68,6 +68,11 @@ jobs: user: __token__ password: ${{ secrets.PYPI_TOKEN }} + - name: Submit metric to Datadog + uses: sendgrid/dx-automator/actions/datadog-release-metric@main + env: + DD_API_KEY: ${{ secrets.DATADOG_API_KEY }} + notify-on-failure: name: Slack notify on failure if: failure() && github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref_type == 'tag') From d7d292cf2241c18d052b8e3705dcc134cc0912d5 Mon Sep 17 00:00:00 2001 From: Vinicius Mesel Date: Fri, 4 Mar 2022 18:29:35 -0300 Subject: [PATCH 413/462] chore: Update mail_example.py (#1048) Co-authored-by: Elise Shanholtz --- examples/helpers/mail_example.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/examples/helpers/mail_example.py b/examples/helpers/mail_example.py index 384181501..f6905787b 100644 --- a/examples/helpers/mail_example.py +++ b/examples/helpers/mail_example.py @@ -1,3 +1,6 @@ +import os +import json + from sendgrid import SendGridAPIClient from sendgrid.helpers.mail import * @@ -8,11 +11,7 @@ def build_hello_email(): ## 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('from@example.com', 'Example From Name'), to_emails=To('to@example.com', 'Example To Name'), subject=Subject('Sending with SendGrid is Fun'), @@ -84,11 +83,8 @@ def get_mock_personalization_dict(): return mock_pers def build_multiple_emails_personalized(): - import json - from sendgrid.helpers.mail import Mail, From, To, Cc, Bcc, Subject, PlainTextContent, \ - HtmlContent, SendGridException, Personalization - # Note that the domain for all From email addresses must match + message = Mail(from_email=From('from@example.com', 'Example From Name'), subject=Subject('Sending with SendGrid is Fun'), plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), @@ -117,6 +113,7 @@ def build_multiple_emails_personalized(): def build_attachment1(): """Build attachment mock. Make sure your content is base64 encoded before passing into attachment.content. Another example: https://github.com/sendgrid/sendgrid-python/blob/HEAD/use_cases/attachment.md""" + attachment = Attachment() attachment.file_content = ("TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNl" "Y3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12") From d1a54500ac460fb9e392701925b1d9f21834581b Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 9 Mar 2022 12:21:55 -0800 Subject: [PATCH 414/462] [Librarian] Version Bump --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f009359c4..a9d8f79db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Change Log All notable changes to this project will be documented in this file. +[2022-03-09] Version 6.9.7 +-------------------------- +**Library - Chore** +- [PR #1048](https://github.com/sendgrid/sendgrid-python/pull/1048): Update mail_example.py. Thanks to [@vmesel](https://github.com/vmesel)! +- [PR #1049](https://github.com/sendgrid/sendgrid-python/pull/1049): push Datadog Release Metric upon deploy success. Thanks to [@eshanholtz](https://github.com/eshanholtz)! +- [PR #1050](https://github.com/sendgrid/sendgrid-python/pull/1050): fix flask dependency test issues. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + + [2022-02-09] Version 6.9.6 -------------------------- **Library - Chore** From 202fabc99073f1a61302d0e8ecb143e0d45bd8ce Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 9 Mar 2022 12:21:55 -0800 Subject: [PATCH 415/462] Release 6.9.7 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index eea67ac10..59b5dedc7 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.9.6' +__version__ = '6.9.7' From e3d3ce500ba41f20ed6ce8f0b1a7fcb18882d917 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Thu, 24 Mar 2022 10:48:08 -0500 Subject: [PATCH 416/462] chore: remove outdated announcements --- README.md | 6 +----- README.rst | 10 +--------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index a653e38f9..321d86ca3 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,6 @@ [![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) -**The default branch name for this repository has been changed to `main` as of 07/27/2020.** - **This library allows you to quickly and easily use the 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). @@ -187,9 +185,7 @@ Please see [our helper](sendgrid/helpers/inbound) for utilizing our Inbound Pars # Announcements -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](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. +All updates to this library are documented in our [CHANGELOG](CHANGELOG.md) and [releases](https://github.com/sendgrid/sendgrid-python/releases). # How to Contribute diff --git a/README.rst b/README.rst index 59f693f0f..93062ba66 100644 --- a/README.rst +++ b/README.rst @@ -3,15 +3,12 @@ -|Tests Badge| |codecov| |Python Versions| |PyPI Version| |Docker Badge| |Email Notifications Badge| |MIT licensed| |Twitter Follow| |GitHub contributors| |Open Source Helpers| +|Tests Badge| |codecov| |Python Versions| |PyPI Version| |Docker Badge| |MIT licensed| |Twitter Follow| |GitHub contributors| |Open Source Helpers| **This library allows you to quickly and easily use the Twilio SendGrid Web API v3 via Python.** **NEW:** -**The default branch name for this repository has been changed to `main` as of 07/27/2020.** - -- Subscribe to email `notifications`_ for releases and breaking changes. - Version 6.X release is a BREAKING CHANGE from version 5.X, please see the `release notes`_ for details. - Send SMS messages with `Twilio`_. @@ -223,7 +220,6 @@ Announcements ============= 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. How to Contribute ================= @@ -253,7 +249,6 @@ License `The MIT License (MIT)`_ -.. _notifications: https://dx.sendgrid.com/newsletter/python .. _Twilio: https://github.com/sendgrid/sendgrid-python/blob/HEAD/use_cases/sms.md .. _release notes: https://github.com/sendgrid/sendgrid-python/releases/tag/v6.0.0 .. _Web API v3: https://sendgrid.com/docs/API_Reference/Web_API_v3/index.html @@ -279,7 +274,6 @@ License .. _breaking changes: https://github.com/sendgrid/sendgrid-python/issues/217 .. _CHANGELOG: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CHANGELOG.md .. _releases: https://github.com/sendgrid/sendgrid-python/releases -.. _release notifications: https://dx.sendgrid.com/newsletter/python .. _CONTRIBUTING: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md .. _Feature Request: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md#feature-request .. _Bug Reports: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md#submit-a-bug-report @@ -298,8 +292,6 @@ License :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 .. |Twitter Follow| image:: https://img.shields.io/twitter/follow/sendgrid.svg?style=social&label=Follow From 27843fe2d5b7fdf188f606ef20f2beab32b97eee Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Fri, 25 Mar 2022 13:39:50 -0500 Subject: [PATCH 417/462] feat: add PR title validation --- .github/workflows/pr-lint.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/pr-lint.yml diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml new file mode 100644 index 000000000..8388f21e8 --- /dev/null +++ b/.github/workflows/pr-lint.yml @@ -0,0 +1,15 @@ +name: Lint PR +on: + pull_request_target: + types: [ opened, edited, reopened ] + +jobs: + validate: + name: Validate title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v4 + with: + types: chore docs fix feat test + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 903c9cb87cf87d018a1d131b4903b4a300b92e0a Mon Sep 17 00:00:00 2001 From: Jonathan Berger Date: Tue, 29 Mar 2022 08:04:42 -0700 Subject: [PATCH 418/462] docs: Fix link that has drifted (#1052) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 321d86ca3..9ffdbb7d0 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ 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](examples/helpers/mail_example.py#L16) is an example of how to add it. +The `Mail` constructor creates a [personalization object](https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/personalizations.html) for you. [Here](examples/helpers/mail_example.py#L28) is an example of how to add it. ### Without Mail Helper Class From 43c03bdd8baca80a80382bd6445a7c6021d697fa Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Thu, 21 Apr 2022 14:44:33 -0500 Subject: [PATCH 419/462] test: lint PRs on synchronize events Since synchronize events clears the status checks, it needs to be re-run. --- .github/workflows/pr-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index 8388f21e8..dc7af3d3c 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -1,7 +1,7 @@ name: Lint PR on: pull_request_target: - types: [ opened, edited, reopened ] + types: [ opened, edited, synchronize, reopened ] jobs: validate: From c798252a31949b9b263f9722294eb94ba3bb1e33 Mon Sep 17 00:00:00 2001 From: "Gareth Paul Jones (GPJ)" Date: Mon, 9 May 2022 10:03:43 -0700 Subject: [PATCH 420/462] docs: Modify README.md in alignment with SendGrid Support (#1055) --- .github/ISSUE_TEMPLATE/config.yml | 10 ---------- ISSUE_TEMPLATE.md | 30 ------------------------------ PULL_REQUEST_TEMPLATE.md | 2 +- README.md | 10 ++++++---- test/unit/test_project.py | 4 ---- 5 files changed, 7 insertions(+), 49 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index afcba3446..000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,10 +0,0 @@ -contact_links: - - name: Twilio SendGrid Support - url: https://support.sendgrid.com - about: Get Support - - name: Stack Overflow - url: https://stackoverflow.com/questions/tagged/sendgrid-python+or+sendgrid+python - about: Ask questions on Stack Overflow - - name: Documentation - url: https://sendgrid.com/docs/for-developers/ - about: View Reference Documentation diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index fb2e15cef..000000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,30 +0,0 @@ - - -### Issue Summary -A summary of the issue and the environment in which it occurs. If suitable, include the steps required to reproduce the bug. Please feel free to include screenshots, screencasts, or code examples. - -### Steps to Reproduce -1. This is the first step -2. This is the second step -3. Further steps, etc. - -### Code Snippet -```python -# paste code here -``` - -### Exception/Log -``` -# paste exception/log here -``` - -### Technical details: -* sendgrid-python version: -* python version: - diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 7c2789ae4..f78ab3918 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -28,4 +28,4 @@ A short description of what this PR does. - [ ] I have added the necessary documentation about the functionality in the appropriate .md file - [ ] I have added inline documentation to the code I modified -If you have questions, please file a [support ticket](https://support.sendgrid.com), or create a GitHub Issue in this repository. +If you have questions, please file a [support ticket](https://support.sendgrid.com). \ No newline at end of file diff --git a/README.md b/README.md index 9ffdbb7d0..663d1e977 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ Version 3.X.X+ of this library provides full support for all SendGrid [Web API v This library represents the beginning of a new path for SendGrid. We want this library to be community driven and SendGrid led. We need your help to realize this goal. To help make sure we are building the right things in the right order, we ask that you create [issues](https://github.com/sendgrid/sendgrid-python/issues) and [pull requests](CONTRIBUTING.md) or simply upvote or comment on existing issues or pull requests. -Please browse the rest of this README for further detail. +**If you need help using SendGrid, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com).** -We appreciate your continued support, thank you! +Please browse the rest of this README for further detail. # Table of Contents @@ -28,6 +28,7 @@ We appreciate your continued support, thank you! * [How to Contribute](#contribute) * [Troubleshooting](#troubleshooting) * [About](#about) +* [Support](#support) * [License](#license) @@ -209,9 +210,10 @@ Please see our [troubleshooting guide](TROUBLESHOOTING.md) for common library is sendgrid-python is maintained and funded by Twilio SendGrid, Inc. The names and logos for sendgrid-python are trademarks of Twilio SendGrid, Inc. -If you need help installing or using the library, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com). + +# Support -If you've instead found a bug in the library or would like new features added, go ahead and open issues or pull requests against this repo! +If you need support, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com). # License diff --git a/test/unit/test_project.py b/test/unit/test_project.py index c78293dff..40282bdb7 100644 --- a/test/unit/test_project.py +++ b/test/unit/test_project.py @@ -23,10 +23,6 @@ def test_code_of_conduct(self): def test_contributing(self): self.assertTrue(os.path.isfile('./CONTRIBUTING.md')) - # ./ISSUE_TEMPLATE.md - def test_issue_template(self): - self.assertTrue(os.path.isfile('./ISSUE_TEMPLATE.md')) - # ./LICENSE def test_license(self): self.assertTrue(os.path.isfile('./LICENSE')) From 17a7bcc94f3c02f7cf9327139378d1f5e06cac50 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Thu, 12 May 2022 10:28:26 -0500 Subject: [PATCH 421/462] chore: drop the issue links from FIRST_TIMERS doc --- FIRST_TIMERS.md | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/FIRST_TIMERS.md b/FIRST_TIMERS.md index 528580c34..a1d563a75 100644 --- a/FIRST_TIMERS.md +++ b/FIRST_TIMERS.md @@ -51,29 +51,3 @@ git push origin ## Important notice Before creating a pull request, make sure that you respect the repository's constraints regarding contributions. You can find them in the [CONTRIBUTING.md](CONTRIBUTING.md) file. - -## Repositories with Open, Easy, Help Wanted, Issue Filters - -* [Python SDK](https://github.com/sendgrid/sendgrid-python/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [PHP SDK](https://github.com/sendgrid/sendgrid-php/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [C# SDK](https://github.com/sendgrid/sendgrid-csharp/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Ruby SDK](https://github.com/sendgrid/sendgrid-ruby/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Node.js SDK](https://github.com/sendgrid/sendgrid-nodejs/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Java SDK](https://github.com/sendgrid/sendgrid-java/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Go SDK](https://github.com/sendgrid/sendgrid-go/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Python SMTPAPI Client](https://github.com/sendgrid/smtpapi-python/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [PHP SMTPAPI Client](https://github.com/sendgrid/smtpapi-php/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [C# SMTPAPI Client](https://github.com/sendgrid/smtpapi-csharp/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Ruby SMTPAPI Client](https://github.com/sendgrid/smtpapi-ruby/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Node.js SMTPAPI Client](https://github.com/sendgrid/smtpapi-nodejs/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Java SMTPAPI Client](https://github.com/sendgrid/smtpapi-java/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Go SMTPAPI Client](https://github.com/sendgrid/smtpapi-go/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Python HTTP Client](https://github.com/sendgrid/python-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [PHP HTTP Client](https://github.com/sendgrid/php-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [C# HTTP Client](https://github.com/sendgrid/csharp-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Java HTTP Client](https://github.com/sendgrid/java-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Ruby HTTP Client](https://github.com/sendgrid/ruby-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Go HTTP Client](https://github.com/sendgrid/rest/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Open API Definition](https://github.com/sendgrid/sendgrid-oai/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [DX Automator](https://github.com/sendgrid/dx-automator/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Documentation](https://github.com/sendgrid/docs/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) From ef1aa7293e4cda85dcba1cba4524d3e85d8e0281 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Mon, 23 May 2022 11:04:22 -0500 Subject: [PATCH 422/462] docs: drop references to ISSUE_TEMPLATE.md Issues are no longer supported so file was previously removed. --- CONTRIBUTING.md | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c2769176d..890641059 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,9 +2,6 @@ Hello! Thank you for choosing to help contribute to one of the Twilio SendGrid o All third party contributors acknowledge that any contributions they provide will be made under the same open source license that the open source project is provided under. -- [Feature Request](#feature-request) -- [Submit a Bug Report](#submit-a-bug-report) - - [Please use our Bug Report Template](#please-use-our-bug-report-template) - [Improvements to the Codebase](#improvements-to-the-codebase) - [Development Environment](#development-environment) - [Prerequisites](#prerequisites) @@ -19,31 +16,6 @@ All third party contributors acknowledge that any contributions they provide wil There are a few ways to contribute, which we'll enumerate below: -## Feature Request - -If you'd like to make a feature request, please read this section. - -The GitHub issue tracker is the preferred channel for library feature requests, but please respect the following restrictions: - -- Please **search for existing issues** in order to ensure we don't have duplicate bugs/feature requests. -- Please be respectful and considerate of others when commenting on issues - -## Submit a Bug Report - -Note: DO NOT include your credentials in ANY code examples, descriptions, or media you make public. - -A software bug is a demonstrable issue in the code base. In order for us to diagnose the issue and respond as quickly as possible, please add as much detail as possible into your bug report. - -Before you decide to create a new issue, please try the following: - -1. Check the GitHub issues tab if the identified issue has already been reported, if so, please add a +1 to the existing post. -2. Update to the latest version of this code and check if the issue has already been fixed -3. Copy and fill in the Bug Report Template we have provided below - -### Please use our Bug Report Template - -In order to make the process easier, we've included a [sample bug report template](ISSUE_TEMPLATE.md). - ## Improvements to the Codebase We welcome direct contributions to the sendgrid-python code base. Thank you! From 653cc47c54d53d401d65d1debb3cdc1d7905b4a1 Mon Sep 17 00:00:00 2001 From: Raghav Katyal Date: Wed, 6 Jul 2022 16:14:12 -0700 Subject: [PATCH 423/462] Adding misc as PR type (#1058) --- .github/workflows/pr-lint.yml | 2 +- PULL_REQUEST_TEMPLATE.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index dc7af3d3c..2f5232b08 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -10,6 +10,6 @@ jobs: steps: - uses: amannn/action-semantic-pull-request@v4 with: - types: chore docs fix feat test + types: chore docs fix feat test misc env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index f78ab3918..f9448a3b1 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -3,7 +3,7 @@ We appreciate the effort for this pull request but before that please make sure Please format the PR title appropriately based on the type of change: [!]: -Where is one of: docs, chore, feat, fix, test. +Where is one of: docs, chore, feat, fix, test, misc. Add a '!' after the type for breaking changes (e.g. feat!: new breaking feature). **All third-party contributors acknowledge that any contributions they provide will be made under the same open-source license that the open-source project is provided under.** From 4288aa618b04a3e99173f59ce43d7a4b379b93c4 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Tue, 3 Jan 2023 09:09:32 -0600 Subject: [PATCH 424/462] docs: updated the year in the license --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 5db04ff6d..3154774a9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (C) 2022, Twilio SendGrid, Inc. +Copyright (C) 2023, Twilio SendGrid, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From 9e8a2599bd86d599465552de591d2ca5413fb101 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 13 Mar 2023 15:46:49 +0100 Subject: [PATCH 425/462] feat: Add Python 3.11 to the testing (#1059) --- .github/workflows/test-and-deploy.yml | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml index ac0bca0a8..f14974e43 100644 --- a/.github/workflows/test-and-deploy.yml +++ b/.github/workflows/test-and-deploy.yml @@ -17,7 +17,7 @@ jobs: timeout-minutes: 20 strategy: matrix: - python-version: [ '2.7', '3.5', '3.6', '3.7', '3.8', '3.9', '3.10' ] + python-version: [ '2.7', '3.5', '3.6', '3.7', '3.8', '3.9', '3.10', '3.11' ] env: DOCKER_LOGIN: ${{ secrets.DOCKER_USERNAME && secrets.DOCKER_AUTH_TOKEN }} steps: diff --git a/setup.py b/setup.py index 7e84802f4..41f11e589 100644 --- a/setup.py +++ b/setup.py @@ -34,12 +34,12 @@ def getRequires(): classifiers=[ 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', ] ) From 93612afeb0114cd98b6efd7c82dbcf98c9a4df27 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 13 Mar 2023 15:57:25 +0100 Subject: [PATCH 426/462] misc: Upgrade GitHub Action pr-lint.yml (#1063) --- .github/workflows/pr-lint.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index 2f5232b08..31520079c 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -8,8 +8,14 @@ jobs: name: Validate title runs-on: ubuntu-latest steps: - - uses: amannn/action-semantic-pull-request@v4 + - uses: amannn/action-semantic-pull-request@v5 with: - types: chore docs fix feat test misc + types: | + chore + docs + fix + feat + misc + test env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 102842a9dbeb1cabe057ffc0e52603f2dbc44596 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 13 Mar 2023 16:05:34 +0100 Subject: [PATCH 427/462] misc: Upgrade GitHub Action test-and-deploy.yml (#1064) --- .github/workflows/test-and-deploy.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml index f14974e43..4db532e3d 100644 --- a/.github/workflows/test-and-deploy.yml +++ b/.github/workflows/test-and-deploy.yml @@ -22,11 +22,11 @@ jobs: DOCKER_LOGIN: ${{ secrets.DOCKER_USERNAME && secrets.DOCKER_AUTH_TOKEN }} steps: - name: Checkout sendgrid-python - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Login to Docker Hub if: env.DOCKER_LOGIN - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_AUTH_TOKEN }} @@ -41,10 +41,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sendgrid-python - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.10' From c8076fa684d66ed5c0569feaf7b99dbedd976b77 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 13 Mar 2023 17:11:47 +0100 Subject: [PATCH 428/462] misc: Create GitHub Action to lint Python code (#1065) --- .github/workflows/lint-python.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/lint-python.yml diff --git a/.github/workflows/lint-python.yml b/.github/workflows/lint-python.yml new file mode 100644 index 000000000..1c5f53f23 --- /dev/null +++ b/.github/workflows/lint-python.yml @@ -0,0 +1,16 @@ +# https://beta.ruff.rs +name: ruff +on: + push: + branches: + - main + pull_request: + branches: + - main +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: pip install --user ruff + - run: ruff --format=github --ignore="E722,F40,F841" --line-length=681 --target-version=py37 . From 4b5c605dda41dbf6dab9ed4ea72ff4014a735885 Mon Sep 17 00:00:00 2001 From: iLan Date: Tue, 21 Mar 2023 11:28:49 -0700 Subject: [PATCH 429/462] feat: Add reply_to_list functionality (#1062) --- sendgrid/helpers/mail/mail.py | 28 ++++++ test/unit/test_mail_helpers.py | 90 +++++++++++++++++++ ..._email_with_multiple_reply_to_addresses.md | 29 ++++++ 3 files changed, 147 insertions(+) create mode 100644 use_cases/send_a_single_email_with_multiple_reply_to_addresses.md diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index ba21f7891..2472ad7d3 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -60,6 +60,7 @@ def __init__( self._ip_pool_name = None self._mail_settings = None self._reply_to = None + self._reply_to_list = None self._send_at = None self._subject = None self._template_id = None @@ -695,6 +696,32 @@ def reply_to(self, value): value = ReplyTo(value[0], value[1]) self._reply_to = value + @property + def reply_to_list(self): + """A list of ReplyTo email addresses + + :rtype: list(ReplyTo), tuple + """ + return self._reply_to_list + + @reply_to_list.setter + def reply_to_list(self, value): + """A list of ReplyTo email addresses + + :param value: A list of ReplyTo email addresses + :type value: list(ReplyTo), tuple + """ + if isinstance(value, list): + for reply in value: + if isinstance(reply, ReplyTo): + if not isinstance(reply.email, str): + raise ValueError('You must provide an email for each entry in a reply_to_list') + else: + raise ValueError( + 'Please use a list of ReplyTos for a reply_to_list.' + ) + self._reply_to_list = value + @property def contents(self): """The contents of the email @@ -981,6 +1008,7 @@ def get(self): 'mail_settings': self._get_or_none(self.mail_settings), 'tracking_settings': self._get_or_none(self.tracking_settings), 'reply_to': self._get_or_none(self.reply_to), + 'reply_to_list': [r.get() for r in self.reply_to_list or []], } return {key: value for key, value in mail.items() diff --git a/test/unit/test_mail_helpers.py b/test/unit/test_mail_helpers.py index a7d08d890..c00307d46 100644 --- a/test/unit/test_mail_helpers.py +++ b/test/unit/test_mail_helpers.py @@ -235,6 +235,58 @@ def test_send_a_single_email_to_multiple_recipients(self): }''') ) + def test_send_a_single_email_with_multiple_reply_to_addresses(self): + from sendgrid.helpers.mail import (Mail, From, ReplyTo, To, Subject, + PlainTextContent, HtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to0@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), + html_content=HtmlContent('and easy to do anywhere, even with Python')) + + message.reply_to_list = [ReplyTo(email = 'test+reply_to_1@example.com'), ReplyTo(email = 'test+reply_to_2@example.com')] + + self.assertEqual( + message.get(), + json.loads(r'''{ + "content": [ + { + "type": "text/plain", + "value": "and easy to do anywhere, even with Python" + }, + { + "type": "text/html", + "value": "and easy to do anywhere, even with Python" + } + ], + "from": { + "email": "test+from@example.com", + "name": "Example From Name" + }, + "personalizations": [ + { + "to": [ + { + "email": "test+to0@example.com", + "name": "Example To Name" + } + ] + } + ], + "reply_to_list": [ + { + "email": "test+reply_to_1@example.com" + }, + { + "email": "test+reply_to_2@example.com" + } + ], + "subject": "Sending with SendGrid is Fun" + }''') + ) + def test_multiple_emails_to_multiple_recipients(self): from sendgrid.helpers.mail import (Mail, From, To, Subject, PlainTextContent, HtmlContent, @@ -568,6 +620,44 @@ def test_value_error_is_raised_on_to_emails_set_to_list_of_lists(self): 'and easy to do anywhere, even with Python'), html_content=HtmlContent( 'and easy to do anywhere, even with Python')) + + def test_value_error_is_raised_on_to_emails_set_to_reply_to_list_of_strs(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = [ + ('test+to0@example.com', 'Example To Name 0'), + ('test+to1@example.com', 'Example To Name 1') + ] + + mail = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + with self.assertRaises(ValueError): + mail.reply_to_list = ['test+reply_to0@example.com', 'test+reply_to1@example.com'] + + def test_value_error_is_raised_on_to_emails_set_to_reply_to_list_of_tuples(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = [ + ('test+to0@example.com', 'Example To Name 0'), + ('test+to1@example.com', 'Example To Name 1') + ] + + mail = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + with self.assertRaises(ValueError): + mail.reply_to_list = [('test+reply_to@example.com', 'Test Name')] def test_error_is_not_raised_on_to_emails_set_to_list_of_tuples(self): from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) diff --git a/use_cases/send_a_single_email_with_multiple_reply_to_addresses.md b/use_cases/send_a_single_email_with_multiple_reply_to_addresses.md new file mode 100644 index 000000000..55d6adf18 --- /dev/null +++ b/use_cases/send_a_single_email_with_multiple_reply_to_addresses.md @@ -0,0 +1,29 @@ +```python +import os +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail + +message = Mail( + from_email='from_email@example.com', + to_emails='to@example.com', + subject='Sending with Twilio SendGrid is Fun', + html_content='and easy to do anywhere, even with Python') +message.reply_to_list = [ + ReplyTo( + email='reply-to-1@example.com', + name="Reply To Name 1", + ), + ReplyTo( + email='reply-to-2@example.com', + name="Reply To Name 2", + ) +] +try: + sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message) + print(response.status_code) + print(response.body) + print(response.headers) +except Exception as e: + print(e) +``` From 9fe3e235d06475573b27fb521d31a592799cf8a6 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 22 Mar 2023 11:45:20 -0700 Subject: [PATCH 430/462] [Librarian] Version Bump --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9d8f79db..802270cd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,25 @@ # Change Log All notable changes to this project will be documented in this file. +[2023-03-22] Version 6.10.0 +--------------------------- +**Library - Feature** +- [PR #1062](https://github.com/sendgrid/sendgrid-python/pull/1062): Add reply_to_list functionality. Thanks to [@thepuzzlemaster](https://github.com/thepuzzlemaster)! +- [PR #1059](https://github.com/sendgrid/sendgrid-python/pull/1059): Add Python 3.11 to the testing. Thanks to [@cclauss](https://github.com/cclauss)! + +**Library - Miscellaneous** +- [PR #1065](https://github.com/sendgrid/sendgrid-python/pull/1065): Create GitHub Action to lint Python code. Thanks to [@cclauss](https://github.com/cclauss)! +- [PR #1064](https://github.com/sendgrid/sendgrid-python/pull/1064): Upgrade GitHub Action test-and-deploy.yml. Thanks to [@cclauss](https://github.com/cclauss)! +- [PR #1063](https://github.com/sendgrid/sendgrid-python/pull/1063): Upgrade GitHub Action pr-lint.yml. Thanks to [@cclauss](https://github.com/cclauss)! + +**Library - Test** +- [PR #1058](https://github.com/sendgrid/sendgrid-python/pull/1058): Adding misc as PR type. Thanks to [@rakatyal](https://github.com/rakatyal)! + +**Library - Docs** +- [PR #1055](https://github.com/sendgrid/sendgrid-python/pull/1055): Modify README.md in alignment with SendGrid Support. Thanks to [@garethpaul](https://github.com/garethpaul)! +- [PR #1052](https://github.com/sendgrid/sendgrid-python/pull/1052): Fix link that has drifted. Thanks to [@jonathanberger](https://github.com/jonathanberger)! + + [2022-03-09] Version 6.9.7 -------------------------- **Library - Chore** From 5e9687caf9ed643362220e239b71537ed539c535 Mon Sep 17 00:00:00 2001 From: Twilio Date: Wed, 22 Mar 2023 11:45:21 -0700 Subject: [PATCH 431/462] Release 6.10.0 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 59b5dedc7..466501d04 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.9.7' +__version__ = '6.10.0' From 2fe145956a1ee50355f5da8deab401e1e118c736 Mon Sep 17 00:00:00 2001 From: Seth Ammons Date: Mon, 17 Apr 2023 04:48:43 -0400 Subject: [PATCH 432/462] fix: removing codedev test dependency (#1066) Fix build by removing dead dependency. Per the announcment: https://about.codecov.io/blog/message-regarding-the-pypi-package/ coddev deprecated their pypi package on april 12, 2023 causing the build to break We no longer are registered at codedev: https://app.codecov.io/gh/sendgrid/sendgrid-python "This repo has been deactivated" --- README.rst | 4 +--- test/requirements.txt | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 93062ba66..e23e2300e 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ -|Tests Badge| |codecov| |Python Versions| |PyPI Version| |Docker Badge| |MIT licensed| |Twitter Follow| |GitHub contributors| |Open Source Helpers| +|Tests Badge| |Python Versions| |PyPI Version| |Docker Badge| |MIT licensed| |Twitter Follow| |GitHub contributors| |Open Source Helpers| **This library allows you to quickly and easily use the Twilio SendGrid Web API v3 via Python.** @@ -288,8 +288,6 @@ License :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/main.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/ .. |MIT licensed| image:: https://img.shields.io/badge/license-MIT-blue.svg diff --git a/test/requirements.txt b/test/requirements.txt index 17302b22e..859c2456d 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -2,7 +2,6 @@ pyyaml Flask==1.1.4 six coverage -codecov mock itsdangerous==1.1.0 markupsafe==1.1.1 From bcb4f3379ec07f8d337d7aaa63d461259ec5ee41 Mon Sep 17 00:00:00 2001 From: manisha1997 Date: Tue, 14 Nov 2023 15:44:25 +0530 Subject: [PATCH 433/462] feat: geolocation setter in sendgrid-python for GDPR compliance --- .github/workflows/lint-python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-python.yml b/.github/workflows/lint-python.yml index 1c5f53f23..ba064864c 100644 --- a/.github/workflows/lint-python.yml +++ b/.github/workflows/lint-python.yml @@ -13,4 +13,4 @@ jobs: steps: - uses: actions/checkout@v3 - run: pip install --user ruff - - run: ruff --format=github --ignore="E722,F40,F841" --line-length=681 --target-version=py37 . + - run: ruff --format=github --ignore="E722,F40,F841" --line-length=320 --target-version=py37 . From d3ddc8aa27707ec5e2b13a65dfa4c1e2bc837497 Mon Sep 17 00:00:00 2001 From: manisha1997 Date: Tue, 14 Nov 2023 15:46:52 +0530 Subject: [PATCH 434/462] revert changes --- .github/workflows/lint-python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-python.yml b/.github/workflows/lint-python.yml index ba064864c..1c5f53f23 100644 --- a/.github/workflows/lint-python.yml +++ b/.github/workflows/lint-python.yml @@ -13,4 +13,4 @@ jobs: steps: - uses: actions/checkout@v3 - run: pip install --user ruff - - run: ruff --format=github --ignore="E722,F40,F841" --line-length=320 --target-version=py37 . + - run: ruff --format=github --ignore="E722,F40,F841" --line-length=681 --target-version=py37 . From b5afcca6c2c4b29330f81f92b79bf2526645223a Mon Sep 17 00:00:00 2001 From: Manisha Singh Date: Fri, 24 Nov 2023 00:29:36 +0530 Subject: [PATCH 435/462] feat: geolocation setter in sendgrid-python for GDPR compliance (#1073) --- .github/workflows/lint-python.yml | 2 +- examples/dataresidency/set_region.py | 37 ++++++++++++++++++++++++++++ sendgrid/base_interface.py | 23 ++++++++++++++++- test/unit/test_sendgrid.py | 27 ++++++++++++++++++++ 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 examples/dataresidency/set_region.py create mode 100644 test/unit/test_sendgrid.py diff --git a/.github/workflows/lint-python.yml b/.github/workflows/lint-python.yml index 1c5f53f23..ace47f77d 100644 --- a/.github/workflows/lint-python.yml +++ b/.github/workflows/lint-python.yml @@ -13,4 +13,4 @@ jobs: steps: - uses: actions/checkout@v3 - run: pip install --user ruff - - run: ruff --format=github --ignore="E722,F40,F841" --line-length=681 --target-version=py37 . + - run: ruff --ignore="E722,F40,F841" --line-length=320 --target-version=py37 . diff --git a/examples/dataresidency/set_region.py b/examples/dataresidency/set_region.py new file mode 100644 index 000000000..9aae2611f --- /dev/null +++ b/examples/dataresidency/set_region.py @@ -0,0 +1,37 @@ +import sendgrid +import os + +from sendgrid import Email, To, Content, Mail + +# Example 1 +# setting region to be "global" + +sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) +from_email = Email("example@abc.com") +to_email = To("example@abc.com") +subject = "Sending with SendGrid is Fun" +content = Content("text/plain", "and easy to do anywhere, even with Python") +mail = Mail(from_email, to_email, subject, content) +sg.set_sendgrid_data_residency("global") +print(sg.client.host) +response = sg.client.mail.send.post(request_body=mail.get()) +print(response) +print(response.status_code) +print(response.body) +print(response.headers) + +# Example 2 +# setting region to "eu" +sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY_EU')) +sg.set_sendgrid_data_residency("eu") +from_email = Email("example@abc.com") +to_email = To("example@abc.com") +subject = "Sending with SendGrid is Fun" +content = Content("text/plain", "and easy to do anywhere, even with Python") +print(sg.client.host) +mail = Mail(from_email, to_email, subject, content) +response = sg.client.mail.send.post(request_body=mail.get()) +print(response) +print(response.status_code) +print(response.body) +print(response.headers) \ No newline at end of file diff --git a/sendgrid/base_interface.py b/sendgrid/base_interface.py index 92b38247e..f94f09479 100644 --- a/sendgrid/base_interface.py +++ b/sendgrid/base_interface.py @@ -1,5 +1,6 @@ import python_http_client +region_host_dict = {'eu':'https://api.eu.sendgrid.com','global':'https://api.sendgrid.com'} class BaseInterface(object): def __init__(self, auth, host, impersonate_subuser): @@ -22,10 +23,10 @@ def __init__(self, auth, host, impersonate_subuser): """ from . import __version__ self.auth = auth - self.host = host self.impersonate_subuser = impersonate_subuser self.version = __version__ self.useragent = 'sendgrid/{};python'.format(self.version) + self.host = host self.client = python_http_client.Client( host=self.host, @@ -60,3 +61,23 @@ def send(self, message): message = message.get() return self.client.mail.send.post(request_body=message) + + def set_sendgrid_data_residency(self, region): + """ + Client libraries contain setters for specifying region/edge. + This supports global and eu regions only. This set will likely expand in the future. + Global is the default residency (or region) + Global region means the message will be sent through https://api.sendgrid.com + EU region means the message will be sent through https://api.eu.sendgrid.com + :param region: string + :return: + """ + if region in region_host_dict.keys(): + self.host = region_host_dict[region] + if self._default_headers is not None: + self.client = python_http_client.Client( + host=self.host, + request_headers=self._default_headers, + version=3) + else: + raise ValueError("region can only be \"eu\" or \"global\"") diff --git a/test/unit/test_sendgrid.py b/test/unit/test_sendgrid.py new file mode 100644 index 000000000..328d978ab --- /dev/null +++ b/test/unit/test_sendgrid.py @@ -0,0 +1,27 @@ +import unittest +import sendgrid + +class UnitTests(unittest.TestCase): + def test_host_with_no_region(self): + sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY') + self.assertEqual("https://api.sendgrid.com",sg.client.host) + + def test_host_with_eu_region(self): + sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY') + sg.set_sendgrid_data_residency("eu") + self.assertEqual("https://api.eu.sendgrid.com",sg.client.host) + + def test_host_with_global_region(self): + sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY') + sg.set_sendgrid_data_residency("global") + self.assertEqual("https://api.sendgrid.com",sg.client.host) + + def test_with_region_is_none(self): + sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY') + with self.assertRaises(ValueError): + sg.set_sendgrid_data_residency(None) + + def test_with_region_is_invalid(self): + sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY') + with self.assertRaises(ValueError): + sg.set_sendgrid_data_residency("abc") \ No newline at end of file From 4804d32b17e9d396e319c1943792537dc1846008 Mon Sep 17 00:00:00 2001 From: Twilio Date: Fri, 1 Dec 2023 05:16:21 +0000 Subject: [PATCH 436/462] [Librarian] Version Bump --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 802270cd8..4415a03f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Change Log All notable changes to this project will be documented in this file. +[2023-12-01] Version 6.11.0 +--------------------------- +**Library - Feature** +- [PR #1073](https://github.com/sendgrid/sendgrid-python/pull/1073): geolocation setter in sendgrid-python for GDPR compliance. Thanks to [@manisha1997](https://github.com/manisha1997)! + +**Library - Test** +- [PR #1066](https://github.com/sendgrid/sendgrid-python/pull/1066): removing codedev test dependency. Thanks to [@sethgrid](https://github.com/sethgrid)! + + [2023-03-22] Version 6.10.0 --------------------------- **Library - Feature** From 4e4ac47e61f52b9ead5020f807f4eb151f9a0bc6 Mon Sep 17 00:00:00 2001 From: Twilio Date: Fri, 1 Dec 2023 05:16:22 +0000 Subject: [PATCH 437/462] Release 6.11.0 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 466501d04..901082d42 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.10.0' +__version__ = '6.11.0' From 78acb96d25a299ad862396c1fbc399c897998bfd Mon Sep 17 00:00:00 2001 From: manisha1997 Date: Tue, 26 Dec 2023 13:53:33 +0530 Subject: [PATCH 438/462] chore: commit to main to check if the commits go directly into main. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 663d1e977..d41c0a1c4 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ 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/)) From 9515dce6c8229c9cf7272701d0c2664c05727f16 Mon Sep 17 00:00:00 2001 From: manisha1997 Date: Tue, 26 Dec 2023 13:54:42 +0530 Subject: [PATCH 439/462] chore: rolling back direct commits --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index d41c0a1c4..663d1e977 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,6 @@ 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/)) From e2a2cfd7c0632ead3ecdeb725ec4798b711a7931 Mon Sep 17 00:00:00 2001 From: Shubham Date: Fri, 11 Apr 2025 15:00:51 +0530 Subject: [PATCH 440/462] chore: install docker-compose (#1099) * chore: install docker-compose * chore: update licence year * chore: fix lint-python --- .github/workflows/lint-python.yml | 2 +- .github/workflows/test-and-deploy.yml | 5 +++++ LICENSE | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint-python.yml b/.github/workflows/lint-python.yml index ace47f77d..5dcf0328c 100644 --- a/.github/workflows/lint-python.yml +++ b/.github/workflows/lint-python.yml @@ -13,4 +13,4 @@ jobs: steps: - uses: actions/checkout@v3 - run: pip install --user ruff - - run: ruff --ignore="E722,F40,F841" --line-length=320 --target-version=py37 . + - run: ruff check --ignore="E722,F40,F841" --line-length=320 --target-version=py37 . diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml index 4db532e3d..25408ac0a 100644 --- a/.github/workflows/test-and-deploy.yml +++ b/.github/workflows/test-and-deploy.yml @@ -31,6 +31,11 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_AUTH_TOKEN }} + - name: Install Docker Compose + run: | + sudo apt-get update + sudo apt-get install -y docker-compose + - name: Build & Test run: make test-docker version=${{ matrix.python-version }} diff --git a/LICENSE b/LICENSE index 3154774a9..126ceb1a3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (C) 2023, Twilio SendGrid, Inc. +Copyright (C) 2025, Twilio SendGrid, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From ee54d81051d45b0a667642866b74dc7efbc7155b Mon Sep 17 00:00:00 2001 From: Meysam Date: Tue, 15 Apr 2025 22:05:45 +0700 Subject: [PATCH 441/462] feat: add support for python3.12 and 3.13 (#1087) --- .github/workflows/test-and-deploy.yml | 2 +- Makefile | 6 ++--- setup.py | 2 ++ test/requirements.txt | 1 + tox.ini | 32 ++++++++++++++++++++++++++- 5 files changed, 37 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml index 25408ac0a..ba8b69905 100644 --- a/.github/workflows/test-and-deploy.yml +++ b/.github/workflows/test-and-deploy.yml @@ -17,7 +17,7 @@ jobs: timeout-minutes: 20 strategy: matrix: - python-version: [ '2.7', '3.5', '3.6', '3.7', '3.8', '3.9', '3.10', '3.11' ] + python-version: [ '2.7', '3.5', '3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12' , '3.13'] env: DOCKER_LOGIN: ${{ secrets.DOCKER_USERNAME && secrets.DOCKER_AUTH_TOKEN }} steps: diff --git a/Makefile b/Makefile index fb61004ce..96161106d 100644 --- a/Makefile +++ b/Makefile @@ -6,13 +6,11 @@ venv: clean virtualenv --python=python venv install: venv + . venv/bin/activate; pip install -r test/requirements.txt . venv/bin/activate; python setup.py install . venv/bin/activate; pip install -r requirements.txt -test-install: install - . venv/bin/activate; pip install -r test/requirements.txt - -test: test-install +test: install . venv/bin/activate; coverage run -m unittest discover -s test/unit test-integ: test diff --git a/setup.py b/setup.py index 41f11e589..7c797ae67 100644 --- a/setup.py +++ b/setup.py @@ -41,5 +41,7 @@ def getRequires(): 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', ] ) diff --git a/test/requirements.txt b/test/requirements.txt index 859c2456d..40552deba 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -5,3 +5,4 @@ coverage mock itsdangerous==1.1.0 markupsafe==1.1.1 +setuptools diff --git a/tox.ini b/tox.ini index 926a3c9dd..8f4f2db9a 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py27, py34, py35, py36, py37 +envlist = py27, py34, py35, py36, py37, py38, py39, py310, py311, py312, py313 [testenv] commands = coverage erase @@ -39,3 +39,33 @@ basepython = python3.6 commands = {[testenv]commands} deps = {[testenv]deps} basepython = python3.7 + +[testenv:py38] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.8 + +[testenv:py39] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.9 + +[testenv:py310] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.10 + +[testenv:py311] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.11 + +[testenv:py312] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.12 + +[testenv:py313] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.13 From 09bc8466ddf1ecd11a6dc7ebb7a8338fb5afca86 Mon Sep 17 00:00:00 2001 From: GPK Date: Fri, 25 Apr 2025 14:06:33 +0100 Subject: [PATCH 442/462] chore: Add werkzeug package to setup file (#1098) * Add werkzeug package to setup file * Add werkzeug package to setup file * Update versions * Remove EOL python versions --------- Co-authored-by: Shubham --- .github/workflows/test-and-deploy.yml | 2 +- setup.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml index ba8b69905..959970d94 100644 --- a/.github/workflows/test-and-deploy.yml +++ b/.github/workflows/test-and-deploy.yml @@ -17,7 +17,7 @@ jobs: timeout-minutes: 20 strategy: matrix: - python-version: [ '2.7', '3.5', '3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12' , '3.13'] + python-version: [ '3.9', '3.10', '3.11', '3.12' , '3.13'] env: DOCKER_LOGIN: ${{ secrets.DOCKER_USERNAME && secrets.DOCKER_AUTH_TOKEN }} steps: diff --git a/setup.py b/setup.py index 7c797ae67..0753e3997 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,11 @@ def getRequires(): deps = [ 'python_http_client>=3.2.1', - 'starkbank-ecdsa>=2.0.1' + 'starkbank-ecdsa>=2.0.1', + "werkzeug>=0.11.15,<1.0.0 ; python_version < '3.0'", + "werkzeug>=0.15.0,<2.0.0 ; python_version >= '3.0' and python_version < '3.6'", + "werkzeug>=2.0.0,<3.0.0 ; python_version >= '3.6' and python_version < '3.11'", + "werkzeug>=3.0.0 ; python_version >= '3.11'" ] return deps From ac693fe02295e5e0b77070bab6fa460c697f8997 Mon Sep 17 00:00:00 2001 From: Twilio Date: Mon, 5 May 2025 10:35:35 +0000 Subject: [PATCH 443/462] [Librarian] Version Bump --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4415a03f7..a2215795b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ # Change Log All notable changes to this project will be documented in this file. +[2025-05-05] Version 6.12.0 +--------------------------- +**Library - Chore** +- [PR #1098](https://github.com/sendgrid/sendgrid-python/pull/1098): Add werkzeug package to setup file. Thanks to [@gopidesupavan](https://github.com/gopidesupavan)! +- [PR #1099](https://github.com/sendgrid/sendgrid-python/pull/1099): install docker-compose. Thanks to [@tiwarishubham635](https://github.com/tiwarishubham635)! + +**Library - Feature** +- [PR #1087](https://github.com/sendgrid/sendgrid-python/pull/1087): add support for python3.12 and 3.13. Thanks to [@meysam81](https://github.com/meysam81)! + + [2023-12-01] Version 6.11.0 --------------------------- **Library - Feature** From 774663f5a0af011d23908d0dbd932cb64ee96d70 Mon Sep 17 00:00:00 2001 From: Twilio Date: Mon, 5 May 2025 10:35:35 +0000 Subject: [PATCH 444/462] Release 6.12.0 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 901082d42..142f2122e 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.11.0' +__version__ = '6.12.0' From 6eb4375f4d1ef1e0c1ad17d07858706291380ebc Mon Sep 17 00:00:00 2001 From: ranjanprasad1996 <141220652+ranjanprasad1996@users.noreply.github.com> Date: Fri, 9 May 2025 20:54:52 +0530 Subject: [PATCH 445/462] fix: Vulnerability fix for starkbank-ecdsa 2.2.0 dependency (#1085) * Updated webhook helper to use a different edcsa library * chore: dummy commit * Update dependencies --------- Co-authored-by: Shubham --- requirements.txt | 6 ++--- sendgrid/helpers/eventwebhook/__init__.py | 30 +++++++++++++---------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0c34aafd4..c95204480 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -Flask==1.1.2 +Flask==3.1.0 PyYAML>=4.2b1 python-http-client>=3.2.1 -six==1.11.0 -starkbank-ecdsa>=2.0.1 +six==1.17.0 +ecdsa>=0.19.1,<1 more-itertools==5.0.0 diff --git a/sendgrid/helpers/eventwebhook/__init__.py b/sendgrid/helpers/eventwebhook/__init__.py index c1ec7d1c8..d97604df8 100644 --- a/sendgrid/helpers/eventwebhook/__init__.py +++ b/sendgrid/helpers/eventwebhook/__init__.py @@ -1,8 +1,7 @@ -from ellipticcurve.ecdsa import Ecdsa -from ellipticcurve.publicKey import PublicKey -from ellipticcurve.signature import Signature - -from .eventwebhook_header import EventWebhookHeader +from ecdsa import VerifyingKey, BadSignatureError +from ecdsa.util import sigdecode_der +import base64 +import hashlib class EventWebhook: """ @@ -20,14 +19,15 @@ def __init__(self, public_key=None): def convert_public_key_to_ecdsa(self, public_key): """ - Convert the public key string to a ECPublicKey. + Convert the public key string to a VerifyingKey object. :param public_key: verification key under Mail Settings :type public_key string - :return: public key using the ECDSA algorithm - :rtype PublicKey + :return: VerifyingKey object using the ECDSA algorithm + :rtype VerifyingKey """ - return PublicKey.fromPem('\n-----BEGIN PUBLIC KEY-----\n'+public_key+'\n-----END PUBLIC KEY-----\n') + pem_key = "-----BEGIN PUBLIC KEY-----\n" + public_key + "\n-----END PUBLIC KEY-----" + return VerifyingKey.from_pem(pem_key) def verify_signature(self, payload, signature, timestamp, public_key=None): """ @@ -40,11 +40,15 @@ def verify_signature(self, payload, signature, timestamp, public_key=None): :param timestamp: value obtained from the 'X-Twilio-Email-Event-Webhook-Timestamp' header :type timestamp: string :param public_key: elliptic curve public key - :type public_key: PublicKey + :type public_key: VerifyingKey :return: true or false if signature is valid """ - timestamped_payload = timestamp + payload - decoded_signature = Signature.fromBase64(signature) + timestamped_payload = (timestamp + payload).encode('utf-8') + decoded_signature = base64.b64decode(signature) key = public_key or self.public_key - return Ecdsa.verify(timestamped_payload, decoded_signature, key) + try: + key.verify(decoded_signature, timestamped_payload, hashfunc=hashlib.sha256, sigdecode=sigdecode_der) + return True + except BadSignatureError: + return False From 096719d8daf5846664eb3f6b4649c826176a959c Mon Sep 17 00:00:00 2001 From: Twilio Date: Tue, 13 May 2025 09:44:12 +0000 Subject: [PATCH 446/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2215795b..2936c9a18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2025-05-13] Version 6.12.1 +--------------------------- +**Library - Fix** +- [PR #1085](https://github.com/sendgrid/sendgrid-python/pull/1085): Vulnerability fix for starkbank-ecdsa 2.2.0 dependency. Thanks to [@ranjanprasad1996](https://github.com/ranjanprasad1996)! + + [2025-05-05] Version 6.12.0 --------------------------- **Library - Chore** From 2acb010ae303cca78d696b82b739809b7f319187 Mon Sep 17 00:00:00 2001 From: Twilio Date: Tue, 13 May 2025 09:44:12 +0000 Subject: [PATCH 447/462] Release 6.12.1 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 142f2122e..431e91f2e 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.12.0' +__version__ = '6.12.1' From e1ed323bb0a595b7488fbb0c8d8351682ff97f55 Mon Sep 17 00:00:00 2001 From: Shubham Date: Tue, 13 May 2025 16:19:25 +0530 Subject: [PATCH 448/462] chore: update ecdsa in setup.py (#1102) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0753e3997..b3a8e5c86 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ def getRequires(): deps = [ 'python_http_client>=3.2.1', - 'starkbank-ecdsa>=2.0.1', + 'ecdsa>=0.19.1,<1', "werkzeug>=0.11.15,<1.0.0 ; python_version < '3.0'", "werkzeug>=0.15.0,<2.0.0 ; python_version >= '3.0' and python_version < '3.6'", "werkzeug>=2.0.0,<3.0.0 ; python_version >= '3.6' and python_version < '3.11'", From a1acbf3a53e5f5b122614718acbdc6a7196921a9 Mon Sep 17 00:00:00 2001 From: Twilio Date: Tue, 13 May 2025 10:51:11 +0000 Subject: [PATCH 449/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2936c9a18..607b465c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2025-05-13] Version 6.12.2 +--------------------------- +**Library - Chore** +- [PR #1102](https://github.com/sendgrid/sendgrid-python/pull/1102): update ecdsa in setup.py. Thanks to [@tiwarishubham635](https://github.com/tiwarishubham635)! + + [2025-05-13] Version 6.12.1 --------------------------- **Library - Fix** From 3f25a2a6ed33878e13cfe8d76d283b1d6c7b380a Mon Sep 17 00:00:00 2001 From: Twilio Date: Tue, 13 May 2025 10:51:12 +0000 Subject: [PATCH 450/462] Release 6.12.2 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 431e91f2e..67234a38e 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.12.1' +__version__ = '6.12.2' From a78bd11b854ab38978f73c7e3fda98110bd9f840 Mon Sep 17 00:00:00 2001 From: Elad Kalif <45845474+eladkal@users.noreply.github.com> Date: Wed, 14 May 2025 11:02:30 +0300 Subject: [PATCH 451/462] chore: Relax werkzeug version (#1103) --- setup.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b3a8e5c86..a1b1a9743 100644 --- a/setup.py +++ b/setup.py @@ -13,8 +13,11 @@ def getRequires(): 'ecdsa>=0.19.1,<1', "werkzeug>=0.11.15,<1.0.0 ; python_version < '3.0'", "werkzeug>=0.15.0,<2.0.0 ; python_version >= '3.0' and python_version < '3.6'", - "werkzeug>=2.0.0,<3.0.0 ; python_version >= '3.6' and python_version < '3.11'", - "werkzeug>=3.0.0 ; python_version >= '3.11'" + "werkzeug>=0.15.0,<2.3.0 ; python_version >= '3.0' and python_version < '3.7'", + "werkzeug>=0.16.0,<3.1.0 ; python_version >= '3.0' and python_version < '3.8'", + "werkzeug>=1.0.0 ; python_version >= '3.9'", + "werkzeug>=2.2.0 ; python_version >= '3.11'", + "werkzeug>=2.3.5 ; python_version >= '3.12'" ] return deps From 4671192ce266f575eca28c062766ff4194d66ae9 Mon Sep 17 00:00:00 2001 From: Elad Kalif <45845474+eladkal@users.noreply.github.com> Date: Wed, 14 May 2025 17:35:46 +0300 Subject: [PATCH 452/462] chore: fix werkzeug lower versions (#1104) --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index a1b1a9743..745edea81 100644 --- a/setup.py +++ b/setup.py @@ -12,9 +12,9 @@ def getRequires(): 'python_http_client>=3.2.1', 'ecdsa>=0.19.1,<1', "werkzeug>=0.11.15,<1.0.0 ; python_version < '3.0'", - "werkzeug>=0.15.0,<2.0.0 ; python_version >= '3.0' and python_version < '3.6'", - "werkzeug>=0.15.0,<2.3.0 ; python_version >= '3.0' and python_version < '3.7'", - "werkzeug>=0.16.0,<3.1.0 ; python_version >= '3.0' and python_version < '3.8'", + "werkzeug>=0.15.0,<2.0.0 ; python_version >= '3.0' and python_version < '3.7'", + "werkzeug>=0.15.0,<2.3.0 ; python_version >= '3.0' and python_version < '3.8'", # version 2.3.0 dropped support for Python 3.7 + "werkzeug>=0.16.0,<3.1.0 ; python_version >= '3.0' and python_version < '3.9'", # version 3.1.0 dropped support for Python 3.8 "werkzeug>=1.0.0 ; python_version >= '3.9'", "werkzeug>=2.2.0 ; python_version >= '3.11'", "werkzeug>=2.3.5 ; python_version >= '3.12'" From 3e9d4efac5d652de81fb82ae38d256aad78c00d5 Mon Sep 17 00:00:00 2001 From: Shubham Date: Fri, 23 May 2025 11:51:29 +0530 Subject: [PATCH 453/462] chore: export EventWebhookHeader (#1107) --- sendgrid/helpers/eventwebhook/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sendgrid/helpers/eventwebhook/__init__.py b/sendgrid/helpers/eventwebhook/__init__.py index d97604df8..82a2cd6b8 100644 --- a/sendgrid/helpers/eventwebhook/__init__.py +++ b/sendgrid/helpers/eventwebhook/__init__.py @@ -2,6 +2,7 @@ from ecdsa.util import sigdecode_der import base64 import hashlib +from .eventwebhook_header import EventWebhookHeader class EventWebhook: """ From a24ab9de13b2b182985860e5979b1368d2e9fa95 Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 29 May 2025 12:11:32 +0000 Subject: [PATCH 454/462] [Librarian] Version Bump --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 607b465c5..7a69e3f92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Change Log All notable changes to this project will be documented in this file. +[2025-05-29] Version 6.12.3 +--------------------------- +**Library - Chore** +- [PR #1107](https://github.com/sendgrid/sendgrid-python/pull/1107): export EventWebhookHeader. Thanks to [@tiwarishubham635](https://github.com/tiwarishubham635)! +- [PR #1104](https://github.com/sendgrid/sendgrid-python/pull/1104): fix werkzeug lower versions. Thanks to [@eladkal](https://github.com/eladkal)! +- [PR #1103](https://github.com/sendgrid/sendgrid-python/pull/1103): Relax werkzeug version. Thanks to [@eladkal](https://github.com/eladkal)! + + [2025-05-13] Version 6.12.2 --------------------------- **Library - Chore** From 159f5dd2d4a6eb360ca61356b009b37fcc6aa5c8 Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 29 May 2025 12:11:32 +0000 Subject: [PATCH 455/462] Release 6.12.3 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 67234a38e..67ae80f96 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.12.2' +__version__ = '6.12.3' From f5bf6153b5dabf6fe307a8acdb0e59ce17a0c07a Mon Sep 17 00:00:00 2001 From: Manisha Singh Date: Mon, 2 Jun 2025 16:40:55 +0530 Subject: [PATCH 456/462] chore: bug-fix (#1109) --- sendgrid/helpers/mail/mail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 2472ad7d3..e475fe764 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -567,7 +567,7 @@ def add_custom_arg(self, custom_arg): :param value: A CustomArg object or a dict of custom arg key/values :type value: CustomArg, dict """ - if custom_arg.personalization is not None: + if not isinstance(custom_arg, dict) and custom_arg.personalization is not None: try: personalization = \ self._personalizations[custom_arg.personalization] From 59994bfafc59fbe7fa2bb9338654e5e6a4e4f612 Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 12 Jun 2025 10:27:17 +0000 Subject: [PATCH 457/462] [Librarian] Version Bump --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a69e3f92..87dde6650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +[2025-06-12] Version 6.12.4 +--------------------------- +**Library - Chore** +- [PR #1109](https://github.com/sendgrid/sendgrid-python/pull/1109): bug-fix. Thanks to [@manisha1997](https://github.com/manisha1997)! + + [2025-05-29] Version 6.12.3 --------------------------- **Library - Chore** From 7eafe1854f42ef2d6863a7288c1c2e73f9aa5c82 Mon Sep 17 00:00:00 2001 From: Twilio Date: Thu, 12 Jun 2025 10:27:18 +0000 Subject: [PATCH 458/462] Release 6.12.4 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 67ae80f96..127e2ed69 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.12.3' +__version__ = '6.12.4' From af2c4e70b338cdf4ccca4ed12184fdda5926dcc6 Mon Sep 17 00:00:00 2001 From: Shubham Date: Thu, 11 Sep 2025 13:20:25 +0530 Subject: [PATCH 459/462] chore: use make-test instead of make test-docker (#1117) --- .github/workflows/test-and-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml index 959970d94..98bbeefbd 100644 --- a/.github/workflows/test-and-deploy.yml +++ b/.github/workflows/test-and-deploy.yml @@ -37,7 +37,7 @@ jobs: sudo apt-get install -y docker-compose - name: Build & Test - run: make test-docker version=${{ matrix.python-version }} + run: make test deploy: name: Deploy From 68288529c5e510e6fe535c42d7e52fea5922558a Mon Sep 17 00:00:00 2001 From: David Acevedo Date: Thu, 11 Sep 2025 02:57:07 -0500 Subject: [PATCH 460/462] fix: #1108 - Replace ecdsa with cryptography (#1114) Co-authored-by: Shubham --- CONTRIBUTING.md | 2 +- README.md | 2 +- README.rst | 4 ++-- requirements.txt | 2 +- sendgrid/helpers/eventwebhook/__init__.py | 21 +++++++++++---------- setup.py | 2 +- 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 890641059..af9507154 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,7 @@ We welcome direct contributions to the sendgrid-python code base. Thank you! - Python version 2.7, 3.5, 3.6, 3.7, or 3.8 - [python_http_client](https://github.com/sendgrid/python-http-client) -- [ecdsa_python](https://github.com/starkbank/ecdsa-python) +- [cryptography](https://github.com/pyca/cryptography) - [pyenv](https://github.com/yyuu/pyenv) - [tox](https://pypi.python.org/pypi/tox) diff --git a/README.md b/README.md index 663d1e977..b1b36686f 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ pip install sendgrid ## Dependencies - [Python-HTTP-Client](https://github.com/sendgrid/python-http-client) -- [ECDSA-Python](https://github.com/starkbank/ecdsa-python) +- [Cryptography](https://github.com/pyca/cryptography) diff --git a/README.rst b/README.rst index e23e2300e..526c4ca40 100644 --- a/README.rst +++ b/README.rst @@ -90,7 +90,7 @@ Dependencies ------------ - `Python-HTTP-Client`_ -- `ECDSA-Python`_ +- `Cryptography`_ Quick Start =========== @@ -259,7 +259,7 @@ License .. _Twilio account: https://www.twilio.com/try-twilio?source=sendgrid-python .. _SENDGRID_API_KEY: https://app.sendgrid.com/settings/api_keys .. _Python-HTTP-Client: https://github.com/sendgrid/python-http-client -.. _ECDSA-Python: https://github.com/starkbank/ecdsa-python +.. _Cryptography: https://github.com/pyca/cryptography .. _/mail/send Helper: https://github.com/sendgrid/sendgrid-python/tree/HEAD/sendgrid/helpers/mail .. _personalization object: https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/personalizations.html .. _Fluent Interface: https://sendgrid.com/blog/using-python-to-implement-a-fluent-interface-to-any-rest-api/ diff --git a/requirements.txt b/requirements.txt index c95204480..ed2594a90 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,5 +2,5 @@ Flask==3.1.0 PyYAML>=4.2b1 python-http-client>=3.2.1 six==1.17.0 -ecdsa>=0.19.1,<1 +cryptography>=45.0.6 more-itertools==5.0.0 diff --git a/sendgrid/helpers/eventwebhook/__init__.py b/sendgrid/helpers/eventwebhook/__init__.py index 82a2cd6b8..9d618bf3a 100644 --- a/sendgrid/helpers/eventwebhook/__init__.py +++ b/sendgrid/helpers/eventwebhook/__init__.py @@ -1,7 +1,8 @@ -from ecdsa import VerifyingKey, BadSignatureError -from ecdsa.util import sigdecode_der +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.serialization import load_pem_public_key import base64 -import hashlib from .eventwebhook_header import EventWebhookHeader class EventWebhook: @@ -20,15 +21,15 @@ def __init__(self, public_key=None): def convert_public_key_to_ecdsa(self, public_key): """ - Convert the public key string to a VerifyingKey object. + Convert the public key string to an EllipticCurvePublicKey object. :param public_key: verification key under Mail Settings :type public_key string - :return: VerifyingKey object using the ECDSA algorithm - :rtype VerifyingKey + :return: An EllipticCurvePublicKey object using the ECDSA algorithm + :rtype cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey """ pem_key = "-----BEGIN PUBLIC KEY-----\n" + public_key + "\n-----END PUBLIC KEY-----" - return VerifyingKey.from_pem(pem_key) + return load_pem_public_key(pem_key.encode("utf-8")) def verify_signature(self, payload, signature, timestamp, public_key=None): """ @@ -41,7 +42,7 @@ def verify_signature(self, payload, signature, timestamp, public_key=None): :param timestamp: value obtained from the 'X-Twilio-Email-Event-Webhook-Timestamp' header :type timestamp: string :param public_key: elliptic curve public key - :type public_key: VerifyingKey + :type public_key: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey :return: true or false if signature is valid """ timestamped_payload = (timestamp + payload).encode('utf-8') @@ -49,7 +50,7 @@ def verify_signature(self, payload, signature, timestamp, public_key=None): key = public_key or self.public_key try: - key.verify(decoded_signature, timestamped_payload, hashfunc=hashlib.sha256, sigdecode=sigdecode_der) + key.verify(decoded_signature, timestamped_payload, ec.ECDSA(hashes.SHA256())) return True - except BadSignatureError: + except InvalidSignature: return False diff --git a/setup.py b/setup.py index 745edea81..904cd654a 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ def getRequires(): deps = [ 'python_http_client>=3.2.1', - 'ecdsa>=0.19.1,<1', + 'cryptography>=45.0.6', "werkzeug>=0.11.15,<1.0.0 ; python_version < '3.0'", "werkzeug>=0.15.0,<2.0.0 ; python_version >= '3.0' and python_version < '3.7'", "werkzeug>=0.15.0,<2.3.0 ; python_version >= '3.0' and python_version < '3.8'", # version 2.3.0 dropped support for Python 3.7 From 5e26ead73a8d908550b4fab3166ab41697030c9c Mon Sep 17 00:00:00 2001 From: Shubham Tiwari Date: Fri, 19 Sep 2025 11:47:28 +0530 Subject: [PATCH 461/462] [Librarian] Version Bump --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87dde6650..67d327649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Change Log All notable changes to this project will be documented in this file. +[2025-09-19] Version 6.12.5 +--------------------------- +**Library - Fix** +- [PR #1114](https://github.com/sendgrid/sendgrid-python/pull/1114): #1108 - Replace ecdsa with cryptography. Thanks to [@dacevedo12](https://github.com/dacevedo12)! + +**Library - Chore** +- [PR #1117](https://github.com/sendgrid/sendgrid-python/pull/1117): use make-test instead of make test-docker. Thanks to [@tiwarishubham635](https://github.com/tiwarishubham635)! + + [2025-06-12] Version 6.12.4 --------------------------- **Library - Chore** From 76788e70a76b11ce2990821f190e52f887ab9ed6 Mon Sep 17 00:00:00 2001 From: Shubham Tiwari Date: Fri, 19 Sep 2025 11:48:40 +0530 Subject: [PATCH 462/462] Release 6.12.5 --- sendgrid/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sendgrid/version.py b/sendgrid/version.py index 127e2ed69..c1d623f90 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.12.4' +__version__ = '6.12.5'