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