Reporting

Splunk custom HTML email

sboogaar
Path Finder

Im making a custom alert action with a python script where I want to send an html email.
I looked into the default sendemail.py file of splunk but I do not understand it. It uses: splunk.Intersplunk to load the email settings needed for sending mail with the smtplib library. But i can not find any documentation about intersplunk and the code from sendemail.py itself is not clear enough to understand without comments.
Is there any example about how to send an html email in a custom python script that uses the splunk settings for mailing.
Even any documentation about how to send emails from a custom python scripts would help me.

0 Karma
1 Solution

sboogaar
Path Finder

I ended up with the following script.

# This script gets the results from a saved search and sends an email based on the content.
# The searchquery that triggers this alert should contain the following fields
#
#  Severity             The severity of the kpi, the colors in the table are based on the severity value
#  Service              The name of the service
#  KPI                  The name of the kpi
#  Description          A description that describes the error
#  Mail_to              A list of users that should receive the mail separated with ,
#  New error            Values 'Yes' if the error is new
#  Machine ID           The name of the machine that triggers this script

import sys
import gzip
import logging
import csv
import json
import re
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import splunk.entity as entity
import splunk.secure_smtplib as secure_smtplib
from splunk.rest import simpleRequest

logging.basicConfig(filename='development.log', level=logging.DEBUG)

# CSS stylings
style_table = "font-family: verdana,arial,sans-serif; " \
              "font-size:11px; " \
              "color:#333333; " \
              "border-width: 1px; " \
              "border-color: #3A3A3A; " \
              "border-collapse: collapse; "

style_thead = "border-width: 1px; " \
              "padding: 8px; " \
              "text-align:left; " \
              "border-style: solid; " \
              "border-color: #3A3A3A; " \
              "background-color: #B3B3B3; "

style_thead_tr = "padding: 8px; "

style_tr_even = "border-width: 1px; " \
                "padding: 8px; " \
                "border-style: solid; " \
                "border-color: #3A3A3A; " \
                "background-color: #cccccc; "
style_td = "padding:8px; "


class Alert(object):
    def __init__(self, severity, service, kpi, description, is_new):
        self.severity = severity
        self.service = service
        self.kpi = kpi
        self.description = description
        self.is_new = is_new

def create_table_data(data, styling=""):
    if styling != "":
        styling = 'style="%s"' % styling
    return "<td %s>\n%s\n</td>\n" % (styling, data)


def status_to_color(status):
    status = status.lower()
    status_colors = {}
    status_colors["critical"] = "#efbebe"
    status_colors["high"] = "#facdae"

    if status in status_colors:
        return status_colors[status]
    return "#dadada"


def create_table_row(data, styling=""):
    styling = 'style="%s"' % styling
    return "<tr %s>\n%s\n</tr>\n" % (styling, data)


def create_table_head(headings, styling=""):
    result = ""
    for heading in headings:
        result += ('<th style="%s">\n%s\n</th>\n' % (styling, heading))
    return create_table_row(result, style_thead)


def create_alerts_table(alerts):
    table = '<table cellspacing="0" cellpadding="0" style="%s">' % style_table
    table += create_table_head(["Severity", "Service", "Kpi", "Description", "New error"], style_thead_tr)
    x = 0
    for alert in alerts:
        event_row_styling = "font-family: Arial; "
        x += 1
        row_data = create_table_data(alert.severity, style_td)
        row_data += create_table_data(alert.service, style_td)
        row_data += create_table_data(alert.kpi, style_td)
        row_data += create_table_data(alert.description, style_td)
        row_data += create_table_data(alert.is_new, style_td)
        if alert.is_new:
            event_row_styling += "background-color: #f48989;"
        else:
            event_row_styling += "background-color: %s" % (status_to_color(alert.severity))

        table += create_table_row(row_data, event_row_styling)
    table += "</table>"

    return table


