aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/sendgrid
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/sendgrid
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are hereHEADmaster
Diffstat (limited to '.venv/lib/python3.12/site-packages/sendgrid')
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/__init__.py24
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/base_interface.py83
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/__init__.py1
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/endpoints/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/endpoints/ip/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/endpoints/ip/unassigned.py59
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/eventwebhook/__init__.py50
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/eventwebhook/eventwebhook_header.py10
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/__init__.py13
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/app.py45
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/config.py65
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/config.yml34
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/parse.py100
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/sample_data/default_data.txt58
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/sample_data/default_data_with_attachments.txt0
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/sample_data/raw_data.txt57
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/sample_data/raw_data_with_attachments.txt298
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/send.py61
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/__init__.py63
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/amp_html_content.py59
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/asm.py80
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/attachment.py218
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/batch_id.py50
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bcc_email.py5
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bcc_settings.py72
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bcc_settings_email.py40
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bypass_bounce_management.py48
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bypass_list_management.py48
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bypass_spam_management.py47
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bypass_unsubscribe_management.py49
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/category.py40
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/cc_email.py5
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/click_tracking.py71
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/content.py81
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/content_id.py50
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/custom_arg.py94
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/disposition.py72
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/dynamic_template_data.py73
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/email.py228
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/exceptions.py65
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/file_content.py39
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/file_name.py39
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/file_type.py39
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/footer_html.py39
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/footer_settings.py94
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/footer_text.py39
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/from_email.py5
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/ganalytics.py176
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/group_id.py39
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/groups_to_display.py48
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/header.py94
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/html_content.py59
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/ip_pool_name.py40
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/mail.py1041
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/mail_settings.py243
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/mime_type.py6
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/open_tracking.py80
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/open_tracking_substitution_tag.py49
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/personalization.py271
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/plain_text_content.py60
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/reply_to.py5
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/sandbox_mode.py44
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/section.py64
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/send_at.py79
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/spam_check.py112
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/spam_threshold.py53
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/spam_url.py44
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subject.py69
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subscription_html.py45
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subscription_substitution_tag.py58
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subscription_text.py45
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subscription_tracking.py142
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/substitution.py90
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/template_id.py39
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/to_email.py5
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/tracking_settings.py134
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_campaign.py40
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_content.py40
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_medium.py40
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_source.py43
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_term.py40
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/validators.py69
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/stats/__init__.py1
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/helpers/stats/stats.py384
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/sendgrid.py58
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/twilio_email.py73
-rw-r--r--.venv/lib/python3.12/site-packages/sendgrid/version.py1
87 files changed, 6833 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/__init__.py b/.venv/lib/python3.12/site-packages/sendgrid/__init__.py
new file mode 100644
index 00000000..cd994dd2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/__init__.py
@@ -0,0 +1,24 @@
+"""
+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
+
+Available subpackages
+---------------------
+helpers
+ Modules to help with common tasks.
+"""
+
+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/.venv/lib/python3.12/site-packages/sendgrid/base_interface.py b/.venv/lib/python3.12/site-packages/sendgrid/base_interface.py
new file mode 100644
index 00000000..f94f0947
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/base_interface.py
@@ -0,0 +1,83 @@
+import python_http_client
+
+region_host_dict = {'eu':'https://api.eu.sendgrid.com','global':'https://api.sendgrid.com'}
+
+class BaseInterface(object):
+ def __init__(self, auth, host, impersonate_subuser):
+ """
+ Construct the Twilio SendGrid v3 API object.
+ Note that the underlying client is being set up during initialization,
+ therefore changing attributes in runtime will not affect HTTP client
+ behaviour.
+
+ :param auth: the authorization header
+ :type auth: string
+ :param impersonate_subuser: the subuser to impersonate. Will be passed
+ by "On-Behalf-Of" header by underlying
+ client. See
+ https://sendgrid.com/docs/User_Guide/Settings/subusers.html
+ for more details
+ :type impersonate_subuser: string
+ :param host: base URL for API calls
+ :type host: string
+ """
+ from . import __version__
+ self.auth = auth
+ self.impersonate_subuser = impersonate_subuser
+ self.version = __version__
+ self.useragent = 'sendgrid/{};python'.format(self.version)
+ self.host = host
+
+ self.client = python_http_client.Client(
+ host=self.host,
+ request_headers=self._default_headers,
+ version=3)
+
+ @property
+ def _default_headers(self):
+ """Set the default header for a Twilio SendGrid v3 API call"""
+ headers = {
+ "Authorization": self.auth,
+ "User-Agent": self.useragent,
+ "Accept": 'application/json'
+ }
+ if self.impersonate_subuser:
+ headers['On-Behalf-Of'] = self.impersonate_subuser
+
+ return headers
+
+ def reset_request_headers(self):
+ self.client.request_headers = self._default_headers
+
+ def send(self, message):
+ """Make a Twilio SendGrid v3 API request with the request body generated by
+ the Mail object
+
+ :param message: The Twilio SendGrid v3 API request body generated by the Mail
+ object
+ :type message: Mail
+ """
+ if not isinstance(message, dict):
+ message = message.get()
+
+ return self.client.mail.send.post(request_body=message)
+
+ def set_sendgrid_data_residency(self, region):
+ """
+ Client libraries contain setters for specifying region/edge.
+ This supports global and eu regions only. This set will likely expand in the future.
+ Global is the default residency (or region)
+ Global region means the message will be sent through https://api.sendgrid.com
+ EU region means the message will be sent through https://api.eu.sendgrid.com
+ :param region: string
+ :return:
+ """
+ if region in region_host_dict.keys():
+ self.host = region_host_dict[region]
+ if self._default_headers is not None:
+ self.client = python_http_client.Client(
+ host=self.host,
+ request_headers=self._default_headers,
+ version=3)
+ else:
+ raise ValueError("region can only be \"eu\" or \"global\"")
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/__init__.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/__init__.py
new file mode 100644
index 00000000..fb29c5e2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/__init__.py
@@ -0,0 +1 @@
+"""Modules to help with SendGrid v3 API common tasks."""
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/endpoints/__init__.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/endpoints/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/endpoints/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/endpoints/ip/__init__.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/endpoints/ip/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/endpoints/ip/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/endpoints/ip/unassigned.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/endpoints/ip/unassigned.py
new file mode 100644
index 00000000..816050d3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/endpoints/ip/unassigned.py
@@ -0,0 +1,59 @@
+import json
+
+
+def format_ret(return_set, as_json=False):
+ """ decouple, allow for modifications to return type
+ returns a list of ip addresses in object or json form """
+ ret_list = list()
+ for item in return_set:
+ d = {"ip": item}
+ ret_list.append(d)
+
+ if as_json:
+ return json.dumps(ret_list)
+
+ return ret_list
+
+
+def unassigned(data, as_json=False):
+ """ https://sendgrid.com/docs/API_Reference/api_v3.html#ip-addresses
+ The /ips rest endpoint returns information about the IP addresses
+ and the usernames assigned to an IP
+
+ unassigned returns a listing of the IP addresses that are allocated
+ but have 0 users assigned
+
+
+ data (response.body from sg.client.ips.get())
+ as_json False -> get list of dicts
+ True -> get json object
+
+ example:
+ sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
+
+ params = {
+ 'subuser': 'test_string',
+ 'ip': 'test_string',
+ 'limit': 1,
+ 'exclude_whitelabels':
+ 'true', 'offset': 1
+ }
+ response = sg.client.ips.get(query_params=params)
+ if response.status_code == 201:
+ data = response.body
+ unused = unassigned(data)
+ """
+
+ no_subusers = set()
+
+ if not isinstance(data, list):
+ return format_ret(no_subusers, as_json=as_json)
+
+ for current in data:
+ num_subusers = len(current["subusers"])
+ if num_subusers == 0:
+ current_ip = current["ip"]
+ no_subusers.add(current_ip)
+
+ ret_val = format_ret(no_subusers, as_json=as_json)
+ return ret_val
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/eventwebhook/__init__.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/eventwebhook/__init__.py
new file mode 100644
index 00000000..c1ec7d1c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/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('\n-----BEGIN PUBLIC KEY-----\n'+public_key+'\n-----END PUBLIC KEY-----\n')
+
+ 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/.venv/lib/python3.12/site-packages/sendgrid/helpers/eventwebhook/eventwebhook_header.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/eventwebhook/eventwebhook_header.py
new file mode 100644
index 00000000..a41a4852
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/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/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/__init__.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/__init__.py
new file mode 100644
index 00000000..85ab4d0b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/__init__.py
@@ -0,0 +1,13 @@
+"""
+Inbound Parse helper
+--------------------
+This is a standalone module to help get you started consuming and processing
+Inbound Parse data. It provides a Flask server to listen for Inbound Parse
+POSTS, and utilities to send sample data to the server.
+
+See README.txt for detailed usage instructions, including quick-start guides
+for local testing and Heroku deployment.
+"""
+
+from .config import * # noqa
+from .parse import * # noqa
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/app.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/app.py
new file mode 100644
index 00000000..0d443590
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/app.py
@@ -0,0 +1,45 @@
+"""Receiver module for processing SendGrid Inbound Parse messages.
+
+See README.txt for usage instructions."""
+try:
+ from config import Config
+except:
+ # Python 3+, Travis
+ from sendgrid.helpers.inbound.config import Config
+
+try:
+ from parse import Parse
+except:
+ # Python 3+, Travis
+ from sendgrid.helpers.inbound.parse import Parse
+
+from flask import Flask, request, render_template
+import os
+
+app = Flask(__name__)
+config = Config()
+
+
+@app.route('/', methods=['GET'])
+def index():
+ """Show index page to confirm that server is running."""
+ return render_template('index.html')
+
+
+@app.route(config.endpoint, methods=['POST'])
+def inbound_parse():
+ """Process POST from Inbound Parse and print received data."""
+ parse = Parse(config, request)
+ # Sample processing action
+ print(parse.key_values())
+ # Tell SendGrid's Inbound Parse to stop sending POSTs
+ # Everything is 200 OK :)
+ return "OK"
+
+
+if __name__ == '__main__':
+ # Be sure to set config.debug_mode to False in production
+ port = int(os.environ.get("PORT", config.port))
+ if port != config.port:
+ config.debug = False
+ app.run(host='0.0.0.0', debug=config.debug_mode, port=port)
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/config.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/config.py
new file mode 100644
index 00000000..06ca683c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/config.py
@@ -0,0 +1,65 @@
+"""Set up credentials (.env) and application variables (config.yml)"""
+import os
+import yaml
+
+
+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()
+
+ """Allow variables assigned in config.yml available the following variables
+ via properties"""
+ self.path = opts.get(
+ 'path', os.path.abspath(os.path.dirname(__file__))
+ )
+ with open('{0}/config.yml'.format(self.path)) as stream:
+ config = yaml.load(stream, Loader=yaml.FullLoader)
+ self._debug_mode = config['debug_mode']
+ self._endpoint = config['endpoint']
+ self._host = config['host']
+ self._keys = config['keys']
+ self._port = config['port']
+
+ @staticmethod
+ def init_environment():
+ """Allow variables assigned in .env available using
+ os.environ.get('VAR_NAME')"""
+ base_path = os.path.abspath(os.path.dirname(__file__))
+ env_path = '{0}/.env'.format(base_path)
+ if os.path.exists(env_path):
+ with open(env_path) as f:
+ lines = f.readlines()
+ for line in lines:
+ var = line.strip().split('=')
+ if len(var) == 2:
+ os.environ[var[0]] = var[1]
+
+ @property
+ def debug_mode(self):
+ """Flask debug mode - set to False in production."""
+ return self._debug_mode
+
+ @property
+ def endpoint(self):
+ """Endpoint to receive Inbound Parse POSTs."""
+ return self._endpoint
+
+ @property
+ def host(self):
+ """URL that the sender will POST to."""
+ return self._host
+
+ @property
+ def keys(self):
+ """Incoming Parse fields to parse. For reference, see
+ https://sendgrid.com/docs/Classroom/Basics/Inbound_Parse_Webhook/setting_up_the_inbound_parse_webhook.html
+ """
+ return self._keys
+
+ @property
+ def port(self):
+ """Port to listen on."""
+ return self._port
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/config.yml b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/config.yml
new file mode 100644
index 00000000..d1a131ae
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/config.yml
@@ -0,0 +1,34 @@
+# Incoming Parse endpoint
+endpoint: '/inbound'
+
+# Port to listen on
+port: 5000
+
+# Flask debug mode
+# Set this to False in production
+# Reference: http://flask.pocoo.org/docs/0.11/api/#flask.Flask.run
+debug_mode: True
+
+# List all Incoming Parse fields you would like parsed
+# Reference: https://sendgrid.com/docs/Classroom/Basics/Inbound_Parse_Webhook/setting_up_the_inbound_parse_webhook.html
+keys:
+ - from
+ - attachments
+ - headers
+ - text
+ - envelope
+ - to
+ - html
+ - sender_ip
+ - attachment-info
+ - subject
+ - dkim
+ - SPF
+ - charsets
+ - content-ids
+ - spam_report
+ - spam_score
+ - email
+
+# URL that the sender will POST to
+host: 'http://127.0.0.1:5000/inbound'
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/parse.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/parse.py
new file mode 100644
index 00000000..49627a12
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/parse.py
@@ -0,0 +1,100 @@
+"""Parse data received from the SendGrid Inbound Parse webhook"""
+import base64
+import email
+import mimetypes
+from six import iteritems
+from werkzeug.utils import secure_filename
+
+
+class Parse(object):
+
+ def __init__(self, config, request):
+ self._keys = config.keys
+ self._request = request
+ request.get_data(as_text=True)
+ self._payload = request.form
+ self._raw_payload = request.data
+
+ def key_values(self):
+ """
+ Return a dictionary of key/values in the payload received from
+ the webhook
+ """
+ key_values = {}
+ for key in self.keys:
+ if key in self.payload:
+ key_values[key] = self.payload[key]
+ return key_values
+
+ def get_raw_email(self):
+ """
+ This only applies to raw payloads:
+ https://sendgrid.com/docs/Classroom/Basics/Inbound_Parse_Webhook/setting_up_the_inbound_parse_webhook.html#-Raw-Parameters
+ """
+ if 'email' in self.payload:
+ raw_email = email.message_from_string(self.payload['email'])
+ return raw_email
+ else:
+ return None
+
+ def attachments(self):
+ """Returns an object with:
+ type = file content type
+ file_name = the name of the file
+ contents = base64 encoded file contents"""
+ attachments = None
+ if 'attachment-info' in self.payload:
+ attachments = self._get_attachments(self.request)
+ # Check if we have a raw message
+ raw_email = self.get_raw_email()
+ if raw_email is not None:
+ attachments = self._get_attachments_raw(raw_email)
+ return attachments
+
+ def _get_attachments(self, request):
+ attachments = []
+ for _, filestorage in iteritems(request.files):
+ attachment = {}
+ if filestorage.filename not in (None, 'fdopen', '<fdopen>'):
+ filename = secure_filename(filestorage.filename)
+ attachment['type'] = filestorage.content_type
+ attachment['file_name'] = filename
+ attachment['contents'] = base64.b64encode(filestorage.read())
+ attachments.append(attachment)
+ return attachments
+
+ def _get_attachments_raw(self, raw_email):
+ attachments = []
+ counter = 1
+ for part in raw_email.walk():
+ attachment = {}
+ if part.get_content_maintype() == 'multipart':
+ continue
+ filename = part.get_filename()
+ if not filename:
+ ext = mimetypes.guess_extension(part.get_content_type())
+ if not ext:
+ ext = '.bin'
+ filename = 'part-%03d%s' % (counter, ext)
+ counter += 1
+ attachment['type'] = part.get_content_type()
+ attachment['file_name'] = filename
+ attachment['contents'] = part.get_payload(decode=False)
+ attachments.append(attachment)
+ return attachments
+
+ @property
+ def keys(self):
+ return self._keys
+
+ @property
+ def request(self):
+ return self._request
+
+ @property
+ def payload(self):
+ return self._payload
+
+ @property
+ def raw_payload(self):
+ return self._raw_payload
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/sample_data/default_data.txt b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/sample_data/default_data.txt
new file mode 100644
index 00000000..7c3ce6be
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/sample_data/default_data.txt
@@ -0,0 +1,58 @@
+--xYzZY
+Content-Disposition: form-data; name="headers"
+
+MIME-Version: 1.0
+Received: by 0.0.0.0 with HTTP; Wed, 10 Aug 2016 18:10:13 -0700 (PDT)
+From: Example User <test@example.com>
+Date: Wed, 10 Aug 2016 18:10:13 -0700
+Subject: Inbound Parse Test Data
+To: inbound@inbound.example.com
+Content-Type: multipart/alternative; boundary=001a113df448cad2d00539c16e89
+
+--xYzZY
+Content-Disposition: form-data; name="dkim"
+
+{@sendgrid.com : pass}
+--xYzZY
+Content-Disposition: form-data; name="to"
+
+inbound@inbound.example.com
+--xYzZY
+Content-Disposition: form-data; name="html"
+
+<html><body><strong>Hello Twilio SendGrid!</body></html>
+
+--xYzZY
+Content-Disposition: form-data; name="from"
+
+Example User <test@example.com>
+--xYzZY
+Content-Disposition: form-data; name="text"
+
+Hello Twilio SendGrid!
+
+--xYzZY
+Content-Disposition: form-data; name="sender_ip"
+
+0.0.0.0
+--xYzZY
+Content-Disposition: form-data; name="envelope"
+
+{"to":["inbound@inbound.example.com"],"from":"test@example.com"}
+--xYzZY
+Content-Disposition: form-data; name="attachments"
+
+0
+--xYzZY
+Content-Disposition: form-data; name="subject"
+
+Testing non-raw
+--xYzZY
+Content-Disposition: form-data; name="charsets"
+
+{"to":"UTF-8","html":"UTF-8","subject":"UTF-8","from":"UTF-8","text":"UTF-8"}
+--xYzZY
+Content-Disposition: form-data; name="SPF"
+
+pass
+--xYzZY-- \ No newline at end of file
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/sample_data/default_data_with_attachments.txt b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/sample_data/default_data_with_attachments.txt
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/sample_data/default_data_with_attachments.txt
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/sample_data/raw_data.txt b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/sample_data/raw_data.txt
new file mode 100644
index 00000000..12f64cb4
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/sample_data/raw_data.txt
@@ -0,0 +1,57 @@
+--xYzZY
+Content-Disposition: form-data; name="dkim"
+
+{@sendgrid.com : pass}
+--xYzZY
+Content-Disposition: form-data; name="email"
+
+MIME-Version: 1.0
+Received: by 0.0.0.0 with HTTP; Wed, 10 Aug 2016 14:44:21 -0700 (PDT)
+From: Example User <test@example.com>
+Date: Wed, 10 Aug 2016 14:44:21 -0700
+Subject: Inbound Parse Test Raw Data
+To: inbound@inbound.inbound.com
+Content-Type: multipart/alternative; boundary=001a113ee97c89842f0539be8e7a
+
+--001a113ee97c89842f0539be8e7a
+Content-Type: text/plain; charset=UTF-8
+
+Hello Twilio SendGrid!
+
+--001a113ee97c89842f0539be8e7a
+Content-Type: text/html; charset=UTF-8
+Content-Transfer-Encoding: quoted-printable
+
+<html><body><strong>Hello Twilio SendGrid!</body></html>
+
+--001a113ee97c89842f0539be8e7a--
+
+--xYzZY
+Content-Disposition: form-data; name="to"
+
+inbound@inbound.inbound.com
+--xYzZY
+Content-Disposition: form-data; name="from"
+
+Example User <test@example.com>
+--xYzZY
+Content-Disposition: form-data; name="sender_ip"
+
+0.0.0.0
+--xYzZY
+Content-Disposition: form-data; name="envelope"
+
+{"to":["inbound@inbound.inbound.com"],"from":"test@example.com"}
+--xYzZY
+Content-Disposition: form-data; name="subject"
+
+Testing with Request.bin
+--xYzZY
+Content-Disposition: form-data; name="charsets"
+
+{"to":"UTF-8","subject":"UTF-8","from":"UTF-8"}
+--xYzZY
+Content-Disposition: form-data; name="SPF"
+
+pass
+--xYzZY-- \ No newline at end of file
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/sample_data/raw_data_with_attachments.txt b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/sample_data/raw_data_with_attachments.txt
new file mode 100644
index 00000000..058fd8a6
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/sample_data/raw_data_with_attachments.txt
@@ -0,0 +1,298 @@
+--xYzZY
+Content-Disposition: form-data; name="dkim"
+
+{@sendgrid.com : pass}
+--xYzZY
+Content-Disposition: form-data; name="email"
+
+MIME-Version: 1.0
+Received: by 0.0.0.0 with HTTP; Mon, 15 Aug 2016 13:47:21 -0700 (PDT)
+From: Example User <test@example.com>
+Date: Mon, 15 Aug 2016 13:47:21 -0700
+Subject: Inbound Parse Test Raw Data with Attachment
+To: inbound@inbound.inbound.com
+Content-Type: multipart/mixed; boundary=001a1140ffb6f4fc63053a2257e2
+
+--001a1140ffb6f4fc63053a2257e2
+Content-Type: multipart/alternative; boundary=001a1140ffb6f4fc5f053a2257e0
+
+--001a1140ffb6f4fc5f053a2257e0
+Content-Type: text/plain; charset=UTF-8
+
+Hello Twilio SendGrid!
+
+--001a1140ffb6f4fc5f053a2257e0
+Content-Type: text/html; charset=UTF-8
+Content-Transfer-Encoding: quoted-printable
+
+<html><body><strong>Hello Twilio SendGrid!</body></html>
+
+--001a1140ffb6f4fc5f053a2257e0--
+
+--001a1140ffb6f4fc63053a2257e2
+Content-Type: image/jpeg; name="SendGrid.jpg"
+Content-Disposition: attachment; filename="SendGrid.jpg"
+Content-Transfer-Encoding: base64
+X-Attachment-Id: f_irwihell0
+
+/9j/4AAQSkZJRgABAQABLAEsAAD/4QDKRXhpZgAATU0AKgAAAAgABwESAAMA
+AAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAABAAAAagEoAAMAAAABAAIAAAEx
+AAIAAAARAAAAcgEyAAIAAAAUAAAAhIdpAAQAAAABAAAAmAAAAAAAAAEsAAAA
+AQAAASwAAAABUGl4ZWxtYXRvciAzLjQuNAAAMjAxNjowODoxMSAxNjowODo1
+NwAAA6ABAAMAAAABAAEAAKACAAQAAAABAAACEqADAAQAAAABAAACFQAAAAD/
+4Qn2aHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVn
+aW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4
+bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAg
+Q29yZSA1LjQuMCI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53
+My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3Jp
+cHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2Jl
+LmNvbS94YXAvMS4wLyIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9l
+bGVtZW50cy8xLjEvIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxNi0wOC0xMVQxNjow
+ODo1NyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBJbWFnZVJlYWR5Ij4gPGRj
+OnN1YmplY3Q+IDxyZGY6QmFnLz4gPC9kYzpzdWJqZWN0PiA8L3JkZjpEZXNj
+cmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
+ICAgICAgICAgICA8P3hwYWNrZXQgZW5kPSJ3Ij8+AP/tADhQaG90b3Nob3Ag
+My4wADhCSU0EBAAAAAAAADhCSU0EJQAAAAAAENQdjNmPALIE6YAJmOz4Qn7/
+wAARCAIVAhIDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQF
+BgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJx
+FDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdI
+SUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKj
+pKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx
+8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QA
+tREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHB
+CSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldY
+WVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmq
+srO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6
+/9sAQwAcHBwcHBwwHBwwRDAwMERcRERERFx0XFxcXFx0jHR0dHR0dIyMjIyM
+jIyMqKioqKioxMTExMTc3Nzc3Nzc3Nzc/9sAQwEiJCQ4NDhgNDRg5pyAnObm
+5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm
+5ubm/90ABAAi/9oADAMBAAIRAxEAPwDpKKKKACiiigAooooAKKKKACiiigAo
+oooAKKKKACiiigAooooAKKKKACiiigDEnkkEzgMep71F5sn94/nTp/8AXP8A
+U1FXWloeZJu7H+bJ/eP50ebJ/eP50yinYm7H+bJ/eP50ebJ/eP50yiiwXY/z
+ZP7x/OjzZP7x/OmUUWC7H+bJ/eP50ebJ/eP50yiiwXY/zZP7x/OjzZP7x/Om
+UUWC7H+bJ/eP50ebJ/eP50yiiwXY/wA2T+8fzo82T+8fzplFFgux/myf3j+d
+Hmyf3j+dMoosF2P82T+8fzo82T+8fzplFFgux/myf3j+dHmyf3j+dMoosF2I
+0sufvt+dJ5sv99vzNMbrSVVkbpuxJ5sv99vzNHmy/wB9vzNR0U7Id2SebL/f
+b8zR5sv99vzNR0UWQXZJ5sv99vzNHmy/32/M1HRRZBdknmy/32/M0ebL/fb8
+zUdFFkF2SebL/fb8zR5sv99vzNR0UWQXZJ5sv99vzNHmy/32/M1HRRZBdknm
+y/32/M0ebL/fb8zUdFFkF2SebL/fb8zR5sv99vzNR0UWQXZJ5sv99vzNHmy/
+32/M1HRRZBdknmy/32/M0ebL/fb8zUdFFkF2SebL/fb8zR5sv99vzNR0UWQX
+Z//Q6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo
+oooAwZ/9c/1NRVLP/rn+pqKuxbHly3YUUUUyQooooAKKKKACiiigAooooAKK
+KKACiiigAooooAKKKKACiiigCJutJSt1pKo3WwUUUUDCiiigAooooAKKKKAC
+iiigAooooAKKKKACiiigAooooAKKKKACiiigD//R6SiiigAooooAKKKKACii
+igAooooAKKKKACiiigAooooAKKKKACiiigAooooAwZ/9c/1NRVLP/rn+pqKu
+xbHly3YUUUUyQooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiig
+CJutJSt1pKo3WwUUUUDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooo
+oAKKKKACiiigD//S6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA
+KKKKACiiigAooooAwZ/9c/1NRVLP/rn+pqKuxbHly3YUUUUyQooooAKKKKAC
+iiigAooooAKKKKACiiigAooooAKKKKACiiigCJutJSt1pKo3WwUUUUDCiiig
+AooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//T6SiiigAo
+oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAwZ/9c/1N
+RVLP/rn+pqKuxbHly3YUUUUyQooooAKKKKACiiigAooooAKKKKACiiigAooo
+oAKKKKACiiigCJutJSt1pKo3WwUUUUDCiiigAooooAKKKKACiiigAooooAKK
+KKACiiigAooooAKKKKACiiigD//U6SiiigAooooAKKKKACiiigAooooAKKKK
+ACiiigAooooAKKKKACiiigAooooAwZ/9c/1NRVLP/rn+pqKuxbHly3YUUUUy
+QooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigCJutJSt1pKo3
+WwUUUUDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiig
+D//V6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAo
+oooAwZ/9c/1NRVLP/rn+pqKuxbHly3YUUUUyQooooAKKKKACiiigAooooAKK
+KKACiiigAooooAKKKKACiiigCJutJSt1pKo3WwUUUUDCiiigAooooAKKKKAC
+iiigAooooAKKKKACiiigAooooAKKKKACiiigD//W6SiiigAooooAKKKKACii
+igAooooAKKKKACiiigAooooAKKKKACiiigAooooAwZ/9c/1NRVLP/rn+pqKu
+xbHly3YUUUUyQooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiig
+CJutJSt1pKo3WwUUUUDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooo
+oAKKKKACiiigD//X6SiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA
+KKKKACiiigAooooAwZ/9c/1NRVLP/rn+pqKuxbHly3YUUUUyQooooAKKKKAC
+iiigAooooAKKKKACiiigAooooAKKKKACiiigCJutJSt1pKo3WwUUUUDCiiig
+AooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD//Q6SiiigAo
+oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAwZ/9c/1N
+RVLP/rn+pqKuxbHly3YUUUUyQooooAKKKKACiiigAooooAKKKKACiiigAooo
+oAKKKKACiiigCJutJSt1pKo3WwUUUUDCiiigAooooAKKKKACiiigAooooAKK
+KKACiiigAooooAKKKKACiiigD//R6SiiigAooooAKKKKACiiigAooooAKKKK
+ACiiigAooooAKKKKACiiigAooooAwZ/9c/1NRVLP/rn+pqKuxbHly3YUUUUy
+QooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigCJutJSt1pKo3
+WwUUUUDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiig
+D//S2i7560m9/WmnqaStbHPdj97+tG9/WmUUWC7H739aN7+tMoosF2P3v60b
+39aZRRYLsfvf1o3v60yiiwXY/e/rRvf1plFFgux+9/Wje/rTKKLBdj97+tG9
+/WmUUWC7H739aN7+tMoosF2P3v60b39aZRRYLsfvf1o3v60yiiwXZlzEmVvr
+UeTT5f8AWt9ajrdbHI9xcmjJpKKYhcmjJpKKAFyaMmkooAXJoyaSigBcmjJp
+KKAFyaMmkooAXJoyaSigBcmjJpKKAFyaMmkooAXJoyaSigBwAPWlwKB0paBX
+YmBRgUtFAXYmBRgUtFAXYmBRgUtFAXYmBRgUtFAXYmBRgUtFAXYmBRgUtFAX
+YmBRgUtFAXYmBRgUtFAXYmBRgUtFAXYmBRgUtFAXYmBRgUtFAXZ//9PXPU0l
+KeppK1OYKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK
+AMqX/Wt9ajqSX/Wt9ajroRyvcKKKKBBRRRQAUUUUAFFFFABRRRQAUUUUAFFF
+FABRRRQAUUUUAFFFFADx0paQdKWgkKKKKACiiigAooooAKKKKACiiigAoooo
+AKKKKACiiigAooooAKKKKACiiigD/9TXPU0lKeppK1OYKKKKACiiigAooooA
+KKKKACiiigAooooAKKKKACiiigAooooAKKKKAMqX/Wt9ajqSX/Wt9ajroRyv
+cKKKKBBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFADx0paQ
+dKWgkKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACi
+iigD/9XXPU0lKeppK1OYKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii
+igAooooAKKKKAMqX/Wt9ajqSX/Wt9ajroRyvcKKKKBBRRRQAUUUUAFFFFABR
+RRQAUUUUAFFFFABRRRQAUUUUAFFFFADx0paQdKWgkKKKKACiiigAooooAKKK
+KACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9bXPU0lKeppK1OYKKKK
+ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAMqX/Wt9ajqS
+X/Wt9ajroRyvcKKKKBBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUU
+AFFFFADx0paQdKWgkKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigA
+ooooAKKKKACiiigD/9fXPU0lKeppK1OYKKKKACiiigAooooAKKKKACiiigAo
+oooAKKKKACiiigAooooAKKKKAMqX/Wt9ajqSX/Wt9ajroRyvcKKKKBBRRRQA
+UUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFADx0paQdKWgkKKKKACi
+iigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9DXPU0l
+KeppK1OYKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK
+AMqX/Wt9ajqSX/Wt9ajroRyvcKKKKBBRRRQAUUUUAFFFFABRRRQAUUUUAFFF
+FABRRRQAUUUUAFFFFADx0paQdKWgkKKKKACiiigAooooAKKKKACiiigAoooo
+AKKKKACiiigAooooAKKKKACiiigD/9HXPU0lKeppK1OYKKKKACiiigAooooA
+KKKKACiiigAooooAKKKKACiiigAooooAKKKKAMqX/Wt9ajqSX/Wt9ajroRyv
+cKKKKBBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFADx0paQ
+dKWgkKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACi
+iigD/9LXPU0lKeppK1OYKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii
+igAooooAKKKKAMqX/Wt9ajqSX/Wt9ajroRyvcKKKKBBRRRQAUUUUAFFFFABR
+RRQAUUUUAFFFFABRRRQAUUUUAFFFFADx0paQdKWgkKKKKACiiigAooooAKKK
+KACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9PXPU0lKeppK1OYKKKK
+ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAMqX/Wt9ajqS
+X/Wt9ajroRyvcKKKKBBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUU
+AFFFFADx0paQdKWgkKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigA
+ooooAKKKKACiiigD/9TXPU0lKeppK1OYKKKKACiiigAooooAKKKKACiiigAo
+oooAKKKKACiiigAooooAKKKKAMqX/Wt9ajqSX/Wt9ajroRyvcKKKKBBRRRQA
+UUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFADx0paQdKWgkKKKKACi
+iigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9WV7mUO
+Rnv6U37TN6/pUMn32+pptdyiuxxNssfaZvX9KPtM3r+lV6KOVdguyx9pm9f0
+o+0zev6VXoo5V2C7LH2mb1/Sj7TN6/pVeijlXYLssfaZvX9KPtM3r+lV6KOV
+dguyx9pm9f0o+0zev6VXoo5V2C7LH2mb1/Sj7TN6/pVeijlXYLssfaZvX9KP
+tM3r+lV6KOVdguyx9pm9f0o+0zev6VXoo5V2C7LH2mb1/Sj7TN6/pVeijlXY
+LssfaZvX9KPtM3r+lV6KOVdguzRSFJFEjdTyad9mi96fD/ql+lS1g27lcqK/
+2aL3o+zRe9WKKXMw5V2K/wBmi96Ps0XvViijmYcq7Ff7NF70fZoverFFHMw5
+V2K/2aL3o+zRe9WKKOZhyrsV/s0XvR9mi96sUUczDlXYr/Zovej7NF71Yoo5
+mHKuxX+zRe9H2aL3qxRRzMOVdiv9mi96Ps0XvViijmYcq7Ff7NF70fZoverF
+FHMw5V2K/wBmi96Ps0XvViijmYcq7BHZwlcnP50/7FB6H86ni+4KkqHJ9zdU
+422Kn2KD0P50fYoPQ/nVuilzvuP2cexU+xQeh/Oj7FB6H86t0Uc77h7OPYqf
+YoPQ/nR9ig9D+dW6KOd9w9nHsVPsUHofzo+xQeh/OrdFHO+4ezj2Kn2KD0P5
+0fYoPQ/nVuijnfcPZx7FT7FB6H86PsUHofzq3RRzvuHs49ip9ig9D+dH2KD0
+P51boo533D2cexU+xQeh/Oj7FB6H86t0Uc77h7OPYqfYoPQ/nR9ig9D+dW6K
+Od9w9nHsVPsUHofzo+xQeh/OrdFHO+4ezj2Kn2KD0P50fYoPQ/nVuijnfcPZ
+x7H/1mSffb6mm06T77fU02u9HCwooooAKKKKACiiigAooooAKKKKACiiigAo
+oooAKKKKACiiigAooooA1of9Uv0qWoof9Uv0qWuZ7mqCiiikAUUUUAFFFFAB
+RRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQBai+4KkqOL7gqSs3udEdgooo
+pDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9dk
+n32+pptOk++31NNrvRwsKKKKACiiigAooooAKKKKACiiigAooooAKKKKACii
+igAooooAKKKKANaH/VL9KlqKH/VL9Klrme5qgooopAFFFFABRRRQAUUUUAFF
+FFABRRRQAUUUUAFFFFABRRRQAUUUUAWovuCpKji+4KkrN7nRHYKKKKQwoooo
+AKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//QZJ99vqab
+TpPvt9TTa70cLCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKK
+ACiiigDWh/1S/Spaih/1S/Spa5nuaoKKKKQBRRRQAUUUUAFFFFABRRRQAUUU
+UAFFFFABRRRQAUUUUAFFFFAFqL7gqSo4vuCpKze50R2CiiikMKKKKACiiigA
+ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/0WSffb6mm06T77fU
+02u9HCwooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA
+1of9Uv0qWoof9Uv0qWuZ7mqCiiikAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQ
+AUUUUAFFFFABRRRQBai+4KkqOL7gqSs3udEdgooopDCiiigAooooAKKKKACi
+iigAooooAKKKKACiiigAooooAKKKKACiiigD/9Jkn32+pptOk++31NNrvRws
+KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKANaH/VL9
+KlqKH/VL9Klrme5qgooopAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAB
+RRRQAUUUUAWovuCpKji+4KkrN7nRHYKKKKQwooooAKKKKACiiigAooooAKKK
+KACiiigAooooAKKKKACiiigAooooA//TZJ99vqabTpPvt9TTa70cLCiiigAo
+oooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigDWh/1S/Spaih/1
+S/Spa5nuaoKKKKQBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFF
+FFAFqL7gqSo4vuCpKze50R2CiiikMKKKKACiiigAooooAKKKKACiiigAoooo
+AKKKKACiiigAooooAKKKKAP/1GSffb6mm06T77fU02u9HCwooooAKKKKACii
+igAooooAKKKKACiiigAooooAKKKKACiiigAooooA1of9Uv0qWoof9Uv0qWuZ
+7mqCiiikAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQBai+
+4KkqOL7gqSs3udEdgooopDCiiigAooooAKKKKACiiigAooooAKKKKACiiigA
+ooooAKKKKACiiigD/9Vkn32+pptOk++31NNrvRwsKKKKACiiigAooooAKKKK
+ACiiigAooooAKKKKACiiigAooooAKKKKANaH/VL9KlqKH/VL9Klrme5qgooo
+pAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAWovuCpKji+
+4KkrN7nRHYKKKKQwooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACi
+iigAooooA//WZJ99vqabTpPvt9TTa70cLCiiigAooooAKKKKACiiigAooooA
+KKKKACiiigAooooAKKKKACiiigDWh/1S/Spaih/1S/Spa5nuaoKKKKQBRRRQ
+AUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAFqL7gqSo4vuCpKze5
+0R2CiiikMKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKK
+KAP/12Sffb6mm06T77fU02u9HCwooooAKKKKACiiigAooooAKKKKACiiigAo
+oooAKKKKACiiigAooooA1of9Uv0qWoof9Uv0qWuZ7mqCiiikAUUUUAFFFFAB
+RRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQBai+4KkqOL7gqSs3udEdgooo
+pDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9DU
+OnxMSdx5pP7Oi/vNWjRV+0l3I5I9jO/s6L+81H9nRf3mrRoo9pLuHs49jO/s
+6L+81H9nRf3mrRoo9pLuHs49jO/s6L+81H9nRf3mrRoo9pLuHs49jO/s6L+8
+1H9nRf3mrRoo9pLuHs49jO/s6L+81H9nRf3mrRoo9pLuHs49jO/s6L+81H9n
+Rf3mrRoo9pLuHs49jO/s6L+81H9nRf3mrRoo9pLuHs49jO/s6L+81H9nRf3m
+rRoo9pLuHs49jO/s6L+81H9nRf3mrRoo9pLuHs49jO/s6L+81H9nRf3mrRoo
+9pLuHs49iBLdUUKCeKd5K+pqWip5mPlRF5K+po8lfU1LRRdhyoi8lfU0eSvq
+aloouw5UReSvqaPJX1NS0UXYcqIvJX1NHkr6mpaKLsOVEXkr6mjyV9TUtFF2
+HKiLyV9TR5K+pqWii7DlRF5K+po8lfU1LRRdhyoi8lfU0eSvqaloouw5UReS
+vqaPJX1NS0UXYcqIvJX1NHkr6mpaKLsOVCKu0YFLRRSKCiiigAooooAKKKKA
+CiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//9k=
+
+--001a1140ffb6f4fc63053a2257e2--
+
+--xYzZY
+Content-Disposition: form-data; name="to"
+
+inbound@inbound.inbound.com
+--xYzZY
+Content-Disposition: form-data; name="from"
+
+Example User <test@example.com>
+--xYzZY
+Content-Disposition: form-data; name="sender_ip"
+
+0.0.0.0
+--xYzZY
+Content-Disposition: form-data; name="envelope"
+
+{"to":["inbound@inbound.inbound.com"],"from":"test@example.com"}
+--xYzZY
+Content-Disposition: form-data; name="subject"
+
+Raw Payload
+--xYzZY
+Content-Disposition: form-data; name="charsets"
+
+{"to":"UTF-8","subject":"UTF-8","from":"UTF-8"}
+--xYzZY
+Content-Disposition: form-data; name="SPF"
+
+pass
+--xYzZY-- \ No newline at end of file
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/send.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/send.py
new file mode 100644
index 00000000..8dbfa68d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/inbound/send.py
@@ -0,0 +1,61 @@
+"""A module for sending test SendGrid Inbound Parse messages.
+Usage: ./send.py [path to file containing test data]"""
+import argparse
+import sys
+from io import open
+try:
+ from config import Config
+except ImportError:
+ # Python 3+, Travis
+ from sendgrid.helpers.inbound.config import Config
+from python_http_client import Client
+
+
+class Send(object):
+
+ def __init__(self, url):
+ """Create a Send object with target `url`."""
+ self._url = url
+
+ def test_payload(self, payload_filepath):
+ """Send a test payload.
+
+ Load a payload from payload_filepath, apply headers, and POST self.url.
+ Return the response object.
+ """
+ headers = {
+ "User-Agent": "SendGrid-Test",
+ "Content-Type": "multipart/form-data; boundary=xYzZY"
+ }
+ client = Client(host=self.url, request_headers=headers)
+ f = open(payload_filepath, 'r', encoding='utf-8')
+ data = f.read()
+ return client.post(request_body=data)
+
+ @property
+ def url(self):
+ """URL to send to."""
+ return self._url
+
+
+def main():
+ config = Config()
+ parser = argparse.ArgumentParser(
+ description='Test data and optional host.')
+ parser.add_argument('data',
+ type=str,
+ help='path to the sample data')
+ parser.add_argument('-host',
+ type=str,
+ help='name of host to send the sample data to',
+ default=config.host, required=False)
+ args = parser.parse_args()
+ send = Send(args.host)
+ response = send.test_payload(sys.argv[1])
+ print(response.status_code)
+ print(response.headers)
+ print(response.body)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/__init__.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/__init__.py
new file mode 100644
index 00000000..358f2d91
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/__init__.py
@@ -0,0 +1,63 @@
+from .asm import Asm
+from .attachment import Attachment
+from .batch_id import BatchId
+from .bcc_email import Bcc
+from .bcc_settings import BccSettings
+from .bcc_settings_email import BccSettingsEmail
+from .bypass_bounce_management import BypassBounceManagement
+from .bypass_list_management import BypassListManagement
+from .bypass_spam_management import BypassSpamManagement
+from .bypass_unsubscribe_management import BypassUnsubscribeManagement
+from .category import Category
+from .cc_email import Cc
+from .click_tracking import ClickTracking
+from .content import Content
+from .content_id import ContentId
+from .custom_arg import CustomArg
+from .disposition import Disposition
+from .dynamic_template_data import DynamicTemplateData
+from .email import Email
+from .exceptions import SendGridException, ApiKeyIncludedException
+from .file_content import FileContent
+from .file_name import FileName
+from .file_type import FileType
+from .footer_settings import FooterSettings
+from .footer_text import FooterText
+from .footer_html import FooterHtml
+from .from_email import From
+from .ganalytics import Ganalytics
+from .group_id import GroupId
+from .groups_to_display import GroupsToDisplay
+from .header import Header
+from .html_content import HtmlContent
+from .amp_html_content import AmpHtmlContent
+from .ip_pool_name import IpPoolName
+from .mail_settings import MailSettings
+from .mail import Mail
+from .mime_type import MimeType
+from .open_tracking import OpenTracking
+from .open_tracking_substitution_tag import OpenTrackingSubstitutionTag
+from .personalization import Personalization
+from .plain_text_content import PlainTextContent
+from .reply_to import ReplyTo
+from .sandbox_mode import SandBoxMode
+from .section import Section
+from .send_at import SendAt
+from .spam_check import SpamCheck
+from .spam_threshold import SpamThreshold
+from .spam_url import SpamUrl
+from .subject import Subject
+from .subscription_tracking import SubscriptionTracking
+from .subscription_text import SubscriptionText
+from .subscription_html import SubscriptionHtml
+from .subscription_substitution_tag import SubscriptionSubstitutionTag
+from .substitution import Substitution
+from .template_id import TemplateId
+from .tracking_settings import TrackingSettings
+from .to_email import To
+from .utm_source import UtmSource
+from .utm_medium import UtmMedium
+from .utm_term import UtmTerm
+from .utm_content import UtmContent
+from .utm_campaign import UtmCampaign
+from .validators import ValidateApiKey
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/amp_html_content.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/amp_html_content.py
new file mode 100644
index 00000000..1a282053
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/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/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/asm.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/asm.py
new file mode 100644
index 00000000..62db8372
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/asm.py
@@ -0,0 +1,80 @@
+from .group_id import GroupId
+from .groups_to_display import GroupsToDisplay
+
+
+class Asm(object):
+ """An object specifying unsubscribe behavior."""
+
+ 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: GroupId, int, required
+ :param groups_to_display: Unsubscribe groups to display
+ :type groups_to_display: GroupsToDisplay, list(int), optional
+ """
+ self._group_id = None
+ self._groups_to_display = None
+
+ if group_id is not None:
+ self.group_id = group_id
+
+ if groups_to_display is not None:
+ self.groups_to_display = groups_to_display
+
+ @property
+ def group_id(self):
+ """The unsubscribe group to associate with this email.
+
+ :rtype: GroupId
+ """
+ return self._group_id
+
+ @group_id.setter
+ def group_id(self, 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: GroupsToDisplay
+ """
+ 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. 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 object.
+
+ :returns: This ASM object, ready for use in a request body.
+ :rtype: dict
+ """
+ asm = {}
+ if self.group_id is not None:
+ asm["group_id"] = self.group_id.get()
+
+ if self.groups_to_display is not None:
+ asm["groups_to_display"] = self.groups_to_display.get()
+ return asm
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/attachment.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/attachment.py
new file mode 100644
index 00000000..f8b53a68
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/attachment.py
@@ -0,0 +1,218 @@
+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_name=None,
+ file_type=None,
+ disposition=None,
+ content_id=None):
+ """Create an Attachment
+
+ :param file_content: The Base64 encoded content of the attachment
+ :type file_content: FileContent, string
+ :param file_name: The filename of the attachment
+ :type file_name: FileName, string
+ :param file_type: The MIME type of the content you are attaching
+ :type file_type FileType, string, optional
+ :param disposition: The content-disposition of the attachment,
+ specifying display style. Specifies how you
+ would like the attachment to be displayed.
+ - "inline" results in the attached file being
+ displayed automatically within the message.
+ - "attachment" results in the attached file
+ requiring some action to display (e.g. opening
+ or downloading the file).
+ If unspecified, "attachment" is used. Must be one
+ of the two choices.
+ :type disposition: Disposition, string, optional
+ :param content_id: The content id for the attachment.
+ This is used when the Disposition is set to
+ "inline" and the attachment is an image, allowing
+ the file to be displayed within the email body.
+ :type content_id: ContentId, string, optional
+ """
+ self._file_content = None
+ self._file_type = None
+ self._file_name = None
+ self._disposition = None
+ self._content_id = None
+
+ if file_content is not None:
+ self.file_content = file_content
+
+ if file_type is not None:
+ self.file_type = file_type
+
+ if file_name is not None:
+ self.file_name = file_name
+
+ if disposition is not None:
+ self.disposition = disposition
+
+ if content_id is not None:
+ self.content_id = content_id
+
+ @property
+ def file_content(self):
+ """The Base64 encoded content of the attachment.
+
+ :rtype: FileContent
+ """
+ return self._file_content
+
+ @file_content.setter
+ def file_content(self, value):
+ """The Base64 encoded content of the attachment
+
+ :param value: The Base64 encoded content of the attachment
+ :type value: FileContent, string
+ """
+ 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: FileName
+ """
+ return self._file_name
+
+ @file_name.setter
+ def file_name(self, value):
+ """The filename of the attachment
+
+ :param file_name: The filename of the attachment
+ :type file_name: FileName, string
+ """
+ if isinstance(value, FileName):
+ self._file_name = value
+ else:
+ self._file_name = FileName(value)
+
+ @property
+ def 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):
+ """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: Disposition
+ """
+ return self._disposition
+
+ @disposition.setter
+ def disposition(self, value):
+ """The content-disposition of the attachment, specifying display style.
+
+ Specifies how you would like the attachment to be displayed.
+ - "inline" results in the attached file being displayed automatically
+ within the message.
+ - "attachment" results in the attached file requiring some action to
+ display (e.g. opening or downloading the file).
+ If unspecified, "attachment" is used. Must be one of the two choices.
+
+ :param 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):
+ """The content id for the attachment.
+
+ This is used when the disposition is set to "inline" and the attachment
+ is an image, allowing the file to be displayed within the email body.
+
+ :rtype: string
+ """
+ return self._content_id
+
+ @content_id.setter
+ def content_id(self, value):
+ """The content id for the attachment.
+
+ This is used when the disposition is set to "inline" and the attachment
+ is an image, allowing the file to be displayed within the email body.
+
+ :param 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):
+ """
+ Get a JSON-ready representation of this Attachment.
+
+ :returns: This Attachment, ready for use in a request body.
+ :rtype: dict
+ """
+ attachment = {}
+ if self.file_content is not None:
+ attachment["content"] = self.file_content.get()
+
+ if self.file_type is not None:
+ attachment["type"] = self.file_type.get()
+
+ if self.file_name is not None:
+ attachment["filename"] = self.file_name.get()
+
+ if self.disposition is not None:
+ attachment["disposition"] = self.disposition.get()
+
+ if self.content_id is not None:
+ attachment["content_id"] = self.content_id.get()
+ return attachment
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/batch_id.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/batch_id.py
new file mode 100644
index 00000000..a4c0f8e9
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/batch_id.py
@@ -0,0 +1,50 @@
+class BatchId(object):
+ """This ID represents a batch of emails to be sent at the same time.
+ Including a batch_id in your request allows you include this email
+ in that batch, and also enables you to cancel or pause the delivery
+ of that batch. For more information, see
+ https://sendgrid.com/docs/API_Reference/Web_API_v3/cancel_schedule_send.
+ """
+ def __init__(self, batch_id=None):
+ """Create a batch ID.
+
+ :param batch_id: Batch Id
+ :type batch_id: string
+ """
+ self._batch_id = None
+
+ if batch_id is not None:
+ self.batch_id = batch_id
+
+ @property
+ def batch_id(self):
+ """The batch ID.
+
+ :rtype: string
+ """
+ return self._batch_id
+
+ @batch_id.setter
+ def batch_id(self, value):
+ """The batch ID.
+
+ :param value: Batch Id
+ :type value: string
+ """
+ self._batch_id = value
+
+ def __str__(self):
+ """Get a JSON representation of this object.
+
+ :rtype: string
+ """
+ return str(self.get())
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this BatchId object.
+
+ :returns: The BatchId, ready for use in a request body.
+ :rtype: string
+ """
+ return self.batch_id
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bcc_email.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bcc_email.py
new file mode 100644
index 00000000..e78f6703
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/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/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bcc_settings.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bcc_settings.py
new file mode 100644
index 00000000..eeb8ba10
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bcc_settings.py
@@ -0,0 +1,72 @@
+class BccSettings(object):
+ """Settings object for automatic BCC.
+
+ This allows you to have a blind carbon copy automatically sent to the
+ specified email address for every email that is sent.
+ """
+
+ def __init__(self, enable=None, email=None):
+ """Create a BCCSettings.
+
+ :param enable: Whether this BCCSettings is applied to sent emails.
+ :type enable: boolean, optional
+ :param email: Who should be BCCed.
+ :type email: BccSettingEmail, optional
+ """
+ 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):
+ """Indicates if this setting is enabled.
+
+ :rtype: boolean
+ """
+ return self._enable
+
+ @enable.setter
+ def enable(self, value):
+ """Indicates if this setting is enabled.
+
+ :type param: Indicates if this setting is enabled.
+ :type value: boolean
+ """
+ self._enable = value
+
+ @property
+ def email(self):
+ """The email address that you would like to receive the BCC.
+
+ :rtype: string
+ """
+ return self._email
+
+ @email.setter
+ def email(self, value):
+ """The email address that you would like to receive the BCC.
+
+ :param value: The email address that you would like to receive the BCC.
+ :type value: string
+ """
+ self._email = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this BCCSettings.
+
+ :returns: This BCCSettings, ready for use in a request body.
+ :rtype: dict
+ """
+ bcc_settings = {}
+ if self.enable is not None:
+ bcc_settings["enable"] = self.enable
+
+ if self.email is not None:
+ bcc_settings["email"] = self.email.get()
+ return bcc_settings
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bcc_settings_email.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bcc_settings_email.py
new file mode 100644
index 00000000..2c2847e2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bcc_settings_email.py
@@ -0,0 +1,40 @@
+class BccSettingsEmail(object):
+ """The BccSettingsEmail of an Attachment."""
+
+ def __init__(self, bcc_settings_email=None):
+ """Create a BccSettingsEmail object
+
+ :param bcc_settings_email: The email address that you would like to
+ receive the BCC
+ :type bcc_settings_email: string, optional
+ """
+ self._bcc_settings_email = None
+
+ if bcc_settings_email is not None:
+ self.bcc_settings_email = bcc_settings_email
+
+ @property
+ def bcc_settings_email(self):
+ """The email address that you would like to receive the BCC
+
+ :rtype: string
+ """
+ return self._bcc_settings_email
+
+ @bcc_settings_email.setter
+ def bcc_settings_email(self, value):
+ """The email address that you would like to receive the BCC
+
+ :param value: The email address that you would like to receive the BCC
+ :type value: string
+ """
+ self._bcc_settings_email = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this BccSettingsEmail.
+
+ :returns: This BccSettingsEmail, ready for use in a request body.
+ :rtype: string
+ """
+ return self.bcc_settings_email
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bypass_bounce_management.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bypass_bounce_management.py
new file mode 100644
index 00000000..b0a35105
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/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/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bypass_list_management.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bypass_list_management.py
new file mode 100644
index 00000000..ac13e3d7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bypass_list_management.py
@@ -0,0 +1,48 @@
+class BypassListManagement(object):
+ """Setting for Bypass List Management
+
+ Allows you to bypass all unsubscribe groups and suppressions to ensure that
+ the email is delivered to every single recipient. This should only be used
+ in emergencies when it is absolutely necessary that every recipient
+ receives your email.
+ """
+
+ def __init__(self, enable=None):
+ """Create a BypassListManagement.
+
+ :param enable: Whether emails should bypass list 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 BypassListManagement.
+
+ :returns: This BypassListManagement, ready for use in a request body.
+ :rtype: dict
+ """
+ bypass_list_management = {}
+ if self.enable is not None:
+ bypass_list_management["enable"] = self.enable
+ return bypass_list_management
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bypass_spam_management.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bypass_spam_management.py
new file mode 100644
index 00000000..9b2552eb
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/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/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bypass_unsubscribe_management.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/bypass_unsubscribe_management.py
new file mode 100644
index 00000000..4867fac2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/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/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/category.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/category.py
new file mode 100644
index 00000000..0a6394c2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/category.py
@@ -0,0 +1,40 @@
+class Category(object):
+ """A category name for this message."""
+
+ def __init__(self, name=None):
+ """Create a Category.
+
+ :param name: The name of this category
+ :type name: string, optional
+ """
+ self._name = None
+
+ if name is not None:
+ self.name = name
+
+ @property
+ def name(self):
+ """The name of this Category. Must be less than 255 characters.
+
+ :rtype: string
+ """
+ return self._name
+
+ @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):
+ """
+ Get a JSON-ready representation of this Category.
+
+ :returns: This Category, ready for use in a request body.
+ :rtype: string
+ """
+ return self.name
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/cc_email.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/cc_email.py
new file mode 100644
index 00000000..77b8ff28
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/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/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/click_tracking.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/click_tracking.py
new file mode 100644
index 00000000..edfba41e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/click_tracking.py
@@ -0,0 +1,71 @@
+class ClickTracking(object):
+ """Allows you to track whether a recipient clicked a link in your email."""
+
+ def __init__(self, enable=None, enable_text=None):
+ """Create a ClickTracking to track clicked links in your email.
+
+ :param enable: Whether click tracking is enabled
+ :type enable: boolean, optional
+ :param enable_text: If click tracking is on in your email's text/plain.
+ :type enable_text: boolean, optional
+ """
+ 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):
+ """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
+
+ @property
+ 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
+
+ @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):
+ """
+ Get a JSON-ready representation of this ClickTracking.
+
+ :returns: This ClickTracking, ready for use in a request body.
+ :rtype: dict
+ """
+ click_tracking = {}
+ if self.enable is not None:
+ click_tracking["enable"] = self.enable
+
+ if self.enable_text is not None:
+ click_tracking["enable_text"] = self.enable_text
+ return click_tracking
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/content.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/content.py
new file mode 100644
index 00000000..618eee91
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/content.py
@@ -0,0 +1,81 @@
+from .validators import ValidateApiKey
+
+
+
+class Content(object):
+ """Content to be included in your email.
+
+ You must specify at least one mime type in the Contents of your email.
+ """
+
+ def __init__(self, mime_type, content):
+ """Create a Content with the specified MIME type and content.
+
+ :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._mime_type = None
+ self._content = None
+ self._validator = ValidateApiKey()
+
+ if mime_type is not None:
+ self.mime_type = mime_type
+
+ if content is not None:
+ self.content = content
+
+ @property
+ def mime_type(self):
+ """The MIME type of the content you are including in your email.
+ For example, "text/plain" or "text/html" or "text/x-amp-html".
+
+ :rtype: string
+ """
+ return self._mime_type
+
+ @mime_type.setter
+ def mime_type(self, value):
+ """The MIME type of the content you are including in your email.
+ For example, "text/plain" or "text/html" or "text/x-amp-html".
+
+ :param value: The MIME type of the content you are including in your
+ email.
+ For example, "text/plain" or "text/html" or "text/x-amp-html".
+ :type value: string
+ """
+ self._mime_type = value
+
+ @property
+ def content(self):
+ """The actual content (of the specified mime type).
+
+ :rtype: string
+ """
+ return self._content
+
+ @content.setter
+ def content(self, value):
+ """The actual content (of the specified mime type).
+
+ :param value: The actual content (of the specified mime type).
+ :type value: string
+ """
+ self._validator.validate_message_dict(value)
+ self._content = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this Content.
+
+ :returns: This Content, 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/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/content_id.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/content_id.py
new file mode 100644
index 00000000..0fff3010
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/content_id.py
@@ -0,0 +1,50 @@
+class ContentId(object):
+ """The ContentId of an Attachment."""
+
+ def __init__(self, content_id=None):
+ """Create a ContentId object
+
+ :param content_id: The content id for the attachment.
+ This is used when the Disposition is set to "inline"
+ and the attachment is an image, allowing the file to
+ be displayed within the email body.
+ :type content_id: string, optional
+ """
+ self._content_id = None
+
+ if content_id is not None:
+ self.content_id = content_id
+
+ @property
+ def content_id(self):
+ """The content id for the attachment.
+ This is used when the Disposition is set to "inline" and the
+ attachment is an image, allowing the file to be displayed within
+ the email body.
+
+ :rtype: string
+ """
+ return self._content_id
+
+ @content_id.setter
+ def content_id(self, value):
+ """The content id for the attachment.
+ This is used when the Disposition is set to "inline" and the
+ attachment is an image, allowing the file to be displayed within
+ the email body.
+
+ :param value: The content id for the attachment.
+ This is used when the Disposition is set to "inline" and the attachment
+ is an image, allowing the file to be displayed within the email body.
+ :type value: string
+ """
+ self._content_id = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this ContentId.
+
+ :returns: This ContentId, ready for use in a request body.
+ :rtype: string
+ """
+ return self.content_id
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/custom_arg.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/custom_arg.py
new file mode 100644
index 00000000..63b22557
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/custom_arg.py
@@ -0,0 +1,94 @@
+class CustomArg(object):
+ """Values that will be carried along with the email and its activity data.
+
+ Substitutions will not be made on custom arguments, so any string entered
+ into this parameter will be assumed to be the custom argument that you
+ would like to be used. Top-level CustomArgs may be overridden by ones in a
+ Personalization. May not exceed 10,000 bytes.
+ """
+
+ def __init__(self, key=None, value=None, p=None):
+ """Create a CustomArg with the given key and value.
+
+ :param key: Key for this CustomArg
+ :type key: string, optional
+ :param value: Value of this CustomArg
+ :type value: string, optional
+ :param p: p is the Personalization object or Personalization
+ object index
+ :type p: Personalization, integer, optional
+ """
+ self._key = None
+ self._value = None
+ self._personalization = None
+
+ if key is not None:
+ self.key = key
+ if value is not None:
+ self.value = value
+ if p is not None:
+ self.personalization = p
+
+ @property
+ def key(self):
+ """Key for this CustomArg.
+
+ :rtype: string
+ """
+ return self._key
+
+ @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.
+
+ :rtype: string
+ """
+ return self._value
+
+ @value.setter
+ def value(self, value):
+ """Value of this CustomArg.
+
+ :param value: Value of this CustomArg.
+ :type value: string
+ """
+ self._value = value
+
+ @property
+ def personalization(self):
+ """The Personalization object or Personalization object index
+
+ :rtype: Personalization, integer
+ """
+ return self._personalization
+
+ @personalization.setter
+ def personalization(self, value):
+ """The Personalization object or Personalization object index
+
+ :param value: The Personalization object or Personalization object
+ index
+ :type value: Personalization, integer
+ """
+ self._personalization = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this CustomArg.
+
+ :returns: This CustomArg, ready for use in a request body.
+ :rtype: dict
+ """
+ custom_arg = {}
+ if self.key is not None and self.value is not None:
+ custom_arg[self.key] = self.value
+ return custom_arg
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/disposition.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/disposition.py
new file mode 100644
index 00000000..a0bdc354
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/disposition.py
@@ -0,0 +1,72 @@
+class Disposition(object):
+ """The content-disposition of the Attachment specifying how you would like
+ the attachment to be displayed."""
+
+ def __init__(self, disposition=None):
+ """Create a Disposition object
+
+ :param disposition: The content-disposition of the attachment,
+ specifying display style.
+ Specifies how you would like the attachment to be
+ displayed.
+ - "inline" results in the attached file being
+ displayed automatically within the message.
+ - "attachment" results in the attached file
+ requiring some action to display (e.g. opening
+ or downloading the file).
+ If unspecified, "attachment" is used. Must be one
+ of the two choices.
+ :type disposition: string, optional
+ """
+ self._disposition = None
+
+ if disposition is not None:
+ self.disposition = disposition
+
+ @property
+ def disposition(self):
+ """The content-disposition of the attachment, specifying display style.
+ Specifies how you would like the attachment to be displayed.
+ - "inline" results in the attached file being displayed
+ automatically within the message.
+ - "attachment" results in the attached file requiring some action to
+ display (e.g. opening or downloading the file).
+ If unspecified, "attachment" is used. Must be one of the two
+ choices.
+
+ :rtype: string
+ """
+ return self._disposition
+
+ @disposition.setter
+ def disposition(self, value):
+ """The content-disposition of the attachment, specifying display style.
+ Specifies how you would like the attachment to be displayed.
+ - "inline" results in the attached file being displayed
+ automatically within the message.
+ - "attachment" results in the attached file requiring some action to
+ display (e.g. opening or downloading the file).
+ If unspecified, "attachment" is used. Must be one of the two
+ choices.
+
+ :param value: The content-disposition of the attachment, specifying
+ display style.
+ Specifies how you would like the attachment to be displayed.
+ - "inline" results in the attached file being displayed
+ automatically within the message.
+ - "attachment" results in the attached file requiring some action to
+ display (e.g. opening or downloading the file).
+ If unspecified, "attachment" is used. Must be one of the two
+ choices.
+ :type value: string
+ """
+ self._disposition = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this Disposition.
+
+ :returns: This Disposition, ready for use in a request body.
+ :rtype: string
+ """
+ return self.disposition
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/dynamic_template_data.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/dynamic_template_data.py
new file mode 100644
index 00000000..e12967b7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/dynamic_template_data.py
@@ -0,0 +1,73 @@
+class DynamicTemplateData(object):
+ """To send a dynamic template, specify the template ID with the
+ template_id parameter.
+ """
+
+ def __init__(self, dynamic_template_data=None, p=0):
+ """Data for a transactional template.
+ Should be JSON-serializable structure.
+
+ :param dynamic_template_data: Data for a transactional template.
+ :type dynamic_template_data: A JSON-serializable structure
+ :param name: p is the Personalization object or Personalization object
+ index
+ :type name: Personalization, integer, optional
+ """
+ self._dynamic_template_data = None
+ self._personalization = None
+
+ if dynamic_template_data is not None:
+ self.dynamic_template_data = dynamic_template_data
+ if p is not None:
+ self.personalization = p
+
+ @property
+ def dynamic_template_data(self):
+ """Data for a transactional template.
+
+ :rtype: A JSON-serializable structure
+ """
+ return self._dynamic_template_data
+
+ @dynamic_template_data.setter
+ def dynamic_template_data(self, value):
+ """Data for a transactional template.
+
+ :param value: Data for a transactional template.
+ :type value: A JSON-serializable structure
+ """
+ self._dynamic_template_data = value
+
+ @property
+ def personalization(self):
+ """The Personalization object or Personalization object index
+
+ :rtype: Personalization, integer
+ """
+ return self._personalization
+
+ @personalization.setter
+ def personalization(self, value):
+ """The Personalization object or Personalization object index
+
+ :param value: The Personalization object or Personalization object
+ index
+ :type value: Personalization, integer
+ """
+ self._personalization = value
+
+ def __str__(self):
+ """Get a JSON representation of this object.
+
+ :rtype: A JSON-serializable structure
+ """
+ return str(self.get())
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this DynamicTemplateData object.
+
+ :returns: Data for a transactional template.
+ :rtype: A JSON-serializable structure.
+ """
+ return self.dynamic_template_data
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/email.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/email.py
new file mode 100644
index 00000000..aeab26af
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/email.py
@@ -0,0 +1,228 @@
+try:
+ import rfc822
+except ImportError:
+ import email.utils as rfc822
+
+try:
+ basestring = basestring
+except NameError:
+ # Define basestring when Python >= 3.0
+ basestring = str
+
+
+class Email(object):
+ """An email address with an optional name."""
+
+ def __init__(self,
+ email=None,
+ name=None,
+ substitutions=None,
+ subject=None,
+ p=0,
+ dynamic_template_data=None):
+ """Create an Email with the given address and name.
+
+ Either fill the separate name and email fields, or pass all information
+ in the email parameter (e.g. email="dude Fella <example@example.com>").
+ :param email: Email address, or name and address in standard format.
+ :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._personalization = p
+
+ if email and not name:
+ # allows passing emails as "Example Name <example@example.com>"
+ self.parse_email(email)
+ else:
+ # allows backwards compatibility for Email(email, name)
+ if email is not None:
+ self.email = email
+
+ if name is not None:
+ self.name = name
+
+ # Note that these only apply to To Emails (see Personalization.add_to)
+ # and should be moved but have not been for compatibility.
+ self._substitutions = substitutions
+ self._dynamic_template_data = dynamic_template_data
+ self._subject = subject
+
+ @property
+ def name(self):
+ """Name associated with this email.
+
+ :rtype: string
+ """
+ return self._name
+
+ @name.setter
+ def name(self, value):
+ """Name associated with this email.
+
+ :param value: Name associated with this email.
+ :type value: string
+ """
+ if not (value is None or isinstance(value, basestring)):
+ raise TypeError('name must be of type string.')
+
+ self._name = value
+
+ @property
+ def email(self):
+ """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.
+
+ :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 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.
+
+ :rtype: string
+ """
+ return self._subject
+
+ @subject.setter
+ def subject(self, value):
+ """Subject for this sender or recipient.
+
+ :param value: Subject for this sender or recipient.
+ :type value: string, optional
+ """
+ self._subject = value
+
+ @property
+ def personalization(self):
+ """The Personalization object or Personalization object index
+
+ :rtype: Personalization, integer
+ """
+ return self._personalization
+
+ @personalization.setter
+ def personalization(self, value):
+ """The Personalization object or Personalization object index
+
+ :param value: The Personalization object or Personalization object
+ index
+ :type value: Personalization, integer
+ """
+ self._personalization = value
+
+ def parse_email(self, email_info):
+ """Allows passing emails as "Example Name <example@example.com>"
+
+ :param email_info: Allows passing emails as
+ "Example Name <example@example.com>"
+ :type email_info: string
+ """
+ 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
+ email = None
+
+ if not name:
+ name = None
+
+ if not email:
+ email = None
+
+ 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/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/exceptions.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/exceptions.py
new file mode 100644
index 00000000..cbc31134
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/exceptions.py
@@ -0,0 +1,65 @@
+################################################################
+# Various types of extensible Twilio SendGrid related exceptions
+################################################################
+
+
+class SendGridException(Exception):
+ """Wrapper/default SendGrid-related exception"""
+ pass
+
+
+class ApiKeyIncludedException(SendGridException):
+ """Exception raised for when Twilio SendGrid API Key included in message text"""
+
+ def __init__(self,
+ expression="Email body",
+ message="Twilio SendGrid API Key detected"):
+ """Create an exception for when Twilio SendGrid API Key included in message text
+
+ :param expression: Input expression in which the error occurred
+ :type expression: string
+ :param message: Explanation of the error
+ :type message: string
+ """
+ self._expression = None
+ self._message = None
+
+ if expression is not None:
+ self.expression = expression
+
+ if message is not None:
+ self.message = message
+
+ @property
+ def expression(self):
+ """Input expression in which the error occurred
+
+ :rtype: string
+ """
+ return self._expression
+
+ @expression.setter
+ def expression(self, value):
+ """Input expression in which the error occurred
+
+ :param value: Input expression in which the error occurred
+ :type value: string
+ """
+ self._expression = value
+
+ @property
+ def message(self):
+ """Explanation of the error
+
+ :rtype: string
+ """
+ return self._message
+
+ @message.setter
+ def message(self, value):
+ """Explanation of the error
+
+ :param value: Explanation of the error
+ :type value: string
+ """
+ self._message = value
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/file_content.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/file_content.py
new file mode 100644
index 00000000..c1eb81fc
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/file_content.py
@@ -0,0 +1,39 @@
+class FileContent(object):
+ """The Base64 encoded content of an Attachment."""
+
+ def __init__(self, file_content=None):
+ """Create a FileContent object
+
+ :param file_content: The Base64 encoded content of the attachment
+ :type file_content: string, optional
+ """
+ self._file_content = None
+
+ if file_content is not None:
+ self.file_content = file_content
+
+ @property
+ def file_content(self):
+ """The Base64 encoded content of the attachment.
+
+ :rtype: string
+ """
+ return self._file_content
+
+ @file_content.setter
+ def file_content(self, value):
+ """The Base64 encoded content of the attachment.
+
+ :param value: The Base64 encoded content of the attachment.
+ :type value: string
+ """
+ self._file_content = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this FileContent.
+
+ :returns: This FileContent, ready for use in a request body.
+ :rtype: string
+ """
+ return self.file_content
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/file_name.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/file_name.py
new file mode 100644
index 00000000..3a4e3ff2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/file_name.py
@@ -0,0 +1,39 @@
+class FileName(object):
+ """The filename of an Attachment."""
+
+ def __init__(self, file_name=None):
+ """Create a FileName object
+
+ :param file_name: The file name of the attachment
+ :type file_name: string, optional
+ """
+ self._file_name = None
+
+ if file_name is not None:
+ self.file_name = file_name
+
+ @property
+ def file_name(self):
+ """The file name of the attachment.
+
+ :rtype: string
+ """
+ return self._file_name
+
+ @file_name.setter
+ def file_name(self, value):
+ """The file name of the attachment.
+
+ :param value: The file name of the attachment.
+ :type value: string
+ """
+ self._file_name = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this FileName.
+
+ :returns: This FileName, ready for use in a request body.
+ :rtype: string
+ """
+ return self.file_name
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/file_type.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/file_type.py
new file mode 100644
index 00000000..a30e6edf
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/file_type.py
@@ -0,0 +1,39 @@
+class FileType(object):
+ """The MIME type of the content you are attaching to an Attachment."""
+
+ def __init__(self, file_type=None):
+ """Create a FileType object
+
+ :param file_type: The MIME type of the content you are attaching
+ :type file_type: string, optional
+ """
+ self._file_type = None
+
+ if file_type is not None:
+ self.file_type = file_type
+
+ @property
+ def file_type(self):
+ """The MIME type of the content you are attaching.
+
+ :rtype: string
+ """
+ return self._file_type
+
+ @file_type.setter
+ def file_type(self, mime_type):
+ """The MIME type of the content you are attaching.
+
+ :param mime_type: The MIME type of the content you are attaching.
+ :rtype mime_type: string
+ """
+ self._file_type = mime_type
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this FileType.
+
+ :returns: This FileType, ready for use in a request body.
+ :rtype: string
+ """
+ return self.file_type
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/footer_html.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/footer_html.py
new file mode 100644
index 00000000..c8b5ac1a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/footer_html.py
@@ -0,0 +1,39 @@
+class FooterHtml(object):
+ """The HTML in a Footer."""
+
+ def __init__(self, footer_html=None):
+ """Create a FooterHtml object
+
+ :param footer_html: The html content of your footer.
+ :type footer_html: string, optional
+ """
+ self._footer_html = None
+
+ if footer_html is not None:
+ self.footer_html = footer_html
+
+ @property
+ def footer_html(self):
+ """The html content of your footer.
+
+ :rtype: string
+ """
+ return self._footer_html
+
+ @footer_html.setter
+ def footer_html(self, html):
+ """The html content of your footer.
+
+ :param html: The html content of your footer.
+ :type html: string
+ """
+ self._footer_html = html
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this FooterHtml.
+
+ :returns: This FooterHtml, ready for use in a request body.
+ :rtype: string
+ """
+ return self.footer_html
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/footer_settings.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/footer_settings.py
new file mode 100644
index 00000000..1b0efeb1
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/footer_settings.py
@@ -0,0 +1,94 @@
+class FooterSettings(object):
+ """The default footer that you would like included on every email."""
+
+ def __init__(self, enable=None, text=None, html=None):
+ """Create a default footer.
+
+ :param enable: Whether this footer should be applied.
+ :type enable: boolean, optional
+ :param text: Text content of this footer
+ :type text: FooterText, optional
+ :param html: HTML content of this footer
+ :type html: FooterHtml, optional
+ """
+ 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):
+ """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
+
+ @property
+ def text(self):
+ """The plain text content of your footer.
+
+ :rtype: string
+ """
+ return self._text
+
+ @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
+ def html(self):
+ """The HTML content of your footer.
+
+ :rtype: string
+ """
+ return self._html
+
+ @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):
+ """
+ Get a JSON-ready representation of this FooterSettings.
+
+ :returns: This FooterSettings, ready for use in a request body.
+ :rtype: dict
+ """
+ footer_settings = {}
+ if self.enable is not None:
+ footer_settings["enable"] = self.enable
+
+ if self.text is not None:
+ footer_settings["text"] = self.text.get()
+
+ if self.html is not None:
+ footer_settings["html"] = self.html.get()
+ return footer_settings
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/footer_text.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/footer_text.py
new file mode 100644
index 00000000..06f96892
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/footer_text.py
@@ -0,0 +1,39 @@
+class FooterText(object):
+ """The text in an Footer."""
+
+ def __init__(self, footer_text=None):
+ """Create a FooterText object
+
+ :param footer_text: The plain text content of your footer.
+ :type footer_text: string, optional
+ """
+ self._footer_text = None
+
+ if footer_text is not None:
+ self.footer_text = footer_text
+
+ @property
+ def footer_text(self):
+ """The plain text content of your footer.
+
+ :rtype: string
+ """
+ return self._footer_text
+
+ @footer_text.setter
+ def footer_text(self, value):
+ """The plain text content of your footer.
+
+ :param value: The plain text content of your footer.
+ :type value: string
+ """
+ self._footer_text = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this FooterText.
+
+ :returns: This FooterText, ready for use in a request body.
+ :rtype: string
+ """
+ return self.footer_text
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/from_email.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/from_email.py
new file mode 100644
index 00000000..0f6f231c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/from_email.py
@@ -0,0 +1,5 @@
+from .email import Email
+
+
+class From(Email):
+ """A from email address with an optional name."""
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/ganalytics.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/ganalytics.py
new file mode 100644
index 00000000..5f632715
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/ganalytics.py
@@ -0,0 +1,176 @@
+class Ganalytics(object):
+ """Allows you to enable tracking provided by Google Analytics."""
+
+ def __init__(self,
+ enable=None,
+ utm_source=None,
+ utm_medium=None,
+ utm_term=None,
+ utm_content=None,
+ utm_campaign=None):
+ """Create a GAnalytics to enable, customize Google Analytics tracking.
+
+ :param enable: If this setting is enabled.
+ :type enable: boolean, optional
+ :param utm_source: Name of the referrer source.
+ :type utm_source: string, optional
+ :param utm_medium: Name of the marketing medium (e.g. "Email").
+ :type utm_medium: string, optional
+ :param utm_term: Used to identify paid keywords.
+ :type utm_term: string, optional
+ :param utm_content: Used to differentiate your campaign from ads.
+ :type utm_content: string, optional
+ :param utm_campaign: The name of the campaign.
+ :type utm_campaign: string, optional
+ """
+ self._enable = None
+ self._utm_source = None
+ self._utm_medium = None
+ self._utm_term = None
+ self._utm_content = None
+ self._utm_campaign = None
+
+ self.__set_field("enable", enable)
+ self.__set_field("utm_source", utm_source)
+ self.__set_field("utm_medium", utm_medium)
+ self.__set_field("utm_term", utm_term)
+ self.__set_field("utm_content", utm_content)
+ self.__set_field("utm_campaign", utm_campaign)
+
+ def __set_field(self, field, value):
+ """ Sets a field to the provided value if value is not None
+
+ :param field: Name of the field
+ :type field: string
+ :param value: Value to be set, ignored if None
+ :type value: Any
+ """
+ if value is not None:
+ setattr(self, field, value)
+
+ @property
+ def enable(self):
+ """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
+
+ @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
+ def utm_medium(self):
+ """Name of the marketing medium (e.g. Email).
+
+ :rtype: string
+ """
+ return self._utm_medium
+
+ @utm_medium.setter
+ def utm_medium(self, value):
+ """Name of the marketing medium (e.g. Email).
+
+ :param value: Name of the marketing medium (e.g. Email).
+ :type value: string
+ """
+ self._utm_medium = value
+
+ @property
+ def utm_term(self):
+ """Used to identify any paid keywords.
+
+ :rtype: string
+ """
+ return self._utm_term
+
+ @utm_term.setter
+ def utm_term(self, value):
+ """Used to identify any paid keywords.
+
+ :param value: Used to identify any paid keywords.
+ :type value: string
+ """
+ self._utm_term = value
+
+ @property
+ def utm_content(self):
+ """Used to differentiate your campaign from advertisements.
+
+ :rtype: string
+ """
+ return self._utm_content
+
+ @utm_content.setter
+ def utm_content(self, value):
+ """Used to differentiate your campaign from advertisements.
+
+ :param value: Used to differentiate your campaign from advertisements.
+ :type value: string
+ """
+ self._utm_content = value
+
+ @property
+ def utm_campaign(self):
+ """The name of the campaign.
+
+ :rtype: string
+ """
+ return self._utm_campaign
+
+ @utm_campaign.setter
+ def utm_campaign(self, value):
+ """The name of the campaign.
+
+ :param value: The name of the campaign.
+ :type value: string
+ """
+ self._utm_campaign = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this Ganalytics.
+
+ :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 = {}
+
+ for key in keys:
+ value = getattr(self, key, None)
+ if value is not None:
+ if isinstance(value, bool) or isinstance(value, str):
+ ganalytics[key] = value
+ else:
+ ganalytics[key] = value.get()
+
+ return ganalytics
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/group_id.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/group_id.py
new file mode 100644
index 00000000..66778531
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/group_id.py
@@ -0,0 +1,39 @@
+class GroupId(object):
+ """The unsubscribe group ID to associate with this email."""
+
+ def __init__(self, group_id=None):
+ """Create a GroupId object
+
+ :param group_id: The unsubscribe group to associate with this email.
+ :type group_id: integer, optional
+ """
+ self._group_id = None
+
+ if group_id is not None:
+ self.group_id = group_id
+
+ @property
+ def group_id(self):
+ """The unsubscribe group to associate with this email.
+
+ :rtype: integer
+ """
+ return self._group_id
+
+ @group_id.setter
+ def group_id(self, value):
+ """The unsubscribe group to associate with this email.
+
+ :param value: The unsubscribe group to associate with this email.
+ :type value: integer
+ """
+ self._group_id = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this GroupId.
+
+ :returns: This GroupId, ready for use in a request body.
+ :rtype: integer
+ """
+ return self.group_id
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/groups_to_display.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/groups_to_display.py
new file mode 100644
index 00000000..2e3fca77
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/groups_to_display.py
@@ -0,0 +1,48 @@
+class GroupsToDisplay(object):
+ """The unsubscribe groups that you would like to be displayed on the
+ unsubscribe preferences page.."""
+
+ def __init__(self, groups_to_display=None):
+ """Create a GroupsToDisplay object
+
+ :param groups_to_display: An array containing the unsubscribe groups
+ that you would like to be displayed on the
+ unsubscribe preferences page.
+ :type groups_to_display: array of integers, optional
+ """
+ self._groups_to_display = None
+
+ if groups_to_display is not None:
+ self.groups_to_display = groups_to_display
+
+ @property
+ def groups_to_display(self):
+ """An array containing the unsubscribe groups that you would like to be
+ displayed on the unsubscribe preferences page.
+
+ :rtype: array(int)
+ """
+ return self._groups_to_display
+
+ @groups_to_display.setter
+ def groups_to_display(self, value):
+ """An array containing the unsubscribe groups that you would like to be
+ displayed on the unsubscribe preferences page.
+
+ :param value: An array containing the unsubscribe groups that you
+ would like to be displayed on the unsubscribe
+ preferences page.
+ :type value: array(int)
+ """
+ if value is not None and len(value) > 25:
+ raise ValueError("New groups_to_display exceeds max length of 25.")
+ self._groups_to_display = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this GroupsToDisplay.
+
+ :returns: This GroupsToDisplay, ready for use in a request body.
+ :rtype: array of integers
+ """
+ return self.groups_to_display
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/header.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/header.py
new file mode 100644
index 00000000..7f3bd4c4
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/header.py
@@ -0,0 +1,94 @@
+class Header(object):
+ """A header to specify specific handling instructions for your email.
+
+ If the name or value contain Unicode characters, they must be properly
+ encoded. You may not overwrite the following reserved headers:
+ x-sg-id, x-sg-eid, received, dkim-signature, Content-Type,
+ Content-Transfer-Encoding, To, From, Subject, Reply-To, CC, BCC
+ """
+
+ def __init__(self, key=None, value=None, p=None):
+ """Create a Header.
+
+ :param key: The name of the header (e.g. "Date")
+ :type key: string, optional
+ :param value: The header's value (e.g. "2013-02-27 1:23:45 PM PDT")
+ :type value: string, optional
+ :param name: p is the Personalization object or Personalization object
+ index
+ :type name: Personalization, integer, optional
+ """
+ self._key = None
+ self._value = None
+ self._personalization = None
+
+ if key is not None:
+ self.key = key
+ if value is not None:
+ self.value = value
+ if p is not None:
+ self.personalization = p
+
+ @property
+ def key(self):
+ """The name of the header.
+
+ :rtype: string
+ """
+ return self._key
+
+ @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
+ def value(self):
+ """The value of the header.
+
+ :rtype: string
+ """
+ return self._value
+
+ @value.setter
+ def value(self, value):
+ """The value of the header.
+
+ :param value: The value of the header.
+ :type value: string
+ """
+ self._value = value
+
+ @property
+ def personalization(self):
+ """The Personalization object or Personalization object index
+
+ :rtype: Personalization, integer
+ """
+ return self._personalization
+
+ @personalization.setter
+ def personalization(self, value):
+ """The Personalization object or Personalization object index
+
+ :param value: The Personalization object or Personalization object
+ index
+ :type value: Personalization, integer
+ """
+ self._personalization = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this Header.
+
+ :returns: This Header, ready for use in a request body.
+ :rtype: dict
+ """
+ header = {}
+ if self.key is not None and self.value is not None:
+ header[self.key] = self.value
+ return header
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/html_content.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/html_content.py
new file mode 100644
index 00000000..c3f40d53
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/html_content.py
@@ -0,0 +1,59 @@
+from .content import Content
+from .validators import ValidateApiKey
+
+
+class HtmlContent(Content):
+ """HTML content to be included in your email."""
+
+ def __init__(self, content):
+ """Create an HtmlContent with the specified MIME type and content.
+
+ :param content: The HTML content.
+ :type content: string
+ """
+ self._content = None
+ self._validator = ValidateApiKey()
+
+ if content is not None:
+ self.content = content
+
+ @property
+ def mime_type(self):
+ """The MIME type for HTML content.
+
+ :rtype: string
+ """
+ return "text/html"
+
+ @property
+ def content(self):
+ """The actual HTML content.
+
+ :rtype: string
+ """
+ return self._content
+
+ @content.setter
+ def content(self, value):
+ """The actual HTML content.
+
+ :param value: The actual HTML content.
+ :type value: string
+ """
+ self._validator.validate_message_dict(value)
+ self._content = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this HtmlContent.
+
+ :returns: This HtmlContent, ready for use in a request body.
+ :rtype: dict
+ """
+ content = {}
+ if self.mime_type is not None:
+ content["type"] = self.mime_type
+
+ if self.content is not None:
+ content["value"] = self.content
+ return content
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/ip_pool_name.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/ip_pool_name.py
new file mode 100644
index 00000000..8ba8d91b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/ip_pool_name.py
@@ -0,0 +1,40 @@
+class IpPoolName(object):
+ """The IP Pool that you would like to send this email from."""
+
+ def __init__(self, ip_pool_name=None):
+ """Create a IpPoolName object
+
+ :param ip_pool_name: The IP Pool that you would like to send this
+ email from.
+ :type ip_pool_name: string, optional
+ """
+ self._ip_pool_name = None
+
+ if ip_pool_name is not None:
+ self.ip_pool_name = ip_pool_name
+
+ @property
+ def ip_pool_name(self):
+ """The IP Pool that you would like to send this email from.
+
+ :rtype: string
+ """
+ return self._ip_pool_name
+
+ @ip_pool_name.setter
+ def ip_pool_name(self, value):
+ """The IP Pool that you would like to send this email from.
+
+ :param value: The IP Pool that you would like to send this email from.
+ :type value: string
+ """
+ self._ip_pool_name = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this IpPoolName.
+
+ :returns: This IpPoolName, ready for use in a request body.
+ :rtype: string
+ """
+ return self.ip_pool_name
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/mail.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/mail.py
new file mode 100644
index 00000000..2472ad7d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/mail.py
@@ -0,0 +1,1041 @@
+"""Twilio SendGrid v3/mail/send response body builder"""
+from .bcc_email import Bcc
+from .cc_email import Cc
+from .content import Content
+from .custom_arg import CustomArg
+from .dynamic_template_data import DynamicTemplateData
+from .email import Email
+from .from_email import From
+from .header import Header
+from .mime_type import MimeType
+from .personalization import Personalization
+from .reply_to import ReplyTo
+from .send_at import SendAt
+from .subject import Subject
+from .substitution import Substitution
+from .template_id import TemplateId
+from .to_email import To
+
+
+class Mail(object):
+ """Creates the response body for v3/mail/send"""
+
+ def __init__(
+ self,
+ from_email=None,
+ to_emails=None,
+ subject=None,
+ plain_text_content=None,
+ html_content=None,
+ amp_html_content=None,
+ global_substitutions=None,
+ 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
+ :type subject: Subject, optional
+ :param to_emails: The email address of the recipient
+ :type to_emails: To, str, tuple, list(str), list(tuple),
+ list(To), optional
+ :param plain_text_content: The plain text body of the email
+ :type plain_text_content: string, optional
+ :param html_content: The html body of the email
+ :type html_content: string, optional
+ :param amp_html_content: The amp-html body of the email
+ :type amp_html_content: string, optional
+ """
+ self._attachments = None
+ self._categories = None
+ self._contents = None
+ self._custom_args = None
+ self._headers = None
+ self._personalizations = []
+ self._sections = None
+ self._asm = None
+ self._batch_id = None
+ self._from_email = None
+ self._ip_pool_name = None
+ self._mail_settings = None
+ self._reply_to = None
+ self._reply_to_list = None
+ self._send_at = None
+ self._subject = None
+ self._template_id = None
+ self._tracking_settings = None
+
+ # Minimum required data to send a single email
+ if from_email is not None:
+ self.from_email = from_email
+ if to_emails is not None:
+ self.add_to(to_emails, global_substitutions, is_multiple)
+ if subject is not None:
+ self.subject = subject
+ if plain_text_content is not None:
+ self.add_content(plain_text_content, MimeType.text)
+ if amp_html_content is not None:
+ self.add_content(amp_html_content, MimeType.amp)
+ if html_content is not None:
+ self.add_content(html_content, MimeType.html)
+
+ def __str__(self):
+ """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)
+ """
+ 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,
+ else return None
+ :type from_obj: obj
+ """
+ return from_obj.get() if from_obj is not None else None
+
+ def _set_emails(
+ self, emails, global_substitutions=None, is_multiple=False, p=0):
+ """Adds emails to the Personalization object
+
+ :param emails: An Email or list of Email objects
+ :type emails: Email, list(Email)
+ :param global_substitutions: A dict of substitutions for all recipients
+ :type global_substitutions: dict
+ :param is_multiple: Create a new personalization for each recipient
+ :type is_multiple: bool
+ :param p: p is the Personalization object or Personalization object
+ index
+ :type p: Personalization, integer, optional
+ """
+ # Send multiple emails to multiple recipients
+ if is_multiple is True:
+ if isinstance(emails, list):
+ for email in emails:
+ personalization = Personalization()
+ personalization.add_email(email)
+ self.add_personalization(personalization)
+ else:
+ personalization = Personalization()
+ personalization.add_email(emails)
+ self.add_personalization(personalization)
+ if global_substitutions is not None:
+ if isinstance(global_substitutions, list):
+ for substitution in global_substitutions:
+ for p in self.personalizations:
+ p.add_substitution(substitution)
+ else:
+ for p in self.personalizations:
+ p.add_substitution(global_substitutions)
+ else:
+ try:
+ personalization = self._personalizations[p]
+ has_internal_personalization = True
+ except IndexError:
+ personalization = Personalization()
+ has_internal_personalization = False
+
+ if isinstance(emails, list):
+ for email in emails:
+ personalization.add_email(email)
+ else:
+ personalization.add_email(emails)
+
+ if global_substitutions is not None:
+ if isinstance(global_substitutions, list):
+ for substitution in global_substitutions:
+ personalization.add_substitution(substitution)
+ else:
+ personalization.add_substitution(global_substitutions)
+
+ if not has_internal_personalization:
+ self.add_personalization(personalization, index=p)
+
+ @property
+ def personalizations(self):
+ """A list of one or more Personalization objects
+
+ :rtype: list(Personalization)
+ """
+ return self._personalizations
+
+ def add_personalization(self, personalization, index=0):
+ """Add a Personalization object
+
+ :param personalization: Add a Personalization object
+ :type personalization: Personalization
+ :param index: The index where to add the Personalization
+ :type index: int
+ """
+ self._personalizations = self._ensure_append(
+ personalization, self._personalizations, index)
+
+ @property
+ def to(self):
+ pass
+
+ @to.setter
+ def to(self, to_emails, global_substitutions=None, is_multiple=False, p=0):
+ """Adds To objects to the Personalization object
+
+ :param to_emails: The email addresses of all recipients
+ :type to_emails: To, str, tuple, list(str), list(tuple), list(To)
+ :param global_substitutions: A dict of substitutions for all recipients
+ :type global_substitutions: dict
+ :param is_multiple: Create a new personalization for each recipient
+ :type is_multiple: bool
+ :param p: p is the Personalization object or Personalization object
+ index
+ :type p: Personalization, integer, optional
+ """
+ if isinstance(to_emails, list):
+ for email in to_emails:
+ if isinstance(email, str):
+ email = To(email, None)
+ if isinstance(email, tuple):
+ email = To(email[0], email[1])
+ self.add_to(email, global_substitutions, is_multiple, p)
+ else:
+ if isinstance(to_emails, str):
+ to_emails = To(to_emails, None)
+ if isinstance(to_emails, tuple):
+ to_emails = To(to_emails[0], to_emails[1])
+ self.add_to(to_emails, global_substitutions, is_multiple, p)
+
+ def add_to(
+ self, to_email, global_substitutions=None, is_multiple=False, p=0):
+ """Adds a To object to the Personalization object
+
+ :param to_email: A To object
+ :type to_email: To, str, tuple, list(str), list(tuple), list(To)
+ :param global_substitutions: A dict of substitutions for all recipients
+ :type global_substitutions: dict
+ :param is_multiple: Create a new personalization for each recipient
+ :type is_multiple: bool
+ :param p: p is the Personalization object or Personalization object
+ index
+ :type p: Personalization, integer, optional
+ """
+
+ if isinstance(to_email, list):
+ for email in to_email:
+ if isinstance(email, str):
+ email = To(email, None)
+ elif isinstance(email, tuple):
+ email = To(email[0], email[1])
+ elif not isinstance(email, Email):
+ raise ValueError(
+ 'Please use a To/Cc/Bcc, tuple, or a str for a to_email list.'
+ )
+ self._set_emails(email, global_substitutions, is_multiple, p)
+ else:
+ if isinstance(to_email, str):
+ to_email = To(to_email, None)
+ if isinstance(to_email, tuple):
+ to_email = To(to_email[0], to_email[1])
+ if isinstance(to_email, Email):
+ p = to_email.personalization
+ self._set_emails(to_email, global_substitutions, is_multiple, p)
+
+ @property
+ def cc(self):
+ pass
+
+ @cc.setter
+ def cc(self, cc_emails, global_substitutions=None, is_multiple=False, p=0):
+ """Adds Cc objects to the Personalization object
+
+ :param cc_emails: An Cc or list of Cc objects
+ :type cc_emails: Cc, list(Cc), tuple
+ :param global_substitutions: A dict of substitutions for all recipients
+ :type global_substitutions: dict
+ :param is_multiple: Create a new personalization for each recipient
+ :type is_multiple: bool
+ :param p: p is the Personalization object or Personalization object
+ index
+ :type p: Personalization, integer, optional
+ """
+ if isinstance(cc_emails, list):
+ for email in cc_emails:
+ if isinstance(email, str):
+ email = Cc(email, None)
+ if isinstance(email, tuple):
+ email = Cc(email[0], email[1])
+ self.add_cc(email, global_substitutions, is_multiple, p)
+ else:
+ if isinstance(cc_emails, str):
+ cc_emails = Cc(cc_emails, None)
+ if isinstance(cc_emails, tuple):
+ cc_emails = To(cc_emails[0], cc_emails[1])
+ self.add_cc(cc_emails, global_substitutions, is_multiple, p)
+
+ def add_cc(
+ self, cc_email, global_substitutions=None, is_multiple=False, p=0):
+ """Adds a Cc object to the Personalization object
+
+ :param to_emails: An Cc object
+ :type to_emails: Cc
+ :param global_substitutions: A dict of substitutions for all recipients
+ :type global_substitutions: dict
+ :param is_multiple: Create a new personalization for each recipient
+ :type is_multiple: bool
+ :param p: p is the Personalization object or Personalization object
+ index
+ :type p: Personalization, integer, optional
+ """
+ if isinstance(cc_email, str):
+ cc_email = Cc(cc_email, None)
+ if isinstance(cc_email, tuple):
+ cc_email = Cc(cc_email[0], cc_email[1])
+ if isinstance(cc_email, Email):
+ p = cc_email.personalization
+ self._set_emails(
+ cc_email, global_substitutions, is_multiple=is_multiple, p=p)
+
+ @property
+ def bcc(self):
+ pass
+
+ @bcc.setter
+ def bcc(
+ self,
+ bcc_emails,
+ global_substitutions=None,
+ is_multiple=False,
+ p=0):
+ """Adds Bcc objects to the Personalization object
+
+ :param bcc_emails: An Bcc or list of Bcc objects
+ :type bcc_emails: Bcc, list(Bcc), tuple
+ :param global_substitutions: A dict of substitutions for all recipients
+ :type global_substitutions: dict
+ :param is_multiple: Create a new personalization for each recipient
+ :type is_multiple: bool
+ :param p: p is the Personalization object or Personalization object
+ index
+ :type p: Personalization, integer, optional
+ """
+ if isinstance(bcc_emails, list):
+ for email in bcc_emails:
+ if isinstance(email, str):
+ email = Bcc(email, None)
+ if isinstance(email, tuple):
+ email = Bcc(email[0], email[1])
+ self.add_bcc(email, global_substitutions, is_multiple, p)
+ else:
+ if isinstance(bcc_emails, str):
+ bcc_emails = Bcc(bcc_emails, None)
+ if isinstance(bcc_emails, tuple):
+ bcc_emails = Bcc(bcc_emails[0], bcc_emails[1])
+ self.add_bcc(bcc_emails, global_substitutions, is_multiple, p)
+
+ def add_bcc(
+ self,
+ bcc_email,
+ global_substitutions=None,
+ is_multiple=False,
+ p=0):
+ """Adds a Bcc object to the Personalization object
+
+ :param to_emails: An Bcc object
+ :type to_emails: Bcc
+ :param global_substitutions: A dict of substitutions for all recipients
+ :type global_substitutions: dict
+ :param is_multiple: Create a new personalization for each recipient
+ :type is_multiple: bool
+ :param p: p is the Personalization object or Personalization object
+ index
+ :type p: Personalization, integer, optional
+ """
+ if isinstance(bcc_email, str):
+ bcc_email = Bcc(bcc_email, None)
+ if isinstance(bcc_email, tuple):
+ bcc_email = Bcc(bcc_email[0], bcc_email[1])
+ if isinstance(bcc_email, Email):
+ p = bcc_email.personalization
+ self._set_emails(
+ bcc_email,
+ global_substitutions,
+ is_multiple=is_multiple,
+ p=p)
+
+ @property
+ def subject(self):
+ """The global 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]
+ 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 headers(self):
+ """A list of global Header objects
+
+ :rtype: list(Header)
+ """
+ return self._headers
+
+ @property
+ def header(self):
+ pass
+
+ @header.setter
+ def header(self, headers):
+ """Add headers to the email
+
+ :param value: A list of Header objects or a dict of header key/values
+ :type value: Header, list(Header), dict
+ """
+ if isinstance(headers, list):
+ for h in headers:
+ self.add_header(h)
+ else:
+ self.add_header(headers)
+
+ 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]
+ 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 substitution(self):
+ pass
+
+ @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)
+ else:
+ 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]
+ 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):
+ """A list of global CustomArg objects
+
+ :rtype: list(CustomArg)
+ """
+ return self._custom_args
+
+ @property
+ def custom_arg(self):
+ return self._custom_args
+
+ @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)
+ else:
+ 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]
+ 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 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:
+ 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 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-serializable structure
+ """
+ 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):
+ """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 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
+
+ :rtype: list(Content)
+ """
+ return self._contents
+
+ @property
+ def content(self):
+ pass
+
+ @content.setter
+ def content(self, contents):
+ """The content(s) of the email
+
+ :param contents: The content(s) of the email
+ :type contents: Content, list(Content)
+ """
+ if isinstance(contents, list):
+ for c in contents:
+ self.add_content(c)
+ else:
+ self.add_content(contents)
+
+ 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, followed by text/x-amp-html and then text/html
+ if content.mime_type == MimeType.text:
+ self._contents = self._ensure_insert(content, self._contents)
+ elif content.mime_type == MimeType.amp:
+ if self._contents:
+ for _content in self._contents:
+ # this is written in the context that plain text content will always come earlier than the html content
+ if _content.mime_type == MimeType.text:
+ index = 1
+ break
+ elif _content.mime_type == MimeType.html:
+ index = 0
+ break
+ else:
+ index = 0
+ self._contents = self._ensure_append(
+ content, self._contents, index=index)
+ else:
+ if self._contents:
+ index = len(self._contents)
+ else:
+ index = 0
+ self._contents = self._ensure_append(
+ content, self._contents, index=index)
+
+ @property
+ def attachments(self):
+ """The attachments 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)
+ """
+ if isinstance(attachment, list):
+ for a in attachment:
+ self.add_attachment(a)
+ else:
+ self.add_attachment(attachment)
+
+ def add_attachment(self, attachment):
+ """Add an attachment to this 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):
+ """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
+ 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)
+
+ 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
+ def category(self):
+ pass
+
+ @category.setter
+ def category(self, categories):
+ """Add categories assigned to this message
+
+ :rtype: list(Category)
+ """
+ if isinstance(categories, list):
+ for c in categories:
+ self.add_category(c)
+ else:
+ self.add_category(categories)
+
+ def add_category(self, category):
+ """Add a category 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):
+ """
+ Get a JSON-ready representation of this Mail object.
+
+ :returns: This Mail object, ready for use in a request body.
+ :rtype: dict
+ """
+ mail = {
+ 'from': self._get_or_none(self.from_email),
+ 'subject': self._get_or_none(self.subject),
+ 'personalizations': [p.get() for p in self.personalizations or []],
+ 'content': [c.get() for c in self.contents or []],
+ 'attachments': [a.get() for a in self.attachments or []],
+ 'template_id': self._get_or_none(self.template_id),
+ 'sections': self._flatten_dicts(self.sections),
+ 'headers': self._flatten_dicts(self.headers),
+ 'categories': [c.get() for c in self.categories or []],
+ 'custom_args': self._flatten_dicts(self.custom_args),
+ 'send_at': self._get_or_none(self.send_at),
+ 'batch_id': self._get_or_none(self.batch_id),
+ 'asm': self._get_or_none(self.asm),
+ 'ip_pool_name': self._get_or_none(self.ip_pool_name),
+ 'mail_settings': self._get_or_none(self.mail_settings),
+ 'tracking_settings': self._get_or_none(self.tracking_settings),
+ 'reply_to': self._get_or_none(self.reply_to),
+ 'reply_to_list': [r.get() for r in self.reply_to_list or []],
+ }
+
+ return {key: value for key, value in mail.items()
+ if value is not None and value != [] and value != {}}
+
+ @classmethod
+ def from_EmailMessage(cls, message):
+ """Create a Mail object from an instance of
+ email.message.EmailMessage.
+
+ :type message: email.message.EmailMessage
+ :rtype: Mail
+ """
+ mail = cls(
+ from_email=Email(message.get('From')),
+ subject=message.get('Subject'),
+ to_emails=Email(message.get('To')),
+ )
+ try:
+ body = message.get_content()
+ except AttributeError:
+ # Python2
+ body = message.get_payload()
+ mail.add_content(Content(
+ message.get_content_type(),
+ body.strip()
+ ))
+ for k, v in message.items():
+ mail.add_header(Header(k, v))
+ return mail
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/mail_settings.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/mail_settings.py
new file mode 100644
index 00000000..78499ac3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/mail_settings.py
@@ -0,0 +1,243 @@
+class MailSettings(object):
+ """A collection of mail settings that specify how to handle this email."""
+
+ def __init__(self,
+ bcc_settings=None,
+ bypass_bounce_management=None,
+ bypass_list_management=None,
+ bypass_spam_management=None,
+ bypass_unsubscribe_management=None,
+ footer_settings=None,
+ sandbox_mode=None,
+ spam_check=None):
+ """Create a MailSettings object
+
+ :param bcc_settings: The BCC Settings of this MailSettings
+ :type bcc_settings: BCCSettings, optional
+ :param bypass_bounce_management: Whether this MailSettings bypasses bounce management.
+ Should not be combined with bypass_list_management.
+ :type bypass_list_management: BypassBounceManagement, optional
+ :param bypass_list_management: Whether this MailSettings bypasses list
+ management
+ :type bypass_list_management: BypassListManagement, optional
+ :param bypass_spam_management: Whether this MailSettings bypasses spam management.
+ Should not be combined with bypass_list_management.
+ :type bypass_list_management: BypassSpamManagement, optional
+ :param bypass_unsubscribe_management: Whether this MailSettings bypasses unsubscribe management.
+ Should not be combined with bypass_list_management.
+ :type bypass_list_management: BypassUnsubscribeManagement, optional
+ :param footer_settings: The default footer specified by this
+ MailSettings
+ :type footer_settings: FooterSettings, optional
+ :param sandbox_mode: Whether this MailSettings enables sandbox mode
+ :type sandbox_mode: SandBoxMode, optional
+ :param spam_check: How this MailSettings requests email to be checked
+ for spam
+ :type spam_check: SpamCheck, optional
+ """
+ self._bcc_settings = None
+ self._bypass_bounce_management = None
+ self._bypass_list_management = None
+ self._bypass_spam_management = None
+ self._bypass_unsubscribe_management = None
+ self._footer_settings = None
+ self._sandbox_mode = None
+ self._spam_check = None
+
+ if bcc_settings is not None:
+ self.bcc_settings = bcc_settings
+
+ if bypass_bounce_management is not None:
+ self.bypass_bounce_management = bypass_bounce_management
+
+ if bypass_list_management is not None:
+ self.bypass_list_management = bypass_list_management
+
+ if bypass_spam_management is not None:
+ self.bypass_spam_management = bypass_spam_management
+
+ if bypass_unsubscribe_management is not None:
+ self.bypass_unsubscribe_management = bypass_unsubscribe_management
+
+ if footer_settings is not None:
+ self.footer_settings = footer_settings
+
+ if sandbox_mode is not None:
+ self.sandbox_mode = sandbox_mode
+
+ if spam_check is not None:
+ self.spam_check = spam_check
+
+ @property
+ def bcc_settings(self):
+ """The BCC Settings of this MailSettings.
+
+ :rtype: BCCSettings
+ """
+ return self._bcc_settings
+
+ @bcc_settings.setter
+ def bcc_settings(self, value):
+ """The BCC Settings of this MailSettings.
+
+ :param value: The BCC Settings of this MailSettings.
+ :type value: BCCSettings
+ """
+ self._bcc_settings = value
+
+ @property
+ def bypass_bounce_management(self):
+ """Whether this MailSettings bypasses bounce management.
+
+ :rtype: BypassBounceManagement
+ """
+ return self._bypass_bounce_management
+
+ @bypass_bounce_management.setter
+ def bypass_bounce_management(self, value):
+ """Whether this MailSettings bypasses bounce management.
+
+ :param value: Whether this MailSettings bypasses bounce management.
+ :type value: BypassBounceManagement
+ """
+ self._bypass_bounce_management = value
+
+ @property
+ def bypass_list_management(self):
+ """Whether this MailSettings bypasses list management.
+
+ :rtype: BypassListManagement
+ """
+ return self._bypass_list_management
+
+ @bypass_list_management.setter
+ def bypass_list_management(self, value):
+ """Whether this MailSettings bypasses list management.
+
+ :param value: Whether this MailSettings bypasses list management.
+ :type value: BypassListManagement
+ """
+ self._bypass_list_management = value
+
+ @property
+ def bypass_spam_management(self):
+ """Whether this MailSettings bypasses spam management.
+
+ :rtype: BypassSpamManagement
+ """
+ return self._bypass_spam_management
+
+ @bypass_spam_management.setter
+ def bypass_spam_management(self, value):
+ """Whether this MailSettings bypasses spam management.
+
+ :param value: Whether this MailSettings bypasses spam management.
+ :type value: BypassSpamManagement
+ """
+ self._bypass_spam_management = value
+
+ @property
+ def bypass_unsubscribe_management(self):
+ """Whether this MailSettings bypasses unsubscribe management.
+
+ :rtype: BypassUnsubscribeManagement
+ """
+ return self._bypass_unsubscribe_management
+
+ @bypass_unsubscribe_management.setter
+ def bypass_unsubscribe_management(self, value):
+ """Whether this MailSettings bypasses unsubscribe management.
+
+ :param value: Whether this MailSettings bypasses unsubscribe management.
+ :type value: BypassUnsubscribeManagement
+ """
+ self._bypass_unsubscribe_management = value
+
+ @property
+ def footer_settings(self):
+ """The default footer specified by this MailSettings.
+
+ :rtype: FooterSettings
+ """
+ return self._footer_settings
+
+ @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
+ def sandbox_mode(self):
+ """Whether this MailSettings enables sandbox mode.
+
+ :rtype: SandBoxMode
+ """
+ return self._sandbox_mode
+
+ @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
+ def spam_check(self):
+ """How this MailSettings requests email to be checked for spam.
+
+ :rtype: SpamCheck
+ """
+ return self._spam_check
+
+ @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):
+ """
+ Get a JSON-ready representation of this MailSettings.
+
+ :returns: This MailSettings, ready for use in a request body.
+ :rtype: dict
+ """
+ mail_settings = {}
+ 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()
+
+ if self.sandbox_mode is not None:
+ mail_settings["sandbox_mode"] = self.sandbox_mode.get()
+
+ if self.spam_check is not None:
+ mail_settings["spam_check"] = self.spam_check.get()
+ return mail_settings
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/mime_type.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/mime_type.py
new file mode 100644
index 00000000..a2f88c5a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/mime_type.py
@@ -0,0 +1,6 @@
+class MimeType(object):
+ """The MIME type of the content included in your email.
+ """
+ text = "text/plain"
+ html = "text/html"
+ amp = "text/x-amp-html"
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/open_tracking.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/open_tracking.py
new file mode 100644
index 00000000..7124a2e6
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/open_tracking.py
@@ -0,0 +1,80 @@
+class OpenTracking(object):
+ """
+ Allows you to track whether the email was opened or not, by including a
+ single pixel image in the body of the content. When the pixel is loaded,
+ we log that the email was opened.
+ """
+
+ def __init__(self, enable=None, substitution_tag=None):
+ """Create an OpenTracking to track when your email is opened.
+
+ :param enable: If open tracking is enabled.
+ :type enable: boolean, optional
+ :param substitution_tag: Tag in body to be replaced by tracking pixel.
+ :type substitution_tag: OpenTrackingSubstitionTag, optional
+ """
+ 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):
+ """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
+
+ @property
+ def 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._substitution_tag
+
+ @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):
+ """
+ Get a JSON-ready representation of this OpenTracking.
+
+ :returns: This OpenTracking, ready for use in a request body.
+ :rtype: dict
+ """
+ open_tracking = {}
+ if self.enable is not None:
+ open_tracking["enable"] = self.enable
+
+ if self.substitution_tag is not None:
+ open_tracking["substitution_tag"] = self.substitution_tag.get()
+ return open_tracking
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/open_tracking_substitution_tag.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/open_tracking_substitution_tag.py
new file mode 100644
index 00000000..d9967f9c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/open_tracking_substitution_tag.py
@@ -0,0 +1,49 @@
+class OpenTrackingSubstitutionTag(object):
+ """The open tracking substitution tag of an SubscriptionTracking object."""
+
+ def __init__(self, open_tracking_substitution_tag=None):
+ """Create a OpenTrackingSubstitutionTag object
+
+ :param open_tracking_substitution_tag: Allows you to specify a
+ substitution tag that you can insert in the body of your
+ email at a location that you desire. This tag will be replaced
+ by the open tracking pixel.
+ """
+ self._open_tracking_substitution_tag = None
+
+ if open_tracking_substitution_tag is not None:
+ self.open_tracking_substitution_tag = \
+ open_tracking_substitution_tag
+
+ @property
+ def open_tracking_substitution_tag(self):
+ """Allows you to specify a substitution tag that you can insert in
+ the body of your email at a location that you desire. This tag
+ will be replaced by the open tracking pixel.
+
+ :rtype: string
+ """
+ return self._open_tracking_substitution_tag
+
+ @open_tracking_substitution_tag.setter
+ def open_tracking_substitution_tag(self, value):
+ """Allows you to specify a substitution tag that you can insert in
+ the body of your email at a location that you desire. This tag will
+ be replaced by the open tracking pixel.
+
+ :param value: Allows you to specify a substitution tag that you can
+ insert in the body of your email at a location that you desire. This
+ tag will be replaced by the open tracking pixel.
+ :type value: string
+ """
+ self._open_tracking_substitution_tag = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this OpenTrackingSubstitutionTag.
+
+ :returns: This OpenTrackingSubstitutionTag, ready for use in a request
+ body.
+ :rtype: string
+ """
+ return self.open_tracking_substitution_tag
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/personalization.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/personalization.py
new file mode 100644
index 00000000..a4e1c1de
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/personalization.py
@@ -0,0 +1,271 @@
+class Personalization(object):
+ """A Personalization defines who should receive an individual message and
+ how that message should be handled.
+ """
+
+ def __init__(self):
+ """Create an empty Personalization and initialize member variables."""
+ self._tos = []
+ self._from_email = None
+ self._ccs = []
+ self._bccs = []
+ self._subject = None
+ self._headers = []
+ self._substitutions = []
+ self._custom_args = []
+ self._send_at = None
+ self._dynamic_template_data = None
+
+ def add_email(self, email):
+ email_type = type(email)
+ if email_type.__name__ == 'To':
+ self.add_to(email)
+ return
+ if email_type.__name__ == 'Cc':
+ self.add_cc(email)
+ return
+ if email_type.__name__ == 'Bcc':
+ self.add_bcc(email)
+ return
+ if email_type.__name__ == 'From':
+ self.from_email = email
+ return
+ raise ValueError('Please use a To, From, Cc or Bcc object.')
+
+ def _get_unique_recipients(self, recipients):
+ unique_recipients = []
+
+ for recipient in recipients:
+ recipient_email = recipient['email'].lower() if isinstance(recipient, dict) else recipient.email.lower()
+ if all(
+ unique_recipient['email'].lower() != recipient_email for unique_recipient in unique_recipients
+ ):
+ new_unique_recipient = recipient if isinstance(recipient, dict) else recipient.get()
+ unique_recipients.append(new_unique_recipient)
+
+ return unique_recipients
+
+
+ @property
+ def tos(self):
+ """A list of recipients for this Personalization.
+
+ :rtype: list(dict)
+ """
+ return self._get_unique_recipients(self._tos)
+
+ @tos.setter
+ def tos(self, value):
+ self._tos = value
+
+ def add_to(self, email):
+ """Add a single recipient to this Personalization.
+
+ :type email: Email
+ """
+ if email.substitutions:
+ if isinstance(email.substitutions, list):
+ for substitution in email.substitutions:
+ self.add_substitution(substitution)
+ else:
+ self.add_substitution(email.substitutions)
+
+ if email.dynamic_template_data:
+ self.dynamic_template_data = email.dynamic_template_data
+
+ if email.subject:
+ if isinstance(email.subject, str):
+ self.subject = email.subject
+ else:
+ self.subject = email.subject.get()
+
+ self._tos.append(email.get())
+
+ @property
+ def from_email(self):
+ return self._from_email
+
+ @from_email.setter
+ def from_email(self, value):
+ self._from_email = value
+
+ def set_from(self, email):
+ self._from_email = email.get()
+
+ @property
+ def ccs(self):
+ """A list of recipients who will receive copies of this email.
+
+ :rtype: list(dict)
+ """
+ return self._get_unique_recipients(self._ccs)
+
+ @ccs.setter
+ def ccs(self, value):
+ self._ccs = value
+
+ def add_cc(self, email):
+ """Add a single recipient to receive a copy of this email.
+
+ :param email: new recipient to be CCed
+ :type email: Email
+ """
+ self._ccs.append(email.get())
+
+ @property
+ def bccs(self):
+ """A list of recipients who will receive blind carbon copies of this email.
+
+ :rtype: list(dict)
+ """
+ return self._get_unique_recipients(self._bccs)
+
+ @bccs.setter
+ def bccs(self, value):
+ self._bccs = value
+
+ def add_bcc(self, email):
+ """Add a single recipient to receive a blind carbon copy of this email.
+
+ :param email: new recipient to be BCCed
+ :type email: Email
+ """
+ self._bccs.append(email.get())
+
+ @property
+ def subject(self):
+ """The subject of your email (within this Personalization).
+
+ Char length requirements, according to the RFC:
+ https://stackoverflow.com/a/1592310
+
+ :rtype: string
+ """
+ return self._subject
+
+ @subject.setter
+ def subject(self, value):
+ self._subject = value
+
+ @property
+ def headers(self):
+ """The headers for emails in this Personalization.
+
+ :rtype: list(dict)
+ """
+ return self._headers
+
+ @headers.setter
+ def headers(self, value):
+ self._headers = value
+
+ def add_header(self, header):
+ """Add a single Header to this Personalization.
+
+ :type header: Header
+ """
+ self._headers.append(header.get())
+
+ @property
+ def substitutions(self):
+ """Substitutions to be applied within this Personalization.
+
+ :rtype: list(dict)
+ """
+ return self._substitutions
+
+ @substitutions.setter
+ def substitutions(self, value):
+ self._substitutions = value
+
+ def add_substitution(self, substitution):
+ """Add a new Substitution to this Personalization.
+
+ :type substitution: Substitution
+ """
+ if not isinstance(substitution, dict):
+ substitution = substitution.get()
+
+ self._substitutions.append(substitution)
+
+ @property
+ def custom_args(self):
+ """The CustomArgs that will be carried along with this Personalization.
+
+ :rtype: list(dict)
+ """
+ return self._custom_args
+
+ @custom_args.setter
+ def custom_args(self, value):
+ self._custom_args = value
+
+ def add_custom_arg(self, custom_arg):
+ """Add a CustomArg to this Personalization.
+
+ :type custom_arg: CustomArg
+ """
+ self._custom_args.append(custom_arg.get())
+
+ @property
+ def send_at(self):
+ """A unix timestamp allowing you to specify when you want emails from
+ this Personalization to be delivered. 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 dynamic_template_data(self):
+ """Data for dynamic transactional template.
+ Should be JSON-serializable structure.
+
+ :rtype: JSON-serializable structure
+ """
+ return self._dynamic_template_data
+
+ @dynamic_template_data.setter
+ def dynamic_template_data(self, value):
+ if not isinstance(value, dict):
+ value = value.get()
+
+ self._dynamic_template_data = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this Personalization.
+
+ :returns: This Personalization, ready for use in a request body.
+ :rtype: dict
+ """
+ personalization = {}
+
+ for key in ['tos', 'ccs', 'bccs']:
+ value = getattr(self, key)
+ if value:
+ personalization[key[:-1]] = value
+
+ from_value = getattr(self, 'from_email')
+ if from_value:
+ personalization['from'] = from_value
+
+ for key in ['subject', 'send_at', 'dynamic_template_data']:
+ value = getattr(self, key)
+ if value:
+ personalization[key] = value
+
+ for prop_name in ['headers', 'substitutions', 'custom_args']:
+ prop = getattr(self, prop_name)
+ if prop:
+ obj = {}
+ for key in prop:
+ obj.update(key)
+ personalization[prop_name] = obj
+
+ return personalization
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/plain_text_content.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/plain_text_content.py
new file mode 100644
index 00000000..78bce1a8
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/plain_text_content.py
@@ -0,0 +1,60 @@
+from .content import Content
+from .validators import ValidateApiKey
+
+
+class PlainTextContent(Content):
+ """Plain text content to be included in your email.
+ """
+
+ def __init__(self, content):
+ """Create a PlainTextContent with the specified MIME type and content.
+
+ :param content: The actual text content.
+ :type content: string
+ """
+ self._content = None
+ self._validator = ValidateApiKey()
+
+ if content is not None:
+ self.content = content
+
+ @property
+ def mime_type(self):
+ """The MIME type.
+
+ :rtype: string
+ """
+ return "text/plain"
+
+ @property
+ def content(self):
+ """The actual text content.
+
+ :rtype: string
+ """
+ return self._content
+
+ @content.setter
+ def content(self, value):
+ """The actual text content.
+
+ :param value: The actual text content.
+ :type value: string
+ """
+ self._validator.validate_message_dict(value)
+ self._content = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this PlainTextContent.
+
+ :returns: This PlainTextContent, ready for use in a request body.
+ :rtype: dict
+ """
+ content = {}
+ if self.mime_type is not None:
+ content["type"] = self.mime_type
+
+ if self.content is not None:
+ content["value"] = self.content
+ return content
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/reply_to.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/reply_to.py
new file mode 100644
index 00000000..731e2ad8
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/reply_to.py
@@ -0,0 +1,5 @@
+from .email import Email
+
+
+class ReplyTo(Email):
+ """A reply to email address with an optional name."""
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/sandbox_mode.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/sandbox_mode.py
new file mode 100644
index 00000000..e8990ee9
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/sandbox_mode.py
@@ -0,0 +1,44 @@
+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.
+ """
+ def __init__(self, enable=None):
+ """Create an enabled or disabled SandBoxMode.
+
+ :param enable: Whether this is a test request.
+ :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 SandBoxMode.
+
+ :returns: This SandBoxMode, ready for use in a request body.
+ :rtype: dict
+ """
+ sandbox_mode = {}
+ if self.enable is not None:
+ sandbox_mode["enable"] = self.enable
+ return sandbox_mode
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/section.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/section.py
new file mode 100644
index 00000000..cea949b1
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/section.py
@@ -0,0 +1,64 @@
+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.
+
+ :param key: section of code key
+ :type key: string
+ :param value: section of code value
+ :type value: string
+ """
+ self._key = None
+ self._value = None
+
+ if key is not None:
+ self.key = key
+ if value is not None:
+ self.value = value
+
+ @property
+ def key(self):
+ """A section of code's key.
+
+ :rtype key: string
+ """
+ return self._key
+
+ @key.setter
+ def key(self, value):
+ """A section of code's key.
+
+ :param key: section of code key
+ :type key: string
+ """
+ self._key = value
+
+ @property
+ def value(self):
+ """A section of code's value.
+
+ :rtype: string
+ """
+ return self._value
+
+ @value.setter
+ def value(self, value):
+ """A section of code's value.
+
+ :param value: A section of code's value.
+ :type value: string
+ """
+ self._value = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this Section.
+
+ :returns: This Section, ready for use in a request body.
+ :rtype: dict
+ """
+ section = {}
+ if self.key is not None and self.value is not None:
+ section[self.key] = self.value
+ return section
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/send_at.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/send_at.py
new file mode 100644
index 00000000..6e3a1541
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/send_at.py
@@ -0,0 +1,79 @@
+class SendAt(object):
+ """A unix timestamp allowing you to specify when you want your
+ email to be delivered. This may be overridden by the
+ personalizations[x].send_at parameter. You can't schedule more
+ than 72 hours in advance. If you have the flexibility, it's
+ better to schedule mail for off-peak times. Most emails are
+ scheduled and sent at the top of the hour or half hour.
+ Scheduling email to avoid those times (for example, scheduling
+ at 10:53) can result in lower deferral rates because it won't
+ be going through our servers at the same times as everyone else's
+ mail."""
+ def __init__(self, send_at=None, p=None):
+ """Create a unix timestamp specifying when your email should
+ be delivered.
+
+ :param send_at: Unix timestamp
+ :type send_at: integer
+ :param name: p is the Personalization object or Personalization object
+ index
+ :type name: Personalization, integer, optional
+ """
+ self._send_at = None
+ self._personalization = None
+
+ if send_at is not None:
+ self.send_at = send_at
+ if p is not None:
+ self.personalization = p
+
+ @property
+ def send_at(self):
+ """A unix timestamp.
+
+ :rtype: integer
+ """
+ return self._send_at
+
+ @send_at.setter
+ def send_at(self, value):
+ """A unix timestamp.
+
+ :param value: A unix timestamp.
+ :type value: integer
+ """
+ self._send_at = value
+
+ @property
+ def personalization(self):
+ """The Personalization object or Personalization object index
+
+ :rtype: Personalization, integer
+ """
+ return self._personalization
+
+ @personalization.setter
+ def personalization(self, value):
+ """The Personalization object or Personalization object index
+
+ :param value: The Personalization object or Personalization object
+ index
+ :type value: Personalization, integer
+ """
+ self._personalization = value
+
+ def __str__(self):
+ """Get a JSON representation of this object.
+
+ :rtype: integer
+ """
+ return str(self.get())
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this SendAt object.
+
+ :returns: The unix timestamp, ready for use in a request body.
+ :rtype: integer
+ """
+ return self.send_at
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/spam_check.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/spam_check.py
new file mode 100644
index 00000000..c584f8cf
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/spam_check.py
@@ -0,0 +1,112 @@
+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."""
+
+ def __init__(self, enable=None, threshold=None, post_to_url=None):
+ """Create a SpamCheck to test the content of your email for spam.
+
+ :param enable: If this setting is applied.
+ :type enable: boolean, optional
+ :param threshold: Spam qualification threshold, from 1 to 10 (strict).
+ :type threshold: int, optional
+ :param post_to_url: Inbound Parse URL to send a copy of your email.
+ :type post_to_url: string, optional
+ """
+ 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):
+ """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
+
+ @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:
+ self._threshold = SpamThreshold(value)
+
+ @property
+ def post_to_url(self):
+ """An Inbound Parse URL to send a copy of your email.
+ If defined, a copy of your email and its spam report will be sent here.
+
+ :rtype: string
+ """
+ return self._post_to_url
+
+ @post_to_url.setter
+ def post_to_url(self, value):
+ """An Inbound Parse URL to send a copy of your email.
+ If defined, a copy of your email and its spam report will be sent here.
+
+ :param value: An Inbound Parse URL to send a copy of your email.
+ If defined, a copy of your email and its spam report will be sent here.
+ :type value: string
+ """
+ if isinstance(value, SpamUrl):
+ self._post_to_url = value
+ else:
+ self._post_to_url = SpamUrl(value)
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this SpamCheck.
+
+ :returns: This SpamCheck, ready for use in a request body.
+ :rtype: dict
+ """
+ spam_check = {}
+ if self.enable is not None:
+ spam_check["enable"] = self.enable
+
+ if self.threshold is not None:
+ spam_check["threshold"] = self.threshold.get()
+
+ if self.post_to_url is not None:
+ spam_check["post_to_url"] = self.post_to_url.get()
+ return spam_check
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/spam_threshold.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/spam_threshold.py
new file mode 100644
index 00000000..45053dbd
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/spam_threshold.py
@@ -0,0 +1,53 @@
+class SpamThreshold(object):
+ """The threshold used to determine if your content qualifies as spam
+ on a scale from 1 to 10, with 10 being most strict, or most likely
+ to be considered as spam."""
+
+ def __init__(self, spam_threshold=None):
+ """Create a SpamThreshold object
+
+ :param spam_threshold: The threshold used to determine if your content
+ qualifies as spam on a scale from 1 to 10, with
+ 10 being most strict, or most likely to be
+ considered as spam.
+ :type spam_threshold: integer, optional
+ """
+ self._spam_threshold = None
+
+ if spam_threshold is not None:
+ self.spam_threshold = spam_threshold
+
+ @property
+ def spam_threshold(self):
+ """The threshold used to determine if your content
+ qualifies as spam on a scale from 1 to 10, with
+ 10 being most strict, or most likely to be
+ considered as spam.
+
+ :rtype: integer
+ """
+ return self._spam_threshold
+
+ @spam_threshold.setter
+ def spam_threshold(self, value):
+ """The threshold used to determine if your content
+ qualifies as spam on a scale from 1 to 10, with
+ 10 being most strict, or most likely to be
+ considered as spam.
+
+ :param value: The threshold used to determine if your content
+ qualifies as spam on a scale from 1 to 10, with
+ 10 being most strict, or most likely to be
+ considered as spam.
+ :type value: integer
+ """
+ self._spam_threshold = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this SpamThreshold.
+
+ :returns: This SpamThreshold, ready for use in a request body.
+ :rtype: integer
+ """
+ return self.spam_threshold
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/spam_url.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/spam_url.py
new file mode 100644
index 00000000..529438ce
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/spam_url.py
@@ -0,0 +1,44 @@
+class SpamUrl(object):
+ """An Inbound Parse URL that you would like a copy of your email
+ along with the spam report to be sent to."""
+
+ def __init__(self, spam_url=None):
+ """Create a SpamUrl object
+
+ :param spam_url: An Inbound Parse URL that you would like a copy of
+ your email along with the spam report to be sent to.
+ :type spam_url: string, optional
+ """
+ self._spam_url = None
+
+ if spam_url is not None:
+ self.spam_url = spam_url
+
+ @property
+ def spam_url(self):
+ """An Inbound Parse URL that you would like a copy of your email
+ along with the spam report to be sent to.
+
+ :rtype: string
+ """
+ return self._spam_url
+
+ @spam_url.setter
+ def spam_url(self, value):
+ """An Inbound Parse URL that you would like a copy of your email
+ along with the spam report to be sent to.
+
+ :param value: An Inbound Parse URL that you would like a copy of your
+ email along with the spam report to be sent to.
+ :type value: string
+ """
+ self._spam_url = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this SpamUrl.
+
+ :returns: This SpamUrl, ready for use in a request body.
+ :rtype: string
+ """
+ return self.spam_url
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subject.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subject.py
new file mode 100644
index 00000000..1637f400
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subject.py
@@ -0,0 +1,69 @@
+class Subject(object):
+ """A subject for an email message."""
+
+ def __init__(self, subject, p=None):
+ """Create a Subject.
+
+ :param subject: The subject for an email
+ :type subject: string
+ :param name: p is the Personalization object or Personalization object
+ index
+ :type name: Personalization, integer, optional
+ """
+ self._subject = None
+ self._personalization = None
+
+ self.subject = subject
+ if p is not None:
+ self.personalization = p
+
+ @property
+ def subject(self):
+ """The subject of an email.
+
+ :rtype: string
+ """
+ return self._subject
+
+ @subject.setter
+ def subject(self, value):
+ """The subject of an email.
+
+ :param value: The subject of an email.
+ :type value: string
+ """
+ self._subject = value
+
+ @property
+ def personalization(self):
+ """The Personalization object or Personalization object index
+
+ :rtype: Personalization, integer
+ """
+ return self._personalization
+
+ @personalization.setter
+ def personalization(self, value):
+ """The Personalization object or Personalization object index
+
+ :param value: The Personalization object or Personalization object
+ index
+ :type value: Personalization, integer
+ """
+ self._personalization = value
+
+ def __str__(self):
+ """Get a JSON representation of this Mail request.
+
+ :rtype: string
+ """
+ return str(self.get())
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this Subject.
+
+ :returns: This Subject, ready for use in a request body.
+ :rtype: string
+ """
+ return self.subject
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subscription_html.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subscription_html.py
new file mode 100644
index 00000000..bfe8629f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subscription_html.py
@@ -0,0 +1,45 @@
+class SubscriptionHtml(object):
+ """The HTML of an SubscriptionTracking."""
+
+ def __init__(self, subscription_html=None):
+ """Create a SubscriptionHtml object
+
+ :param subscription_html: Html to be appended to the email, with the
+ subscription tracking link. You may control
+ where the link is by using the tag <% %>
+ :type subscription_html: string, optional
+ """
+ self._subscription_html = None
+
+ if subscription_html is not None:
+ self.subscription_html = subscription_html
+
+ @property
+ def subscription_html(self):
+ """Html to be appended to the email, with the subscription tracking link.
+ You may control where the link is by using the tag <% %>
+
+ :rtype: string
+ """
+ return self._subscription_html
+
+ @subscription_html.setter
+ def subscription_html(self, value):
+ """Html to be appended to the email, with the subscription tracking link.
+ You may control where the link is by using the tag <% %>
+
+ :param value: Html to be appended to the email, with the subscription
+ tracking link. You may control where the link is by using
+ the tag <% %>
+ :type value: string
+ """
+ self._subscription_html = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this SubscriptionHtml.
+
+ :returns: This SubscriptionHtml, ready for use in a request body.
+ :rtype: string
+ """
+ return self.subscription_html
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subscription_substitution_tag.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subscription_substitution_tag.py
new file mode 100644
index 00000000..9a7d6a95
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subscription_substitution_tag.py
@@ -0,0 +1,58 @@
+class SubscriptionSubstitutionTag(object):
+ """The subscription substitution tag of an SubscriptionTracking."""
+
+ def __init__(self, subscription_substitution_tag=None):
+ """Create a SubscriptionSubstitutionTag object
+
+ :param subscription_substitution_tag: A tag that will be replaced with
+ the unsubscribe URL. for example:
+ [unsubscribe_url]. If this
+ parameter is used, it will
+ override both the text and html
+ parameters. The URL of the link
+ will be placed at the
+ substitution tag's location,
+ with no additional formatting.
+ :type subscription_substitution_tag: string, optional
+ """
+ self._subscription_substitution_tag = None
+
+ if subscription_substitution_tag is not None:
+ self.subscription_substitution_tag = subscription_substitution_tag
+
+ @property
+ def subscription_substitution_tag(self):
+ """A tag that will be replaced with the unsubscribe URL. for example:
+ [unsubscribe_url]. If this parameter is used, it will override both
+ the text and html parameters. The URL of the link will be placed at
+ the substitution tag's location, with no additional formatting.
+
+ :rtype: string
+ """
+ return self._subscription_substitution_tag
+
+ @subscription_substitution_tag.setter
+ def subscription_substitution_tag(self, value):
+ """A tag that will be replaced with the unsubscribe URL. for example:
+ [unsubscribe_url]. If this parameter is used, it will override both
+ the text and html parameters. The URL of the link will be placed at
+ the substitution tag's location, with no additional formatting.
+
+ :param value: A tag that will be replaced with the unsubscribe URL.
+ for example: [unsubscribe_url]. If this parameter is
+ used, it will override both the text and html parameters.
+ The URL of the link will be placed at the substitution
+ tag's location, with no additional formatting.
+ :type value: string
+ """
+ self._subscription_substitution_tag = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this SubscriptionSubstitutionTag.
+
+ :returns: This SubscriptionSubstitutionTag, ready for use in a request
+ body.
+ :rtype: string
+ """
+ return self.subscription_substitution_tag
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subscription_text.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subscription_text.py
new file mode 100644
index 00000000..ca20894a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subscription_text.py
@@ -0,0 +1,45 @@
+class SubscriptionText(object):
+ """The text of an SubscriptionTracking."""
+
+ def __init__(self, subscription_text=None):
+ """Create a SubscriptionText object
+
+ :param subscription_text: Text to be appended to the email, with the
+ subscription tracking link. You may control
+ where the link is by using the tag <% %>
+ :type subscription_text: string, optional
+ """
+ self._subscription_text = None
+
+ if subscription_text is not None:
+ self.subscription_text = subscription_text
+
+ @property
+ def subscription_text(self):
+ """Text to be appended to the email, with the subscription tracking link.
+ You may control where the link is by using the tag <% %>
+
+ :rtype: string
+ """
+ return self._subscription_text
+
+ @subscription_text.setter
+ def subscription_text(self, value):
+ """Text to be appended to the email, with the subscription tracking link.
+ You may control where the link is by using the tag <% %>
+
+ :param value: Text to be appended to the email, with the subscription
+ tracking link. You may control where the link is by using
+ the tag <% %>
+ :type value: string
+ """
+ self._subscription_text = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this SubscriptionText.
+
+ :returns: This SubscriptionText, ready for use in a request body.
+ :rtype: string
+ """
+ return self.subscription_text
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subscription_tracking.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subscription_tracking.py
new file mode 100644
index 00000000..8db65372
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/subscription_tracking.py
@@ -0,0 +1,142 @@
+class SubscriptionTracking(object):
+ """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.
+ """
+
+ def __init__(
+ self, enable=None, text=None, html=None, substitution_tag=None):
+ """Create a SubscriptionTracking to customize subscription management.
+
+ :param enable: Whether this setting is enabled.
+ :type enable: boolean, optional
+ :param text: Text to be appended to the email with the link as "<% %>".
+ :type text: SubscriptionText, optional
+ :param html: HTML to be appended to the email with the link as "<% %>".
+ :type html: SubscriptionHtml, optional
+ :param substitution_tag: Tag replaced with URL. Overrides text, html
+ params.
+ :type substitution_tag: SubscriptionSubstitutionTag, optional
+ """
+ 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):
+ """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
+
+ @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
+ def 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._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):
+ """
+ Get a JSON-ready representation of this SubscriptionTracking.
+
+ :returns: This SubscriptionTracking, ready for use in a request body.
+ :rtype: dict
+ """
+ subscription_tracking = {}
+ if self.enable is not None:
+ subscription_tracking["enable"] = self.enable
+
+ if self.text is not None:
+ subscription_tracking["text"] = self.text.get()
+
+ if self.html is not None:
+ subscription_tracking["html"] = self.html.get()
+
+ if self.substitution_tag is not None:
+ subscription_tracking["substitution_tag"] = \
+ self.substitution_tag.get()
+ return subscription_tracking
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/substitution.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/substitution.py
new file mode 100644
index 00000000..d515e885
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/substitution.py
@@ -0,0 +1,90 @@
+class Substitution(object):
+ """A string substitution to be applied to the text and HTML contents of
+ the body of your email, as well as in the Subject and Reply-To parameters.
+ """
+
+ def __init__(self, key=None, value=None, p=None):
+ """Create a Substitution with the given key and value.
+
+ :param key: Text to be replaced with "value" param
+ :type key: string, optional
+ :param value: Value to substitute into email
+ :type value: string, optional
+ :param name: p is the Personalization object or Personalization object
+ index
+ :type name: Personalization, integer, optional
+ """
+ self._key = None
+ self._value = None
+ self._personalization = None
+
+ if key is not None:
+ self.key = key
+ if value is not None:
+ self.value = value
+ if p is not None:
+ self.personalization = p
+
+ @property
+ def key(self):
+ """The substitution key.
+
+ :rtype key: string
+ """
+ return self._key
+
+ @key.setter
+ def key(self, value):
+ """The substitution key.
+
+ :param key: The substitution key.
+ :type key: string
+ """
+ self._key = value
+
+ @property
+ def value(self):
+ """The substitution value.
+
+ :rtype value: string
+ """
+ return str(self._value) if isinstance(self._value, int) else self._value
+
+ @value.setter
+ def value(self, value):
+ """The substitution value.
+
+ :param value: The substitution value.
+ :type value: string
+ """
+ self._value = value
+
+ @property
+ def personalization(self):
+ """The Personalization object or Personalization object index
+
+ :rtype: Personalization, integer
+ """
+ return self._personalization
+
+ @personalization.setter
+ def personalization(self, value):
+ """The Personalization object or Personalization object index
+
+ :param value: The Personalization object or Personalization object
+ index
+ :type value: Personalization, integer
+ """
+ self._personalization = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this Substitution.
+
+ :returns: This Substitution, ready for use in a request body.
+ :rtype: dict
+ """
+ substitution = {}
+ if self.key is not None and self.value is not None:
+ substitution[self.key] = self.value
+ return substitution
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/template_id.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/template_id.py
new file mode 100644
index 00000000..f18c5f58
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/template_id.py
@@ -0,0 +1,39 @@
+class TemplateId(object):
+ """The template ID of an Attachment object."""
+
+ def __init__(self, template_id=None):
+ """Create a TemplateId object
+
+ :param template_id: The template id for the message
+ :type template_id: string, optional
+ """
+ self._template_id = None
+
+ if template_id is not None:
+ self.template_id = template_id
+
+ @property
+ def template_id(self):
+ """The template id for the message
+
+ :rtype: string
+ """
+ return self._template_id
+
+ @template_id.setter
+ def template_id(self, value):
+ """The template id for the message
+
+ :param value: The template id for the message
+ :type value: string
+ """
+ self._template_id = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this TemplateId.
+
+ :returns: This TemplateId, ready for use in a request body.
+ :rtype: string
+ """
+ return self.template_id
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/to_email.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/to_email.py
new file mode 100644
index 00000000..e4f294f0
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/to_email.py
@@ -0,0 +1,5 @@
+from .email import Email
+
+
+class To(Email):
+ """A to email address with an optional name."""
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/tracking_settings.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/tracking_settings.py
new file mode 100644
index 00000000..d5f2e4fd
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/tracking_settings.py
@@ -0,0 +1,134 @@
+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):
+ """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.
+
+ :rtype: ClickTracking
+ """
+ return self._click_tracking
+
+ @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
+ def open_tracking(self):
+ """Allows you to track whether a recipient opened your email.
+
+ :rtype: OpenTracking
+ """
+ return self._open_tracking
+
+ @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
+ def subscription_tracking(self):
+ """Settings for the subscription management link.
+
+ :rtype: SubscriptionTracking
+ """
+ return self._subscription_tracking
+
+ @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
+ def ganalytics(self):
+ """Settings for Google Analytics.
+
+ :rtype: Ganalytics
+ """
+ return self._ganalytics
+
+ @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):
+ """
+ Get a JSON-ready representation of this TrackingSettings.
+
+ :returns: This TrackingSettings, ready for use in a request body.
+ :rtype: dict
+ """
+ tracking_settings = {}
+ if self.click_tracking is not None:
+ tracking_settings["click_tracking"] = self.click_tracking.get()
+ if self.open_tracking is not None:
+ tracking_settings["open_tracking"] = self.open_tracking.get()
+ if self.subscription_tracking is not None:
+ tracking_settings[
+ "subscription_tracking"] = self.subscription_tracking.get()
+ if self.ganalytics is not None:
+ tracking_settings["ganalytics"] = self.ganalytics.get()
+ return tracking_settings
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_campaign.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_campaign.py
new file mode 100644
index 00000000..5b200682
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_campaign.py
@@ -0,0 +1,40 @@
+class UtmCampaign(object):
+ """The utm campaign of an Ganalytics object."""
+
+ def __init__(self, utm_campaign=None):
+ """Create a UtmCampaign object
+
+ :param utm_campaign: The name of the campaign
+
+ :type utm_campaign: string, optional
+ """
+ self._utm_campaign = None
+
+ if utm_campaign is not None:
+ self.utm_campaign = utm_campaign
+
+ @property
+ def utm_campaign(self):
+ """The name of the campaign
+
+ :rtype: string
+ """
+ return self._utm_campaign
+
+ @utm_campaign.setter
+ def utm_campaign(self, value):
+ """The name of the campaign
+
+ :param value: The name of the campaign
+ :type value: string
+ """
+ self._utm_campaign = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this UtmCampaign.
+
+ :returns: This UtmCampaign, ready for use in a request body.
+ :rtype: string
+ """
+ return self.utm_campaign
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_content.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_content.py
new file mode 100644
index 00000000..e2a8ccff
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_content.py
@@ -0,0 +1,40 @@
+class UtmContent(object):
+ """The utm content of an Ganalytics object."""
+
+ def __init__(self, utm_content=None):
+ """Create a UtmContent object
+
+ :param utm_content: Used to differentiate your campaign from advertisements.
+
+ :type utm_content: string, optional
+ """
+ self._utm_content = None
+
+ if utm_content is not None:
+ self.utm_content = utm_content
+
+ @property
+ def utm_content(self):
+ """Used to differentiate your campaign from advertisements.
+
+ :rtype: string
+ """
+ return self._utm_content
+
+ @utm_content.setter
+ def utm_content(self, value):
+ """Used to differentiate your campaign from advertisements.
+
+ :param value: Used to differentiate your campaign from advertisements.
+ :type value: string
+ """
+ self._utm_content = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this UtmContent.
+
+ :returns: This UtmContent, ready for use in a request body.
+ :rtype: string
+ """
+ return self.utm_content
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_medium.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_medium.py
new file mode 100644
index 00000000..3687a0b2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_medium.py
@@ -0,0 +1,40 @@
+class UtmMedium(object):
+ """The utm medium of an Ganalytics object."""
+
+ def __init__(self, utm_medium=None):
+ """Create a UtmMedium object
+
+ :param utm_medium: Name of the marketing medium. (e.g. Email)
+
+ :type utm_medium: string, optional
+ """
+ self._utm_medium = None
+
+ if utm_medium is not None:
+ self.utm_medium = utm_medium
+
+ @property
+ def utm_medium(self):
+ """Name of the marketing medium. (e.g. Email)
+
+ :rtype: string
+ """
+ return self._utm_medium
+
+ @utm_medium.setter
+ def utm_medium(self, value):
+ """Name of the marketing medium. (e.g. Email)
+
+ :param value: Name of the marketing medium. (e.g. Email)
+ :type value: string
+ """
+ self._utm_medium = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this UtmMedium.
+
+ :returns: This UtmMedium, ready for use in a request body.
+ :rtype: string
+ """
+ return self.utm_medium
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_source.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_source.py
new file mode 100644
index 00000000..841ba0a8
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_source.py
@@ -0,0 +1,43 @@
+class UtmSource(object):
+ """The utm source of an Ganalytics object."""
+
+ def __init__(self, utm_source=None):
+ """Create a UtmSource object
+
+ :param utm_source: Name of the referrer source.
+ (e.g. Google, SomeDomain.com, or Marketing Email)
+ :type utm_source: string, optional
+ """
+ self._utm_source = None
+
+ if utm_source is not None:
+ self.utm_source = utm_source
+
+ @property
+ def utm_source(self):
+ """Name of the referrer source. (e.g. Google, SomeDomain.com, or
+ Marketing Email)
+
+ :rtype: string
+ """
+ return self._utm_source
+
+ @utm_source.setter
+ def utm_source(self, value):
+ """Name of the referrer source. (e.g. Google, SomeDomain.com, or
+ Marketing Email)
+
+ :param value: Name of the referrer source.
+ (e.g. Google, SomeDomain.com, or Marketing Email)
+ :type value: string
+ """
+ self._utm_source = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this UtmSource.
+
+ :returns: This UtmSource, ready for use in a request body.
+ :rtype: string
+ """
+ return self.utm_source
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_term.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_term.py
new file mode 100644
index 00000000..e8a9518a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/utm_term.py
@@ -0,0 +1,40 @@
+class UtmTerm(object):
+ """The utm term of an Ganalytics object."""
+
+ def __init__(self, utm_term=None):
+ """Create a UtmTerm object
+
+ :param utm_term: Used to identify any paid keywords.
+
+ :type utm_term: string, optional
+ """
+ self._utm_term = None
+
+ if utm_term is not None:
+ self.utm_term = utm_term
+
+ @property
+ def utm_term(self):
+ """Used to identify any paid keywords.
+
+ :rtype: string
+ """
+ return self._utm_term
+
+ @utm_term.setter
+ def utm_term(self, value):
+ """Used to identify any paid keywords.
+
+ :param value: Used to identify any paid keywords.
+ :type value: string
+ """
+ self._utm_term = value
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this UtmTerm.
+
+ :returns: This UtmTerm, ready for use in a request body.
+ :rtype: string
+ """
+ return self.utm_term
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/validators.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/validators.py
new file mode 100644
index 00000000..00e3276e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail/validators.py
@@ -0,0 +1,69 @@
+from .exceptions import ApiKeyIncludedException
+
+
+class ValidateApiKey(object):
+ """Validates content to ensure SendGrid API key is not present"""
+
+ regexes = None
+
+ def __init__(self, regex_strings=None, use_default=True):
+ """Create an API key validator
+
+ :param regex_strings: list of regex strings
+ :type regex_strings: list(str)
+ :param use_default: Whether or not to include default regex
+ :type use_default: bool
+ """
+
+ import re
+ self.regexes = set()
+
+ # Compile the regex strings into patterns, add them to our set
+ if regex_strings is not None:
+ for regex_string in regex_strings:
+ self.regexes.add(re.compile(regex_string))
+
+ if use_default:
+ default_regex_string = r'SG\.[0-9a-zA-Z]+\.[0-9a-zA-Z]+'
+ self.regexes.add(re.compile(default_regex_string))
+
+ def validate_message_dict(self, request_body):
+ """With the JSON dict that will be sent to SendGrid's API,
+ check the content for SendGrid API keys - throw exception if found.
+
+ :param request_body: The JSON dict that will be sent to SendGrid's
+ API.
+ :type request_body: JSON serializable structure
+ :raise ApiKeyIncludedException: If any content in request_body
+ matches regex
+ """
+
+ # Handle string in edge-case
+ if isinstance(request_body, str):
+ self.validate_message_text(request_body)
+
+ # Default param
+ elif isinstance(request_body, dict):
+
+ contents = request_body.get("content", list())
+
+ for content in contents:
+ if content is not None:
+ if (content.get("type") == "text/html" or
+ isinstance(content.get("value"), str)):
+ message_text = content.get("value", "")
+ self.validate_message_text(message_text)
+
+ def validate_message_text(self, message_string):
+ """With a message string, check to see if it contains a SendGrid API Key
+ If a key is found, throw an exception
+
+ :param message_string: message that will be sent
+ :type message_string: string
+ :raises ApiKeyIncludedException: If message_string matches a regex
+ string
+ """
+ if isinstance(message_string, str):
+ for regex in self.regexes:
+ if regex.match(message_string) is not None:
+ raise ApiKeyIncludedException()
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/stats/__init__.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/stats/__init__.py
new file mode 100644
index 00000000..9ee4dcdd
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/stats/__init__.py
@@ -0,0 +1 @@
+from .stats import * # noqa
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/helpers/stats/stats.py b/.venv/lib/python3.12/site-packages/sendgrid/helpers/stats/stats.py
new file mode 100644
index 00000000..b866093b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/helpers/stats/stats.py
@@ -0,0 +1,384 @@
+class Stats(object):
+ """
+ Object for building query params for a global email statistics request
+ """
+ def __init__(
+ self, start_date=None):
+ """Create a Stats object
+
+ :param start_date: Date of when stats should begin in YYYY-MM-DD format, defaults to None
+ :type start_date: string, optional
+ """
+ self._start_date = None
+ self._end_date = None
+ self._aggregated_by = None
+ self._sort_by_metric = None
+ self._sort_by_direction = None
+ self._limit = None
+ self._offset = None
+
+ # Minimum required for stats
+ if start_date:
+ self.start_date = start_date
+
+ def __str__(self):
+ """Get a JSON representation of this object.
+
+ :rtype: string
+ """
+ return str(self.get())
+
+ def get(self):
+ """
+ Get a JSON-ready representation of Stats
+
+ :returns: This GlobalStats, ready for use in a request body.
+ :rtype: response stats dict
+ """
+ stats = {}
+ if self.start_date is not None:
+ stats["start_date"] = self.start_date
+ if self.end_date is not None:
+ stats["end_date"] = self.end_date
+ if self.aggregated_by is not None:
+ stats["aggregated_by"] = self.aggregated_by
+ if self.sort_by_metric is not None:
+ stats["sort_by_metric"] = self.sort_by_metric
+ if self.sort_by_direction is not None:
+ stats["sort_by_direction"] = self.sort_by_direction
+ if self.limit is not None:
+ stats["limit"] = self.limit
+ if self.offset is not None:
+ stats["offset"] = self.offset
+ return stats
+
+ @property
+ def start_date(self):
+ """Date of when stats should begin in YYYY-MM-DD format
+
+ :rtype: string
+ """
+ return self._start_date
+
+ @start_date.setter
+ def start_date(self, value):
+ """Date of when stats should begin in YYYY-MM-DD format
+
+ :param value: Date representing when stats should begin
+ :type value: string
+ """
+ self._start_date = value
+
+ @property
+ def end_date(self):
+ """Date of when stats should end in YYYY-MM-DD format
+
+ :rtype: string
+ """
+ return self._end_date
+
+ @end_date.setter
+ def end_date(self, value):
+ """Date of when stats should end in YYYY-MM-DD format
+
+ :param value: Date representing when stats should end
+ :type value: string
+ """
+ self._end_date = value
+
+ @property
+ def aggregated_by(self):
+ """Chosen period (e.g. 'day', 'week', 'month') for how stats get grouped
+
+ :rtype: string
+ """
+ return self._aggregated_by
+
+ @aggregated_by.setter
+ def aggregated_by(self, value):
+ """Chosen period (e.g. 'day', 'week', 'month') for how stats get grouped
+
+ :param value: Period for how keys will get formatted
+ :type value: string
+ """
+ self._aggregated_by = value
+
+ @property
+ def sort_by_metric(self):
+ """Metric to sort stats by
+
+ :rtype: string
+ """
+ return self._sort_by_metric
+
+ @sort_by_metric.setter
+ def sort_by_metric(self, value):
+ """Metric to sort stats by
+
+ :param value: Chosen metric stats will by sorted by
+ :type value: string
+ """
+ self._sort_by_metric = value
+
+ @property
+ def sort_by_direction(self):
+ """Direction data will be sorted, either 'asc' or 'desc'
+
+ :rtype: string
+ """
+ return self._sort_by_direction
+
+ @sort_by_direction.setter
+ def sort_by_direction(self, value):
+ """Direction data will be sorted, either 'asc' or 'desc'
+
+ :param value: Direction of data, either 'asc' or 'desc'
+ :type value: string
+ """
+ self._sort_by_direction = value
+
+ @property
+ def limit(self):
+ """Max amount of results to be returned
+
+ :rtype: int
+ """
+ return self._limit
+
+ @limit.setter
+ def limit(self, value):
+ """Max amount of results to be returned
+
+ :param value: Max amount of results
+ :type value: int
+ """
+ self._limit = value
+
+ @property
+ def offset(self):
+ """Number of places a starting point of a data set will move
+
+ :rtype: int
+ """
+ return self._offset
+
+ @offset.setter
+ def offset(self, value):
+ """Number of places a starting point of a data set will move
+
+ :param value: Number of positions to move from starting point
+ :type value: int
+ """
+ self._offset = value
+
+
+class CategoryStats(Stats):
+ """
+ object for building query params for a category statistics request
+ """
+ def __init__(self, start_date=None, categories=None):
+ """Create a CategoryStats object
+
+ :param start_date: Date of when stats should begin in YYYY-MM-DD format, defaults to None
+ :type start_date: string, optional
+ :param categories: list of categories to get results of, defaults to None
+ :type categories: list(string), optional
+ """
+ self._categories = None
+ super(CategoryStats, self).__init__()
+
+ # Minimum required for category stats
+ if start_date and categories:
+ self.start_date = start_date
+ for cat_name in categories:
+ self.add_category(Category(cat_name))
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this CategoryStats.
+
+ :return: response category stats dict
+ """
+ stats = {}
+ if self.start_date is not None:
+ stats["start_date"] = self.start_date
+ if self.end_date is not None:
+ stats["end_date"] = self.end_date
+ if self.aggregated_by is not None:
+ stats["aggregated_by"] = self.aggregated_by
+ if self.sort_by_metric is not None:
+ stats["sort_by_metric"] = self.sort_by_metric
+ if self.sort_by_direction is not None:
+ stats["sort_by_direction"] = self.sort_by_direction
+ if self.limit is not None:
+ stats["limit"] = self.limit
+ if self.offset is not None:
+ stats["offset"] = self.offset
+ if self.categories is not None:
+ stats['categories'] = [category.get() for category in
+ self.categories]
+ return stats
+
+ @property
+ def categories(self):
+ """List of categories
+
+ :rtype: list(Category)
+ """
+ return self._categories
+
+ def add_category(self, category):
+ """Appends a category to this object's category list
+
+ :param category: Category to append to CategoryStats
+ :type category: Category
+ """
+ if self._categories is None:
+ self._categories = []
+ self._categories.append(category)
+
+
+class SubuserStats(Stats):
+ """
+ object of building query params for a subuser statistics request
+ """
+ def __init__(self, start_date=None, subusers=None):
+ """Create a SubuserStats object
+
+ :param start_date: Date of when stats should begin in YYYY-MM-DD format, defaults to None
+ :type start_date: string, optional
+ :param subusers: list of subusers to get results of, defaults to None
+ :type subusers: list(string), optional
+ """
+ self._subusers = None
+ super(SubuserStats, self).__init__()
+
+ # Minimum required for subusers stats
+ if start_date and subusers:
+ self.start_date = start_date
+ for subuser_name in subusers:
+ self.add_subuser(Subuser(subuser_name))
+
+ def get(self):
+ """
+ Get a JSON-ready representation of this SubuserStats.
+
+ :return: response subuser stats dict
+ """
+ stats = {}
+ if self.start_date is not None:
+ stats["start_date"] = self.start_date
+ if self.end_date is not None:
+ stats["end_date"] = self.end_date
+ if self.aggregated_by is not None:
+ stats["aggregated_by"] = self.aggregated_by
+ if self.sort_by_metric is not None:
+ stats["sort_by_metric"] = self.sort_by_metric
+ if self.sort_by_direction is not None:
+ stats["sort_by_direction"] = self.sort_by_direction
+ if self.limit is not None:
+ stats["limit"] = self.limit
+ if self.offset is not None:
+ stats["offset"] = self.offset
+ if self.subusers is not None:
+ stats['subusers'] = [subuser.get() for subuser in
+ self.subusers]
+ return stats
+
+ @property
+ def subusers(self):
+ """List of subusers
+
+ :rtype: list(Subuser)
+ """
+ return self._subusers
+
+ def add_subuser(self, subuser):
+ """Appends a subuser to this object's subuser list
+
+ :param subuser: Subuser to append to SubuserStats
+ :type subuser: Subuser
+ """
+ if self._subusers is None:
+ self._subusers = []
+ self._subusers.append(subuser)
+
+
+class Category(object):
+ """
+ Represents a searchable statistics category to be used in a CategoryStats object
+ """
+ def __init__(self, name=None):
+ """Create a Category object
+
+ :param name: name of category, defaults to None
+ :type name: string, optional
+ """
+ self._name = None
+ if name is not None:
+ self._name = name
+
+ @property
+ def name(self):
+ """Get name of category
+
+ :rtype: string
+ """
+ return self._name
+
+ @name.setter
+ def name(self, value):
+ """Set name of category
+
+ :param value: name of the statistical category
+ :type value: string
+ """
+ self._name = value
+
+ def get(self):
+ """
+ Get a string representation of Category.
+
+ :return: string of the category's name
+ """
+ return self.name
+
+
+class Subuser(object):
+ """
+ Represents a searchable subuser to be used in a SubuserStats object
+ """
+ def __init__(self, name=None):
+ """Create a Subuser object
+
+ :param name: name of subuser, defaults to None
+ :type name: string, optional
+ """
+ self._name = None
+ if name is not None:
+ self._name = name
+
+ @property
+ def name(self):
+ """Get name of the subuser
+
+ :rtype: string
+ """
+ return self._name
+
+ @name.setter
+ def name(self, value):
+ """Set name of the subuser
+
+ :param value: name of the subuser
+ :type value: string
+ """
+ self._name = value
+
+ def get(self):
+ """
+ Get a string representation of Subuser.
+
+ :return: string of the subuser's name
+ """
+ return self.name
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/sendgrid.py b/.venv/lib/python3.12/site-packages/sendgrid/sendgrid.py
new file mode 100644
index 00000000..912d8336
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/sendgrid.py
@@ -0,0 +1,58 @@
+"""
+This library allows you to quickly and easily use the Twilio SendGrid Web API v3 via Python.
+
+For more information on this library, see the README on GitHub.
+ http://github.com/sendgrid/sendgrid-python
+For more information on the Twilio SendGrid v3 API, see the v3 docs:
+ http://sendgrid.com/docs/API_Reference/api_v3.html
+For the user guide, code examples, and more, visit the main docs page:
+ http://sendgrid.com/docs/index.html
+
+This file provides the Twilio SendGrid API Client.
+"""
+
+import os
+
+from .base_interface import BaseInterface
+
+
+class SendGridAPIClient(BaseInterface):
+ """The Twilio SendGrid API Client.
+
+ Use this object to interact with the v3 API. For example:
+ mail_client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
+ ...
+ mail = Mail(from_email, subject, to_email, content)
+ response = mail_client.send(mail)
+
+ For examples and detailed use instructions, see
+ https://github.com/sendgrid/sendgrid-python
+ """
+
+ def __init__(
+ self,
+ api_key=None,
+ host='https://api.sendgrid.com',
+ impersonate_subuser=None):
+ """
+ Construct the Twilio SendGrid v3 API object.
+ Note that the underlying client is being set up during initialization,
+ therefore changing attributes in runtime will not affect HTTP client
+ behaviour.
+
+ :param api_key: Twilio SendGrid API key to use. If not provided, value
+ will be read from environment variable "SENDGRID_API_KEY"
+ :type api_key: string
+ :param impersonate_subuser: the subuser to impersonate. Will be passed
+ by "On-Behalf-Of" header by underlying
+ client. See
+ https://sendgrid.com/docs/User_Guide/Settings/subusers.html
+ for more details
+ :type impersonate_subuser: string
+ :param host: base URL for API calls
+ :type host: string
+ """
+ self.api_key = api_key or os.environ.get('SENDGRID_API_KEY')
+ auth = 'Bearer {}'.format(self.api_key)
+
+ super(SendGridAPIClient, self).__init__(auth, host, impersonate_subuser)
diff --git a/.venv/lib/python3.12/site-packages/sendgrid/twilio_email.py b/.venv/lib/python3.12/site-packages/sendgrid/twilio_email.py
new file mode 100644
index 00000000..78bd0181
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/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/.venv/lib/python3.12/site-packages/sendgrid/version.py b/.venv/lib/python3.12/site-packages/sendgrid/version.py
new file mode 100644
index 00000000..901082d4
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sendgrid/version.py
@@ -0,0 +1 @@
+__version__ = '6.11.0'