about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/sendgrid/helpers/mail
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/sendgrid/helpers/mail')
-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
64 files changed, 5358 insertions, 0 deletions
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()