def create_html_template(content):
    mail_template_filled = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ' \
                           '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html ' \
                           'xmlns="http://www.w3.org/1999/xhtml">\n <head>\n  <meta http-equiv="Content-Type" ' \
                           'content="text/html; charset=UTF-8" />\n  <title>\n HTML email\n</title>\n  <meta ' \
                           'name="viewport" content="width=device-width, ' \
                           'initial-scale=1.0"/>\n</head>\n<body>\n%s\n</body>\n</html>\n ' % content
    return mail_template_filled


def send_mail(mail_to, email_message, mail_from="default@somedomain.com", mailserver="localhost", subject="Message from python script"):
    msg = MIMEMultipart('alternative')
    part1 = MIMEText(email_message, 'html')
    msg.attach(part1)
    msg['To'] = mail_to
    msg['Subject'] = subject
    mail_to_list = mail_to.split(",")
    try:
        smtp = secure_smtplib.SecureSMTP(host=mailserver)
        logging.debug(mail_to_list)
        logging.debug(mail_to)
        smtp.sendmail(mail_from, mail_to_list, msg.as_string())
        smtp.quit()
    except Exception as e:
        logging.error(str(e))


if __name__ == "__main__":
    try:
        alerts = []

        data = sys.stdin.read()
        parsed_result = json.loads(data)
        # Get the address of the gzip file.
        csv_gzip_location = parsed_result["results_file"]
        owner = parsed_result["owner"]
        app = parsed_result["app"]
        session_key = parsed_result["session_key"]

        # Read the results of the query that triggered the alert
        csv_reader = csv.DictReader(gzip.open(csv_gzip_location, 'rb'))
        to_mail_list = ""
        line_counter = 0
        new_events = 0
        total_events = 0
        machine_id = ""
        for line in csv_reader:
            total_events += 1
            if line["New error"]:
                new_events += 1
            alerts.append(Alert(severity=line["Severity"], service=line["Service"], kpi=line["KPI"],
                                description=line["Description"], is_new=line["New error"]))

            if not machine_id:
                if "Machine ID" in line:
                    machine_id = line["Machine ID"]
            if not to_mail_list:
                if 'Mail_to' in line:
                    to_mail_list = line["Mail_to"]

        # Write the HTML formatted email content
        message = "<h1>Notable events</h1>\n"
        message += "<p>New events: <b>%s</b></p>\n" % new_events
        message += "<p>Total events: <b>%s</b></p>\n" % total_events
        message += "<p>Machine ID: <b>%s</b></p>\n" % machine_id
        events_table = create_alerts_table(alerts)
        message += events_table
        message = create_html_template(message)

        # Get the mailserver
        uri = entity.buildEndpoint(
            [
                'saved',
                'searches',
                '_new'
            ],
            namespace=app,
            owner=owner
        )
        responseHeaders, responseBody = simpleRequest(uri, method='GET', getargs={'output_mode': 'json'},
                                                      sessionKey=session_key)
        savedSearch = json.loads(responseBody)
        ssContent = savedSearch['entry'][0]['content']
        mail_server = ssContent.get('action.email.mailserver', 'localhost')
        mail_from = ssContent.get('action.email.from', 'localhost')
        mail_subject = (" %s | New notable events: %s, Total events: %s."% (machine_id, new_events, total_events))
        try:
            f = open('email_content.html','w')
            f.write(message)
            send_mail(mail_to=to_mail_list, mail_from=mail_from, email_message=message, mailserver=mail_server, subject=mail_subject)
        except:
            logging.exception('Failed sending email')
    except Exception as e:
        logging.exception("Failed while getting the information for the email")

Note that this only uses unsecured smtplib email, if you want to use ssl/password you should update the following part to get the mail information:

     ssContent = savedSearch['entry'][0]['content']
     mail_server = ssContent.get('action.email.mailserver', 'localhost')
     mail_from = ssContent.get('action.email.from', 'localhost')

View solution in original post

gjanders
SplunkTrust
SplunkTrust

sendresults could possibly be used here and this can send HTML-style email content, you just need to change the body of the email. I believe there will be other solutions here as well such as the one you have posted, thanks for sharing!

0 Karma

sboogaar
Path Finder

I ended up with the following script.

