SendGrid

SendGrid provides a cloud-based service that assists businesses with email delivery. It allows companies to track email opens, unsubscribes, bounces, and spam reports. Our SendGrid pack utilize these SendGrid use cases to help you send and manage your emails.

Messaging and Conferencing · SendGrid

Details

IDSendGrid
ProviderTwilio
CategoryMessaging and Conferencing
From Version6.0.0
Docker Imagedemisto/py3-tools:1.0.0.117220
Supported ModulesAgentix XSIAM

README

SendGrid provides a cloud-based service that assists businesses with email delivery. It allows companies to track email opens, unsubscribes, bounces, and spam reports. Our SendGrid pack utilize these SendGrid use cases to help you send and manage your emails.

Configure SendGrid in Cortex

Parameter Required
API Key True
From Email ID True
From Sender Name True

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

sg-send-email


Send an email.

Base Command

sg-send-email

Input

Argument Name Description Required
ToEmails A CSV list of to email recipients. Make sure To, Cc and Bcc emails are unique. Required
Subject Email subject. Required
HtmlBody html content of email. Optional
RawBody Raw content of email. Optional
ReplyTo Reply To email. Optional
Categories List of categories. For example: cake,pie,baking. Optional
BatchID An ID representing a batch of emails to be sent at the same time. Optional
SendAt An UTC time allowing you to specify when you want your email to be delivered. Delivery cannot be scheduled more than 72 hours in advance. ISO format(UTC timezone): 2021-04-23T12:07:44. Optional
Asm An object allowing you to specify how to handle unsubscribes. For example: {“group_id”: 12345,”groups_to_display”:[1,2,3]}. Optional
IPPoolName The IP Pool that you would like to send this email from. Optional
ClickTracking Allows you to track if a recipient clicked a link in your email. For ex: {“enable”: “True”,”enable_text”: True}. Optional
GAnalytics Allows you to enable tracking provided by Google Analytics. For ex: {“enable”: “True”,”utm_campaign”: “[NAME OF YOUR REFERRER SOURCE]”,”utm_content”: “[USE THIS SPACE TO DIFFERENTIATE YOUR EMAIL FROM ADS]”,”utm_medium”: “[NAME OF YOUR MARKETING MEDIUM e.g. email]”,”utm_name”: “[NAME OF YOUR CAMPAIGN]”,”utm_term”: “[IDENTIFY PAID KEYWORDS HERE]”,”utm_source”:”[Name of the referrer source]”}. Optional
OpenTracking Allows you to track if the email was opened by including a single pixel image in the body of the content. When the pixel is loaded, Twilio SendGrid can log that the email was opened. For ex: {“enable”: “True”,”substitution_tag”: “%opentrack”}. Optional
SubscriptionTracking 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. For ex: {“enable”: “True”,”html”: “If you would like to unsubscribe and stop receiving these emails <% clickhere %>.”,”substitution_tag”: “<%click here%>”,”text”: “If you would like to unsubscribe and stop receiving these emails <% click here %>.”}. Optional
BccSettings Bcc email settings. For ex: {“email”: “ben.doe@example.com”, “enable”: True }. Optional
BypassListManagement Allows you to bypass all unsubscribe groups and suppressions to ensure that the email is delivered to every single recipient. Possible values are: True, False. Optional
SandboxMode Sandbox Mode allows you to send a test email to ensure that your request body is valid and formatted correctly. Possible values are: True, False. Optional
Footer The default footer that you would like included on every email. For ex: {“enable”: “True”,”html”: “<p>Thanks</br>The SendGrid Team</p>”,”text”: “Thanks,/n The SendGrid Team”}. Optional
SpamCheck Spam Check allows you to test the content of your email for spam. For ex: {“enable”: “True”,”post_to_url”: “http://example.com/compliance”,”threshold”: 3}. Optional
Headers A collection of JSON key/value pairs allowing you to specify handling instructions for your email. You may not overwrite the following headers: x-sg-id, x-sg-eid, received, dkim-signature, Content-Type, Content-Transfer-Encoding, To, From, Subject, Reply-To, CC, BCC. For ex: {“key1”:”value1”,”key2”:”value2”,”key3”:”value3”}. Optional
TemplateID An email template ID. A template that contains a subject and content — either text or html — will override any subject and content values specified at the personalisations or message level. Optional
CustomArgs Values that are specific to this personalization that will be carried along with the email and its activity data. Substitutions will not be made on custom arguments, so any string that is entered into this parameter will be assumed to be the custom argument that you would like to be used. This field may not exceed 10,000 bytes. For Ex: {“marketing”: “true”,”activationAttempt”: “1”,”customerAccountNumber”: “1234”}. Optional
AttachIDs A CSV list of War Room entry IDs that contain files, and are used to attach files to the outgoing email. For example: attachIDs=15@8,19@8. Optional
AttachNames A CSV list of names of attachments to send. Should be the same number of elements as attachIDs. Optional
Bcc A CSV list of to email bcc recipients. Make sure To, Cc and Bcc emails are unique. Optional
Cc A CSV list of to email cc recipients. Make sure To, Cc and Bcc emails are unique. Optional

Context Output

There is no context output for this command.

sg-get-global-email-stats


Retrieves all of your global email statistics between a given date range.

Base Command

sg-get-global-email-stats

Input

Argument Name Description Required
limit The number of results to return. Optional
offset The point in the list to begin retrieving results. Optional
aggregated_by How to group the statistics. Must be either “day”, “week”, or “month”. Possible values are: day, week, month. Optional
start_date The starting date of the statistics to retrieve. Must follow format YYYY-MM-DD. Required
end_date The end date of the statistics to retrieve. Defaults to today. Must follow format YYYY-MM-DD. Optional
headers Table headers to use the human readable output (if none provided, will show all table headers). Available headers: blocks,bounce_drops,bounces,clicks,date,deferred,delivered,invalid_emails,opens,processed,requests,spam_report_drops,spam_reports,unique_clicks,unique_opens,unsubscribe_drops,unsubscribes. Optional

Context Output

Path Type Description
Sendgrid.GlobalEmailStats unknown List of email statistics day/week/month wise.

sg-get-category-stats


Retrieves all of your email statistics for each of your categories.

Base Command

sg-get-category-stats

Input

Argument Name Description Required
start_date The starting date of the statistics to retrieve. Must follow format YYYY-MM-DD. Required
category The individual category that you want to retrieve statistics for. Required
end_date The end date of the statistics to retrieve. Defaults to today. Must follow format YYYY-MM-DD. Optional
aggregated_by How to group the statistics. Must be either “day”, “week”, or “month”. Possible values are: day, week, month. Optional
limit The number of results to include. default: 500 maximum: 500. Optional
offset The number of results to skip. Optional
headers Table headers to use the human readable output (if none provided, will show all table headers). Available headers: blocks,bounce_drops,bounces,clicks,date,deferred,delivered,invalid_emails,opens,processed,requests,spam_report_drops,spam_reports,unique_clicks,unique_opens,unsubscribe_drops,unsubscribes. Optional

Context Output

Path Type Description
Sendgrid.CategoryStats unknown List of email category statistics day/week/month wise.

sg-get-all-categories-stats


Retrieves the total sum of each email statistic for every category over the given date range. By default it returns only 5 categories. Use limit= argument to define the number of categories to return.

Base Command

sg-get-all-categories-stats

Input

Argument Name Description Required
start_date The starting date of the statistics to retrieve. Must follow format YYYY-MM-DD. Required
end_date The end date of the statistics to retrieve. Defaults to today. Must follow format YYYY-MM-DD. Optional
limit The number of results to return. Optional
aggregated_by How to group the statistics. Must be either “day”, “week”, or “month”. Possible values are: day, week, month. Optional
sort_by_metric The metric that you want to sort by. Must be a single metric. default: delivered. Possible values are: blocks, bounce_drops, bounces, clicks, deferred, delivered, invalid_emails, opens, processed, requests, spam_report_drops, spam_reports, unique_clicks, unique_opens, unsubscribe_drops, unsubscribes. Optional
sort_by_direction The direction you want to sort. Allowed Values: desc, asc default: desc. Possible values are: asc, desc. Optional
offset The point in the list to begin retrieving results. Optional
headers Table headers to use the human readable output (if none provided, will show all table headers). Available headers: blocks,bounce_drops,bounces,clicks,date,deferred,delivered,invalid_emails,opens,processed,requests,spam_report_drops,spam_reports,unique_clicks,unique_opens,unsubscribe_drops,unsubscribes. Optional

Context Output

Path Type Description
Sendgrid.AllCategoriesStats unknown List of all email statistics day/week/month wise.

sg-list-categories


Retrieves a list of all of your categories.

Base Command

sg-list-categories

Input

Argument Name Description Required
category Allows you to perform a prefix search on this particular category. Optional
limit The number of categories to display per page. Default: 50. Optional
offset The point in the list that you would like to begin displaying results. Default: 0. Optional

Context Output

Path Type Description
Sendgrid.CategoriesList unknown List of categories.

sg-create-batch-id


Generates a new batch ID.

Base Command

sg-create-batch-id

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
Sendgrid.BatchId unknown Returns a batch id, which can be used manage scheduled sends.

sg-scheduled-status-change


Cancel or pause a scheduled send associated with a batch ID.

Base Command

sg-scheduled-status-change

Input

Argument Name Description Required
batch_id ID to manage multiple scheduled sends. Required
status The status of the send you would like to implement. This can be pause or cancel. Possible values are: pause, cancel. Required

Context Output

Path Type Description
Sendgrid.ScheduledSendStatus unknown The latest status of the scheduled send.

sg-retrieve-all-scheduled-sends


Retrieves all cancelled and paused scheduled send information.

Base Command

sg-retrieve-all-scheduled-sends

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output

Path Type Description
Sendgrid.ScheduledSends unknown List of all paused/cancelled scheduled sends.

sg-retrieve-scheduled-send


Retrieves the cancel/paused scheduled send information for a specific batch id.

Base Command

sg-retrieve-scheduled-send

Input

Argument Name Description Required
batch_id ID to manage multiple scheduled sends. Required

Context Output

Path Type Description
Sendgrid.ScheduledSend unknown List of all paused/cancelled scheduled sends associated with the given batch id.

sg-update-scheduled-send


Update the status of a scheduled send for the given batch id.

Base Command

sg-update-scheduled-send

Input

Argument Name Description Required
batch_id ID to manage multiple scheduled sends. Required
status The status of the send you would like to implement. This can be pause or cancel. Possible values are: pause, cancel. Required

Context Output

There is no context output for this command.

sg-delete-scheduled-send


Delete the cancellation/pause of a scheduled send.

Base Command

sg-delete-scheduled-send

Input

Argument Name Description Required
batch_id ID to manage multiple scheduled sends. Required

Context Output

There is no context output for this command.

sg-get-email-activity-list


Retrieves the email activity list associated with the messages matching your query. If no query provided, it returns a list of most recent emails you’ve sent. NOTE: This Email Activity API returns email list up to last 30 days.

Base Command

sg-get-email-activity-list

Input

Argument Name Description Required
limit The number of messages returned. This parameter must be greater than 0 and less than or equal to 1000. Default is 10. Required
query Use the query syntax to filter your email activity. For example: query to get email list for category - “Last Login”: query=(Contains(categories,"Last Login")) Document link for query samples: https://docs.sendgrid.com/for-developers/sending-email/getting-started-email-activity-api#query-reference. Optional
headers Table headers to use the human readable output (if none provided, will show all table headers). Available headers: clicks_count,from_email,last_event_time,msg_id,opens_count,status,subject,to_email. Optional

Context Output

Path Type Description
Sendgrid.EmailList unknown Email activity list associated with the messages matching your query.

sg-get-all-lists


Retrieves all of your recipient lists. If you don’t have any lists, an empty array will be returned.

Base Command

sg-get-all-lists

Input

Argument Name Description Required
page_size Maximum number of elements to return. returns 1000 max. default: 100. Optional
page_token Token corresponding to a specific page of results, as provided by metadata. default: None. Optional
headers Table headers to use the human readable output (if none provided, will show all table headers). Available headers: id, name, contact_count,_metadata. Optional

Context Output

Path Type Description
Sendgrid.Lists.Result unknown Array of your contact lists
Sendgrid.Lists.Metadata unknown Metadata of returned set of result

sg-get-list-by-id


Retrieves a single recipient list.

Base Command

sg-get-list-by-id

Input

Argument Name Description Required
list_id ID of contact list. Required
contact_sample Setting the optional parameter contact_sample=true returns the contact_sample in the response body. Up to fifty of the most recent contacts uploaded or attached to a list will be returned, sorted alphabetically, by email address. Default:False. Possible values are: True, False. Default is False. Optional

Context Output

Path Type Description
Sendgrid.List unknown Contact list details

sg-create-list


Creates a new contacts list

Base Command

sg-create-list

Input

Argument Name Description Required
list_name Name for your list. Required

Context Output

Path Type Description
Sendgrid.NewList unknown Newly created List details

sg-get-list-contact-count-by-id


Returns the number of contacts on a specific list

Base Command

sg-get-list-contact-count-by-id

Input

Argument Name Description Required
list_id ID of contact list. Required

Context Output

Path Type Description
Sendgrid.ListCount unknown List contact count details

sg-update-list-name


Updates the name of a list

Base Command

sg-update-list-name

Input

Argument Name Description Required
list_id ID of contact list. Required
updated_list_name New name for your list. Required

Context Output

Path Type Description
Sendgrid.updatedList unknown Updated list details

sg-delete-list


Deletes a specific list

Base Command

sg-delete-list

Input

Argument Name Description Required
list_id ID of contact list or job Id. Required
delete_contacts Flag indicates that all contacts on the list are also to be deleted. default: False. Possible values are: True, False. Default is False. Optional

Context Output

Path Type Description
Sendgrid.DeleteListJobId unknown Job id of the async job

Configuration parameters

  • apiKey — API Key (required)
  • fromEmail — From Email ID (required)
  • fromEmailName — From Sender Name (required)

Commands (18)

  • sg-create-batch-id

    Generates a new batch ID.

  • sg-create-list

    Creates a new contacts list.

  • sg-delete-list

    Deletes a specific list.

  • sg-delete-scheduled-send

    Delete the cancellation/pause of a scheduled send.

  • sg-get-all-categories-stats

    Retrieves the total sum of each email statistic for every category over the given date range. By default it returns only 5 categories. Use limit= argument to define the number of categories to return.

  • sg-get-all-lists

    Retrieves all of your recipient lists. If you don't have any lists, an empty array will be returned.

  • sg-get-category-stats

    Retrieves all of your email statistics for each of your categories.

  • sg-get-email-activity-list

    Retrieves the email activity list associated with the messages matching your query. If no query provided, it returns a list of most recent emails you've sent. NOTE: This Email Activity API returns email list up to last 30 days.

  • sg-get-global-email-stats

    Retrieves all of your global email statistics between a given date range.

  • sg-get-list-by-id

    Retrieves a single recipient list.

  • sg-get-list-contact-count-by-id

    Returns the number of contacts on a specific list.

  • sg-list-categories

    Retrieves a list of all of your categories.

  • sg-retrieve-all-scheduled-sends

    Retrieves all cancelled and paused scheduled send information.

  • sg-retrieve-scheduled-send

    Retrieves the cancel/paused scheduled send information for a specific batch id.

  • sg-scheduled-status-change

    Cancel or pause a scheduled send associated with a batch ID.

  • sg-send-email

    Send an email.

  • sg-update-list-name

    Updates the name of a list.

  • sg-update-scheduled-send

    Update the status of a scheduled send for the given batch id.

import json
import mimetypes
import time

import dateutil.parser
import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import *  # nopycln: import

# IMPORTS

"""Process Email attachments"""


def process_attachments(message, attachIDs="", attachNames=""):
    file_entries_for_attachments = []  # type: list
    attachments_names = []  # type: list

    if attachIDs:
        file_entries_for_attachments = attachIDs if isinstance(attachIDs, list) else attachIDs.split(",")
        if attachNames:
            attachments_names = attachNames if isinstance(attachNames, list) else attachNames.split(",")
        else:
            for att_id in file_entries_for_attachments:
                att_name = demisto.getFilePath(att_id)["name"]
                if isinstance(att_name, list):
                    att_name = att_name[0]
                attachments_names.append(att_name)
        if len(file_entries_for_attachments) != len(attachments_names):
            raise Exception("attachIDs and attachNames lists should be the same length")

    for i in range(len(file_entries_for_attachments)):
        entry_id = file_entries_for_attachments[i]
        attachment_name = attachments_names[i]
        try:
            res = demisto.getFilePath(entry_id)
        except Exception as ex:
            raise Exception(f"entry {entry_id} does not contain a file: {ex!s}")
        file_path = res["path"]
        with open(file_path, "rb") as f:
            f_data = f.read()
        encoded_data = base64.b64encode(f_data).decode()
        file_type = mimetypes.guess_type(attachment_name)[0]
        message.attachment = Attachment(  # type: ignore[name-defined]
            FileContent(encoded_data),  # type: ignore[name-defined]
            FileName(attachment_name),  # type: ignore[name-defined]
            FileType(file_type),  # type: ignore[name-defined]
            Disposition("attachment"),  # type: ignore[name-defined]
        )  # type: ignore[name-defined]
    return "ok"


def test_module(sg):
    """test function
    Returns:
        ok if successful
    """
    try:
        sg.client.categories.get()
    except Exception as e:
        raise DemistoException(f"Test failed. Please check your parameters. \n {e}")
    return "ok"


"""Retrieve an Email list based on the query"""


def get_email_activity_list(args: dict, sg):
    params = {}
    limit = args.get("limit")
    query = args.get("query")
    headers = args.get("headers")
    if limit:
        params["limit"] = int(limit)
    if query:
        params["query"] = query
    response = sg.client.messages.get(query_params=params)
    if response.status_code == 200:
        rBody = response.body
        body = json.loads(rBody.decode("utf-8"))
        ec = {"Sendgrid.EmailList": body["messages"]}
        if headers and isinstance(headers, str):
            headers = headers.split(",")
        md = tableToMarkdown("Email List: ", body["messages"], headers)
        return {
            "ContentsFormat": formats["json"],
            "Type": entryTypes["note"],
            "Contents": body,
            "HumanReadable": md,
            "EntryContext": ec,
        }
    else:
        return "Email list fetch is failed: " + str(response.body)


"""Create a Batch ID"""


def create_batch_id(sg):
    response = sg.client.mail.batch.post()
    if response.status_code == 201:
        rBody = response.body
        body = json.loads(rBody.decode("utf-8"))
        ec = {"Sendgrid.BatchId": body["batch_id"]}
        md = tableToMarkdown("Batch Id: ", body)
        return {
            "ContentsFormat": formats["json"],
            "Type": entryTypes["note"],
            "Contents": body,
            "HumanReadable": md,
            "EntryContext": ec,
        }
    else:
        return "Batch ID creation failed: " + str(response.body)


"""Cancel/Pause a scheduled send"""


def scheduled_send_status_change(args: dict, sg):
    batch_id = args.get("batch_id")
    status = args.get("status")
    data = {"batch_id": batch_id, "status": status}

    response = sg.client.user.scheduled_sends.post(request_body=data)
    if response.status_code == 201:
        rBody = response.body
        body = json.loads(rBody.decode("utf-8"))
        ec = {"Sendgrid.ScheduledSendStatus": body}
        md = tableToMarkdown("Scheduled status changed: ", body)
        return {
            "ContentsFormat": formats["json"],
            "Type": entryTypes["note"],
            "Contents": body,
            "HumanReadable": md,
            "EntryContext": ec,
        }
    else:
        return "scheduled send status change failed: " + str(response.body)


"""Retrieve all scheduled sends"""


def retrieve_all_scheduled_sends(args: dict, sg):
    response = sg.client.user.scheduled_sends.get()
    if response.status_code == 200:
        rBody = response.body
        body = json.loads(rBody.decode("utf-8"))
        if not body:
            return "No scheduled sends found"
        else:
            md = tableToMarkdown("List of Scheduled sends: ", body)
            ec = {"Sendgrid.ScheduledSends": body}
            return {
                "ContentsFormat": formats["json"],
                "Type": entryTypes["note"],
                "Contents": body,
                "HumanReadable": md,
                "EntryContext": ec,
            }
    else:
        return "Retrieval of scheduled sends list is failed: " + str(response.body)


"""Retrieve scheduled send"""


def retrieve_scheduled_send(args: dict, sg):
    batch_id = args.get("batch_id")

    response = sg.client.user.scheduled_sends._(batch_id).get()
    if response.status_code == 200:
        rBody = response.body
        body = json.loads(rBody.decode("utf-8"))
        if not body:
            return "No scheduled sends found for the given batch id"
        else:
            md = tableToMarkdown("List of Scheduled sends for a given Batch Id: ", body)
            ec = {"Sendgrid.ScheduledSend": body}
            return {
                "ContentsFormat": formats["json"],
                "Type": entryTypes["note"],
                "Contents": body,
                "HumanReadable": md,
                "EntryContext": ec,
            }
    else:
        return "Retrieval of scheduled sends for a given batch_id is failed " + str(response.body)


"""Update the status of a scheduled send for the given batch_id"""


def update_scheduled_send(args: dict, sg):
    batch_id = args.get("batch_id")
    status = args.get("status")
    data = {"status": status}

    response = sg.client.user.scheduled_sends._(batch_id).patch(request_body=data)
    if response.status_code == 204:
        return "Status of a scheduled send is updated"
    else:
        return "Update the status of a scheduled send for the given batch_id is failed " + str(response.body)


"""Delete the cancellation/pause of a scheduled send"""


def delete_scheduled_send(args: dict, sg):
    batch_id = args.get("batch_id")

    response = sg.client.user.scheduled_sends._(batch_id).delete()
    if response.status_code == 204:
        return "scheduled send is deleted"
    else:
        return "Delete of a scheduled send for the given batch_id is failed " + str(response.body)


"""Get Global Email Stats"""


def get_global_email_stats(args: dict, sg):
    params = {}
    limit = args.get("limit")
    if limit:
        params["limit"] = int(limit)
    offset = args.get("offset")
    if offset:
        params["offset"] = int(offset)
    aggregated_by = args.get("aggregated_by")
    if aggregated_by:
        params["aggregated_by"] = aggregated_by
    start_date = args.get("start_date")
    if start_date:
        params["start_date"] = start_date
    end_date = args.get("end_date")
    if end_date:
        params["end_date"] = end_date
    headers = args.get("headers")

    response = sg.client.stats.get(query_params=params)
    if response.status_code == 200:
        rBody = response.body
        res_stats = json.loads(rBody.decode("utf-8"))
        mail_stats: list = []
        for day in res_stats:
            res = {}
            res["date"] = day["date"]
            metrics = day["stats"][0]["metrics"]
            res["blocks"] = metrics["blocks"]
            res["bounce_drops"] = metrics["bounce_drops"]
            res["bounces"] = metrics["bounces"]
            res["clicks"] = metrics["clicks"]
            res["deferred"] = metrics["deferred"]
            res["delivered"] = metrics["delivered"]
            res["invalid_emails"] = metrics["invalid_emails"]
            res["opens"] = metrics["opens"]
            res["processed"] = metrics["processed"]
            res["requests"] = metrics["requests"]
            res["spam_report_drops"] = metrics["spam_report_drops"]
            res["spam_reports"] = metrics["spam_reports"]
            res["unique_clicks"] = metrics["unique_clicks"]
            res["unique_opens"] = metrics["unique_opens"]
            res["unsubscribe_drops"] = metrics["unsubscribe_drops"]
            res["unsubscribes"] = metrics["unsubscribes"]
            mail_stats.append(res)

        if headers and isinstance(headers, str):
            headers = headers.split(",")
        md = tableToMarkdown("Global Email Statistics", mail_stats, headers)
        ec = {"Sendgrid.GlobalEmailStats": mail_stats}
        return {
            "ContentsFormat": formats["json"],
            "Type": entryTypes["note"],
            "Contents": mail_stats,
            "ReadableContentsFormat": formats["markdown"],
            "HumanReadable": md,
            "EntryContext": ec,
        }
    else:
        return "Global email stat retrieval error: " + str(response.body)


"""Get Category Stats"""


def get_category_stats(args: dict, sg):
    params = {}
    limit = args.get("limit")
    if limit:
        params["limit"] = int(limit)
    offset = args.get("offset")
    if offset:
        params["offset"] = int(offset)
    aggregated_by = args.get("aggregated_by")
    if aggregated_by:
        params["aggregated_by"] = aggregated_by
    start_date = args.get("start_date")
    if start_date:
        params["start_date"] = start_date
    end_date = args.get("end_date")
    if end_date:
        params["end_date"] = end_date
    category = args.get("category")
    if category:
        params["categories"] = category
    headers = args.get("headers")

    response = sg.client.categories.stats.get(query_params=params)
    if response.status_code == 200:
        rBody = response.body
        res_stats = json.loads(rBody.decode("utf-8"))
        cat_stats: list = []
        for day in res_stats:
            res = {}
            res["date"] = day["date"]
            res["category"] = day["stats"][0]["name"]
            metrics = day["stats"][0]["metrics"]
            res["blocks"] = metrics["blocks"]
            res["bounce_drops"] = metrics["bounce_drops"]
            res["bounces"] = metrics["bounces"]
            res["clicks"] = metrics["clicks"]
            res["deferred"] = metrics["deferred"]
            res["delivered"] = metrics["delivered"]
            res["invalid_emails"] = metrics["invalid_emails"]
            res["opens"] = metrics["opens"]
            res["processed"] = metrics["processed"]
            res["requests"] = metrics["requests"]
            res["spam_report_drops"] = metrics["spam_report_drops"]
            res["spam_reports"] = metrics["spam_reports"]
            res["unique_clicks"] = metrics["unique_clicks"]
            res["unique_opens"] = metrics["unique_opens"]
            res["unsubscribe_drops"] = metrics["unsubscribe_drops"]
            res["unsubscribes"] = metrics["unsubscribes"]
            cat_stats.append(res)

        if headers and isinstance(headers, str):
            headers = headers.split(",")
        md = tableToMarkdown("Statistics for the Category: " + res["category"], cat_stats, headers)
        ec = {"Sendgrid.CategoryStats": cat_stats}
        return {
            "ContentsFormat": formats["json"],
            "Type": entryTypes["note"],
            "Contents": cat_stats,
            "ReadableContentsFormat": formats["markdown"],
            "HumanReadable": md,
            "EntryContext": ec,
        }
    else:
        return "Category stat retrieval error: " + str(response.body)


""" Sum of email statistics for all categories """


def get_all_categories_stats(args: dict, sg):
    params = {}
    limit = args.get("limit")
    if limit:
        params["limit"] = int(limit)
    offset = args.get("offset")
    if offset:
        params["offset"] = int(offset)
    aggregated_by = args.get("aggregated_by")
    if aggregated_by:
        params["aggregated_by"] = aggregated_by
    start_date = args.get("start_date")
    if start_date:
        params["start_date"] = start_date
    end_date = args.get("end_date")
    if end_date:
        params["end_date"] = end_date
    sort_by_direction = args.get("sort_by_direction")
    if sort_by_direction:
        params["sort_by_direction"] = sort_by_direction
    sort_by_metric = args.get("sort_by_metric")
    if sort_by_metric:
        params["sort_by_metric"] = sort_by_metric
    headers = args.get("headers")

    response = sg.client.categories.stats.sums.get(query_params=params)
    if response.status_code == 200:
        rBody = response.body
        body = json.loads(rBody.decode("utf-8"))
        if not body["stats"]:
            return "No Categories Statistics found for the given date range"
        else:
            res_stats = body["stats"]
            cat_stats: list = []
            for category in res_stats:
                res = {}
                res["category"] = category["name"]
                metrics = category["metrics"]
                res["blocks"] = metrics["blocks"]
                res["bounce_drops"] = metrics["bounce_drops"]
                res["bounces"] = metrics["bounces"]
                res["clicks"] = metrics["clicks"]
                res["deferred"] = metrics["deferred"]
                res["delivered"] = metrics["delivered"]
                res["invalid_emails"] = metrics["invalid_emails"]
                res["opens"] = metrics["opens"]
                res["processed"] = metrics["processed"]
                res["requests"] = metrics["requests"]
                res["spam_report_drops"] = metrics["spam_report_drops"]
                res["spam_reports"] = metrics["spam_reports"]
                res["unique_clicks"] = metrics["unique_clicks"]
                res["unique_opens"] = metrics["unique_opens"]
                res["unsubscribe_drops"] = metrics["unsubscribe_drops"]
                res["unsubscribes"] = metrics["unsubscribes"]
                cat_stats.append(res)

            if headers and isinstance(headers, str):
                headers = headers.split(",")
            md = tableToMarkdown("Sum of All Categories Statistics from " + body["date"], cat_stats, headers)
            ec = {"Sendgrid.AllCategoriesStats": body}
            return {
                "ContentsFormat": formats["json"],
                "Type": entryTypes["note"],
                "Contents": body,
                "ReadableContentsFormat": formats["markdown"],
                "HumanReadable": md,
                "EntryContext": ec,
            }
    else:
        return "Categories stat retrieval error: " + str(response.body)


""" List of categories """


def get_categories_list(args: dict, sg):
    params = {}
    limit = args.get("limit")
    if limit:
        params["limit"] = int(limit)
    offset = args.get("offset")
    if offset:
        params["offset"] = int(offset)
    category = args.get("category")
    if category:
        params["category"] = category

    response = sg.client.categories.get(query_params=params)
    if response.status_code == 200:
        rBody = response.body
        body = json.loads(rBody.decode("utf-8"))
        if not body:
            return "No Categories found"
        else:
            md = tableToMarkdown("List of Categories", body, ["category"])
            ec = {"Sendgrid.CategoriesList": body}
            return {
                "ContentsFormat": formats["json"],
                "Type": entryTypes["note"],
                "Contents": body,
                "ReadableContentsFormat": formats["markdown"],
                "HumanReadable": md,
                "EntryContext": ec,
            }
    else:
        return "Categories list retrieval error: " + str(response.body)


def send_mail(args: dict, sg_from_email: str, sg_sender_name: str, sg):
    message = Mail()  # type: ignore[name-defined]

    attach_ids = args.get("AttachIDs")
    attach_names = args.get("AttachNames") or ""

    if attach_ids:
        process_attachments(message, attach_ids, attach_names)

    categories = args.get("Categories")
    if categories:
        categories = categories.split(",")
        for category in categories:
            message.category = Category(category)  # type: ignore[name-defined]

    batch_id = args.get("BatchID")
    if batch_id:
        message.batch_id = BatchId(batch_id)  # type: ignore[name-defined]

    send_at = args.get("SendAt")
    if send_at:
        t = dateutil.parser.parse(send_at)
        send_time = time.mktime(t.timetuple())
        message.send_at = SendAt(int(send_time))  # type: ignore[name-defined]

    asm = args.get("Asm")
    if asm:
        asm = asm if type(asm) is dict else json.loads(asm)
        message.asm = Asm(GroupId(asm["group_id"]), GroupsToDisplay(asm["groups_to_display"]))  # type: ignore[name-defined]

    custom_args = args.get("CustomArgs")
    if custom_args:
        custom_args = custom_args if type(custom_args) is dict else json.loads(custom_args)
        for key in custom_args:
            message.custom_arg = CustomArg(key, custom_args[key])  # type: ignore[name-defined]

    ip_pool_name = args.get("IPPoolName")
    if ip_pool_name:
        message.ip_pool_name = IpPoolName(ip_pool_name)  # type: ignore[name-defined]

    # Mail Tracking settings
    tracking_settings = TrackingSettings()  # type: ignore[name-defined]
    click_tracking = args.get("ClickTracking")
    if click_tracking:
        click_tracking = click_tracking if type(click_tracking) is dict else json.loads(click_tracking)
        is_enable = click_tracking["enable"] != "False"
        tracking_settings.click_tracking = ClickTracking(  # type: ignore[name-defined]
            is_enable,  # type: ignore[name-defined]
            click_tracking["enable_text"],
        )

    open_tracking = args.get("OpenTracking")
    if open_tracking:
        open_tracking = open_tracking if type(open_tracking) is dict else json.loads(open_tracking)
        is_enable = open_tracking["enable"] != "False"
        tracking_settings.open_tracking = OpenTracking(  # type: ignore[name-defined]
            is_enable,
            OpenTrackingSubstitutionTag(open_tracking["substitution_tag"]),  # type: ignore[name-defined]
        )  # type: ignore[name-defined]

    sub_tracking = args.get("SubscriptionTracking")
    if sub_tracking:
        sub_tracking = sub_tracking if type(sub_tracking) is dict else json.loads(sub_tracking)
        is_enable = sub_tracking["enable"] != "False"
        tracking_settings.subscription_tracking = SubscriptionTracking(  # type: ignore[name-defined]
            is_enable,
            SubscriptionText(sub_tracking["text"]),  # type: ignore[name-defined]
            SubscriptionHtml(sub_tracking["html"]),  # type: ignore[name-defined]
            SubscriptionSubstitutionTag(sub_tracking["substitution_tag"]),  # type: ignore[name-defined]
        )  # type: ignore[name-defined]

    ganalytics = args.get("GAnalytics")
    if ganalytics:
        ganalytics = ganalytics if type(ganalytics) is dict else json.loads(ganalytics)
        is_enable = ganalytics["enable"] != "False"
        tracking_settings.ganalytics = Ganalytics(  # type: ignore[name-defined]
            is_enable,
            UtmSource(ganalytics["utm_source"]),  # type: ignore[name-defined]
            UtmMedium(ganalytics["utm_medium"]),  # type: ignore[name-defined]
            UtmTerm(ganalytics["utm_term"]),  # type: ignore[name-defined]
            UtmContent(ganalytics["utm_content"]),  # type: ignore[name-defined]
            UtmCampaign(ganalytics["utm_campaign"]),  # type: ignore[name-defined]
        )

    message.tracking_settings = tracking_settings

    # Mail Settings
    mail_settings = MailSettings()  # type: ignore[name-defined]
    bcc_mail_set = args.get("BccSettings")
    if bcc_mail_set:
        bcc_mail_set = bcc_mail_set if type(bcc_mail_set) is dict else json.loads(bcc_mail_set)
        is_enable = bcc_mail_set["enable"] != "False"
        mail_settings.bcc_settings = BccSettings(  # type: ignore[name-defined]
            is_enable,
            BccSettingsEmail(bcc_mail_set["email"]),  # type: ignore[name-defined]
        )

    footer = args.get("Footer")
    if footer:
        footer = footer if type(footer) is dict else json.loads(footer)
        is_enable = footer["enable"] != "False"
        mail_settings.footer_settings = FooterSettings(  # type: ignore[name-defined]
            is_enable,
            FooterText(footer["text"]),  # type: ignore[name-defined]
            FooterHtml(footer["html"]),  # type: ignore[name-defined]
        )

    spam_check = args.get("SpamCheck")
    if spam_check:
        spam_check = spam_check if type(spam_check) is dict else json.loads(spam_check)
        is_enable = spam_check["enable"] != "False"
        mail_settings.spam_check = SpamCheck(  # type: ignore[name-defined]
            is_enable,
            SpamThreshold(spam_check["threshold"]),  # type: ignore[name-defined]
            SpamUrl(spam_check["post_to_url"]),  # type: ignore[name-defined]
        )

    sandbox_mode = args.get("SandboxMode")
    if sandbox_mode:
        sandbox_mode = sandbox_mode != "False"
        mail_settings.sandbox_mode = SandBoxMode(sandbox_mode)  # type: ignore[name-defined]

    bypass_list_management = args.get("BypassListManagement")
    if bypass_list_management:
        bypass_list_management = bypass_list_management != "False"
        mail_settings.bypass_list_management = BypassListManagement(bypass_list_management)  # type: ignore[name-defined]

    message.mail_settings = mail_settings

    headers = args.get("Headers")
    if headers:
        headers = headers if type(headers) is dict else json.loads(headers)
        for key in headers:
            message.header = Header(key, headers[key])  # type: ignore[name-defined]

    template_id = args.get("TemplateID")
    if template_id:
        message.template_id = TemplateId(template_id)  # type: ignore[name-defined]

    subject = args.get("Subject")
    message.subject = Subject(subject)  # type: ignore[name-defined]

    email_body = args.get("HtmlBody")
    if email_body:
        message.content = Content(MimeType.html, email_body)  # type: ignore[name-defined]

    raw_body = args.get("RawBody")
    if raw_body:
        message.content = Content(MimeType.text, raw_body)  # type: ignore[name-defined]

    reply_to_email = args.get("ReplyTo")
    if reply_to_email:
        message.reply_to = ReplyTo(reply_to_email, None)  # type: ignore[name-defined]

    message.from_email = From(sg_from_email, sg_sender_name)  # type: ignore[name-defined]

    to_emails = args.get("ToEmails")
    to_emails = to_emails if isinstance(to_emails, list) else to_emails.split(",")  # type: ignore[union-attr]
    for email in to_emails:
        message.to = To(email, None, p=0)  # type: ignore[name-defined]

    cc_emails = args.get("Cc")
    if cc_emails:
        cc_emails = cc_emails if isinstance(cc_emails, list) else cc_emails.split(",")
        for email in cc_emails:
            message.cc = Cc(email, None, p=0)  # type: ignore[name-defined]

    bcc_emails = args.get("Bcc")
    if bcc_emails:
        bcc_emails = bcc_emails if isinstance(bcc_emails, list) else bcc_emails.split(",")
        for email in bcc_emails:
            message.bcc = Bcc(email, None, p=0)  # type: ignore[name-defined]

    response = sg.send(message)
    if response.status_code == 202:
        return "Email Sent successfully"
    else:
        return "Failed to send email " + response.status_code


def get_all_lists(args: dict, sg):
    params = {}
    pageSize = args.get("page_size")
    if pageSize:
        params["page_size"] = int(pageSize)
    pageToken = args.get("page_token")
    if pageToken:
        params["page_token"] = pageToken
    headers = args.get("headers")

    response = sg.client.marketing.lists.get(query_params=params)
    if response.status_code == 200:
        rBody = response.body
        body = json.loads(rBody.decode("utf-8"))
        ec = {"Sendgrid.Lists.Result": body["result"], "Sendgrid.Lists.Metadata": body["_metadata"]}
        if headers and isinstance(headers, str):
            headers = headers.split(",")
        md = tableToMarkdown("Lists information was fetched successfully: ", body["result"], headers)
        return {
            "ContentsFormat": formats["json"],
            "Type": entryTypes["note"],
            "Contents": body,
            "HumanReadable": md,
            "EntryContext": ec,
        }
    else:
        return "Failed to fetch lists information: " + str(response.body)


def get_list_by_id(args: dict, sg):
    listID = args.get("list_id")
    params = {}
    contactSample = args.get("contact_sample")
    if contactSample:
        params["contact_sample"] = contactSample != "False"

    response = sg.client.marketing.lists._(listID).get(query_params=params)
    if response.status_code == 200:
        rBody = response.body
        body = json.loads(rBody.decode("utf-8"))
        ec = {"Sendgrid.List": body}
        md = tableToMarkdown("List details ", body)
        return {
            "ContentsFormat": formats["json"],
            "Type": entryTypes["note"],
            "Contents": body,
            "HumanReadable": md,
            "EntryContext": ec,
        }
    else:
        return "Failed to retrieve list information: " + str(response.body)


def create_list(args: dict, sg):
    listName = args.get("list_name")
    data = {"name": listName}

    response = sg.client.marketing.lists.post(request_body=data)
    if response.status_code == 201:
        rBody = response.body
        body = json.loads(rBody.decode("utf-8"))
        ec = {"Sendgrid.NewList": body}
        md = tableToMarkdown("New List has been successfully created ", body)
        return {
            "ContentsFormat": formats["json"],
            "Type": entryTypes["note"],
            "Contents": body,
            "HumanReadable": md,
            "EntryContext": ec,
        }
    else:
        return "Failed to create new list: " + str(response.body)


def get_list_contact_count_by_id(args: dict, sg):
    listID = args.get("list_id")
    response = sg.client.marketing.lists._(listID).contacts.count.get()
    if response.status_code == 200:
        rBody = response.body
        body = json.loads(rBody.decode("utf-8"))
        ec = {"Sendgrid.ListCount": body}
        md = tableToMarkdown("List contact count details ", body)
        return {
            "ContentsFormat": formats["json"],
            "Type": entryTypes["note"],
            "Contents": body,
            "HumanReadable": md,
            "EntryContext": ec,
        }
    else:
        return "Failed to retrieve contact list count information: " + str(response.body)


def update_list_name(args: dict, sg):
    listID = args.get("list_id")
    listName = args.get("updated_list_name")
    data = {"name": listName}

    response = sg.client.marketing.lists._(listID).patch(request_body=data)
    if response.status_code == 200:
        rBody = response.body
        body = json.loads(rBody.decode("utf-8"))
        ec = {"Sendgrid.updatedList": body}
        md = tableToMarkdown("List Name has been updated successfully ", body)
        return {
            "ContentsFormat": formats["json"],
            "Type": entryTypes["note"],
            "Contents": body,
            "HumanReadable": md,
            "EntryContext": ec,
        }
    else:
        return "Failed to update list name: " + str(response.body)


def delete_list(args: dict, sg):
    listID = args.get("list_id")
    params = {}
    deleteContacts = args.get("delete_contacts")
    if deleteContacts:
        params["delete_contacts"] = deleteContacts != "False"

    response = sg.client.marketing.lists._(listID).delete(query_params=params)
    if response.status_code == 200:
        rBody = response.body
        body = json.loads(rBody.decode("utf-8"))
        ec = {"Sendgrid.DeleteListJobId": body["job_id"]}
        md = tableToMarkdown(
            "The delete has been accepted and is processing. \
                You can check the status using the Job ID: ",
            body["job_id"],
        )
        return {
            "ContentsFormat": formats["json"],
            "Type": entryTypes["note"],
            "Contents": body,
            "HumanReadable": md,
            "EntryContext": ec,
        }
    elif response.status_code == 204:
        return "Deletion completed successfully"
    else:
        return "Failed to delete list: " + str(response.body)


def main():
    """
    PARSE AND VALIDATE INTEGRATION PARAMS
    """

    sg_api_key = demisto.params().get("apiKey")
    sg_from_email = demisto.params().get("fromEmail")
    sg_sender_name = demisto.params().get("fromEmailName")

    try:
        # Passing the API key
        sg = SendGridAPIClient(api_key=sg_api_key)

        command = demisto.command()
        args = demisto.args()
        result = ""

        if command == "test-module":
            result = test_module(sg)

        elif demisto.command() == "sg-send-email":
            result = send_mail(args, sg_from_email, sg_sender_name, sg)

        elif demisto.command() == "sg-get-global-email-stats":
            result = get_global_email_stats(args, sg)

        elif demisto.command() == "sg-get-category-stats":
            result = get_category_stats(args, sg)

        elif demisto.command() == "sg-get-all-categories-stats":
            result = get_all_categories_stats(args, sg)

        elif demisto.command() == "sg-list-categories":
            result = get_categories_list(args, sg)

        elif demisto.command() == "sg-create-batch-id":
            result = create_batch_id(sg)

        elif demisto.command() == "sg-scheduled-status-change":
            result = scheduled_send_status_change(args, sg)

        elif demisto.command() == "sg-retrieve-all-scheduled-sends":
            result = retrieve_all_scheduled_sends(args, sg)

        elif demisto.command() == "sg-retrieve-scheduled-send":
            result = retrieve_scheduled_send(args, sg)

        elif demisto.command() == "sg-update-scheduled-send":
            result = update_scheduled_send(args, sg)

        elif demisto.command() == "sg-delete-scheduled-send":
            result = delete_scheduled_send(args, sg)

        elif demisto.command() == "sg-get-email-activity-list":
            result = get_email_activity_list(args, sg)

        elif demisto.command() == "sg-get-all-lists":
            result = get_all_lists(args, sg)

        elif demisto.command() == "sg-get-list-by-id":
            result = get_list_by_id(args, sg)

        elif demisto.command() == "sg-create-list":
            result = create_list(args, sg)

        elif demisto.command() == "sg-get-list-contact-count-by-id":
            result = get_list_contact_count_by_id(args, sg)

        elif demisto.command() == "sg-update-list-name":
            result = update_list_name(args, sg)

        elif demisto.command() == "sg-delete-list":
            result = delete_list(args, sg)

        demisto.results(result)

    # Log exceptions
    except Exception as e:
        if repr(e) == "KeyError('email')":
            return_error(f"Failed to execute {demisto.command()} command. Please provide a valid email.")
        else:
            return_error(f"Failed to execute {demisto.command()} command. Error: {e!s}")


if __name__ in ("__main__", "builtins"):
    main()