about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/sendgrid
diff options
context:
space:
mode:
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'