# This script gets the results from a saved search and sends an email based on the content.
# The searchquery that triggers this alert should contain the following fields
#
#  Severity             The severity of the kpi, the colors in the table are based on the severity value
#  Service              The name of the service
#  KPI                  The name of the kpi
#  Description          A description that describes the error
#  Mail_to              A list of users that should receive the mail separated with ,
#  New error            Values 'Yes' if the error is new
#  Machine ID           The name of the machine that triggers this script

import sys
import gzip
import logging
import csv
import json
import re
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import splunk.entity as entity
import splunk.secure_smtplib as secure_smtplib
from splunk.rest import simpleRequest

logging.basicConfig(filename='development.log', level=logging.DEBUG)

# CSS stylings
style_table = "font-family: verdana,arial,sans-serif; " \
              "font-size:11px; " \
              "color:#333333; " \
              "border-width: 1px; " \
              "border-color: #3A3A3A; " \
              "border-collapse: collapse; "

style_thead = "border-width: 1px; " \
              "padding: 8px; " \
              "text-align:left; " \
              "border-style: solid; " \
              "border-color: #3A3A3A; " \
              "background-color: #B3B3B3; "

style_thead_tr = "padding: 8px; "

style_tr_even = "border-width: 1px; " \
                "padding: 8px; " \
                "border-style: solid; " \
                "border-color: #3A3A3A; " \
                "background-color: #cccccc; "
style_td = "padding:8px; "


class Alert(object):
    def __init__(self, severity, service, kpi, description, is_new):
        self.severity = severity
        self.service = service
        self.kpi = kpi
        self.description = description
        self.is_new = is_new

def create_table_data(data, styling=""):
    if styling != "":
        styling = 'style="%s"' % styling
    return "<td %s>\n%s\n</td>\n" % (styling, data)


def status_to_color(status):
    status = status.lower()
    status_colors = {}
    status_colors["critical"] = "#efbebe"
    status_colors["high"] = "#facdae"

    if status in status_colors:
        return status_colors[status]
    return "#dadada"


def create_table_row(data, styling=""):
    styling = 'style="%s"' % styling
    return "<tr %s>\n%s\n</tr>\n" % (styling, data)


def create_table_head(headings, styling=""):
    result = ""
    for heading in headings:
        result += ('<th style="%s">\n%s\n</th>\n' % (styling, heading))
    return create_table_row(result, style_thead)


def create_alerts_table(alerts):
    table = '<table cellspacing="0" cellpadding="0" style="%s">' % style_table
    table += create_table_head(["Severity", "Service", "Kpi", "Description", "New error"], style_thead_tr)
    x = 0
    for alert in alerts:
        event_row_styling = "font-family: Arial; "
        x += 1
        row_data = create_table_data(alert.severity, style_td)
        row_data += create_table_data(alert.service, style_td)
        row_data += create_table_data(alert.kpi, style_td)
        row_data += create_table_data(alert.description, style_td)
        row_data += create_table_data(alert.is_new, style_td)
        if alert.is_new:
            event_row_styling += "background-color: #f48989;"
        else:
            event_row_styling += "background-color: %s" % (status_to_color(alert.severity))

        table += create_table_row(row_data, event_row_styling)
    table += "</table>"

    return table


def create_html_template(content):
    mail_template_filled = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ' \
                           '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html ' \
                           'xmlns="http://www.w3.org/1999/xhtml">\n <head>\n  <meta http-equiv="Content-Type" ' \
                           'content="text/html; charset=UTF-8" />\n  <title>\n HTML email\n</title>\n  <meta ' \
                           'name="viewport" content="width=device-width, ' \
                           'initial-scale=1.0"/>\n</head>\n<body>\n%s\n</body>\n</html>\n ' % content
    return mail_template_filled


def send_mail(mail_to, email_message, mail_from="default@somedomain.com", mailserver="localhost", subject="Message from python script"):
    msg = MIMEMultipart('alternative')
    part1 = MIMEText(email_message, 'html')
    msg.attach(part1)
    msg['To'] = mail_to
    msg['Subject'] = subject
    mail_to_list = mail_to.split(",")
    try:
        smtp = secure_smtplib.SecureSMTP(host=mailserver)
        logging.debug(mail_to_list)
        logging.debug(mail_to)
        smtp.sendmail(mail_from, mail_to_list, msg.as_string())
        smtp.quit()
    except Exception as e:
        logging.error(str(e))


if __name__ == "__main__":
    try:
        alerts = []

        data = sys.stdin.read()
        parsed_result = json.loads(data)
        # Get the address of the gzip file.
        csv_gzip_location = parsed_result["results_file"]
        owner = parsed_result["owner"]
        app = parsed_result["app"]
        session_key = parsed_result["session_key"]

        # Read the results of the query that triggered the alert
        csv_reader = csv.DictReader(gzip.open(csv_gzip_location, 'rb'))
        to_mail_list = ""
        line_counter = 0
        new_events = 0
        total_events = 0
        machine_id = ""
        for line in csv_reader:
            total_events += 1
            if line["New error"]:
                new_events += 1
            alerts.append(Alert(severity=line["Severity"], service=line["Service"], kpi=line["KPI"],
                                description=line["Description"], is_new=line["New error"]))

            if not machine_id:
                if "Machine ID" in line:
                    machine_id = line["Machine ID"]
            if not to_mail_list:
                if 'Mail_to' in line:
                    to_mail_list = line["Mail_to"]

        # Write the HTML formatted email content
        message = "<h1>Notable events</h1>\n"
        message += "<p>New events: <b>%s</b></p>\n" % new_events
        message += "<p>Total events: <b>%s</b></p>\n" % total_events
        message += "<p>Machine ID: <b>%s</b></p>\n" % machine_id
        events_table = create_alerts_table(alerts)
        message += events_table
        message = create_html_template(message)

        # Get the mailserver
        uri = entity.buildEndpoint(
            [
                'saved',
                'searches',
                '_new'
            ],
            namespace=app,
            owner=owner
        )
        responseHeaders, responseBody = simpleRequest(uri, method='GET', getargs={'output_mode': 'json'},
                                                      sessionKey=session_key)
        savedSearch = json.loads(responseBody)
        ssContent = savedSearch['entry'][0]['content']
        mail_server = ssContent.get('action.email.mailserver', 'localhost')
        mail_from = ssContent.get('action.email.from', 'localhost')
        mail_subject = (" %s | New notable events: %s, Total events: %s."% (machine_id, new_events, total_events))
        try:
            f = open('email_content.html','w')
            f.write(message)
            send_mail(mail_to=to_mail_list, mail_from=mail_from, email_message=message, mailserver=mail_server, subject=mail_subject)
        except:
            logging.exception('Failed sending email')
    except Exception as e:
        logging.exception("Failed while getting the information for the email")

Note that this only uses unsecured smtplib email, if you want to use ssl/password you should update the following part to get the mail information:

     ssContent = savedSearch['entry'][0]['content']
     mail_server = ssContent.get('action.email.mailserver', 'localhost')
     mail_from = ssContent.get('action.email.from', 'localhost')

osakachan
Communicator

I had problems with html and sendemail too. The best solution I found was modifying sendemail.py.
Need to modify {msg|h} to {msg} inside the script.

Credits to:
https://answers.splunk.com/answers/389294/how-to-use-html-code-in-email-alerts-to-format-the.html

0 Karma

sboogaar
Path Finder

@elpred0 I added my solution it might help you.

0 Karma

sboogaar
Path Finder

@elpred0 I want to use my custom python script not the sendmail,py i just use it as a reference,

0 Karma
Get Updates on the Splunk Community!

Earn a $35 Gift Card for Answering our Splunk Admins & App Developer Survey

Survey for Splunk Admins and App Developers is open now! | Earn a $35 gift card!      Hello there,  Splunk ...

Continuing Innovation & New Integrations Unlock Full Stack Observability For Your ...

You’ve probably heard the latest about AppDynamics joining the Splunk Observability portfolio, deepening our ...

Monitoring Amazon Elastic Kubernetes Service (EKS)

As we’ve seen, integrating Kubernetes environments with Splunk Observability Cloud is a quick and easy way to ...