Send Notifications with Amazon SES

Hello together,
i want to configure CheckMK to send the default HTML Mails via API from Amazon SES.

Sending an Email via HTTPS API is quite Simple. Just a few Lines of Code and the pip Extension boto3.

Reason: Mails can be sent out directly over https and it usually works without opening Ports on the Firewall. It would open the way to deploy checkmk on any customer site and allowing to send Mails when https port 443 to the internet is reachable.

Is there any way to use the default Template from CheckMK for Sending Mails or do i really have to program my own Notification Script for my own including the whole E-Mail Template?

You have to create a custom notification script for it. In the Notification documentation we have an example to create notification scripts. Other than that you can have a look at other notification scripts like Servicenow and get an idea. The scripts are located under ~/share/check_mk/notifications.

Thank you for the answer @chauhan_sudhir .

Is it possible to make a copy of the original Mailing Script mail.py and the wato file (dont know which one it is).

I want to use the original Mailing format of checkmk Enterprise and just want to develop a new Sending Method to let CheckMK use Amazon SES over API as Method.

Yes, this is possible. See Notifications - via Email, SMS, Ticketsystems and more

Hello @chauhan_sudhir ,

i copied the mail file and created a new File Called “mailses”

What do i have to do to copy the Wato Config for this Notification, to make the Configurable Variables Available for my copied notification plugin?

I want a copy of the Original Mail Plugin including all Features the Original HTML-Mail of CheckMK offers. I just modified the copied mail.py and added the Feature to send the mail over the Amazon-SES Api.

image

Hi again @chauhan_sudhir,

i got it working.
I can send mails with the copy of original HTML E-Mail Script. Its quite easy to use with amazon.

I just have a problem with the Feature
“Send seperate notifications to every recipient”

Because the module Names are hardcoded in the notify.py

                split_contexts = (
                    plugin_name not in ["", "mail", "asciimail", "slack"] or
                    # params can be a list (e.g. for custom notificatios)
                    params.get("disable_multiplexing") or bulk)

My new Module has the Name “mailses”

How can i add the module to the this file without hardcoding this file? It would get overwritten with every checkmk Update.

The Problem now is, that every notification gets send seperately and not together.

Glad to hear that it works.

You can try importing the part from notify.py with:
from cmk.base.notify import _process_notifications

than you can add _process_notifications() to your script into a patched function and change the lines in question.

def _process_notifications_patched(raw_context: EventContext, notifications: Notifications,
                           num_rule_matches: int, analyse: bool) -> List[NotifyPluginInfo]:
    plugin_info = []
    ...

After changing you can assign this patch to _process_notifications in the same script
_process_notifications = _process_notifications_patched

This is just a rough guess but worth a try.

I have added this proposal as a feature request to our feature portal. If you find it valuable, please vote for it

1 Like

Hello @chauhan_sudhir ,

thank you for your help.

I did not get the patch to work.

Maybe the notify.py runs before my plugin is called and my Script does not get the information anymore who should get this Mail.

I checked all variables in my output and also the variable CONTACTEMAIL shows only one mail address with my plugin called mailses:

And here is the Same when i send my Message with the plugin Name “mail”

So i guess i can code whatever i want, the information is already missing when the script gets started to be executed. :frowning:

Also the log creates only one spoolfile with the mail plugin, while my mailses plugin gets called with 2 spoolfiles.

~/var/log/notify (mailses plugin)

2022-10-18 00:36:19,262 [20] [cmk.base.notify] Got raw notification (CheckMK;Check_MK) context with 73 variables
2022-10-18 00:36:19,263 [20] [cmk.base.notify] Global rule ''...
2022-10-18 00:36:19,263 [20] [cmk.base.notify]  -> matches!
2022-10-18 00:36:19,263 [20] [cmk.base.notify]    - adding notification of test, cmkadmin via mailses
2022-10-18 00:36:19,263 [20] [cmk.base.notify] Executing 1 notifications:
2022-10-18 00:36:19,263 [20] [cmk.base.notify]   * notifying test, cmkadmin via mailses, parameters: from, amazonses, graphs_per_notification, notifications_with_graphs, bulk: no
2022-10-18 00:36:19,263 [20] [cmk.base.notify] Creating spoolfile: /omd/sites/monitoring/var/check_mk/notify/spool/46835d1f-6715-4946-a574-3053787ae53f
2022-10-18 00:36:19,264 [20] [cmk.base.notify] Creating spoolfile: /omd/sites/monitoring/var/check_mk/notify/spool/9c16471c-111e-4969-aee6-61fd9ee879a9
2022-10-18 00:36:23,881 [20] [cmk.base.notify] ----------------------------------------------------------------------
2022-10-18 00:36:23,882 [20] [cmk.base.notify] Got spool file 46835d1f (CheckMK;Check_MK) for local delivery via mailses
2022-10-18 00:36:23,882 [20] [cmk.base.notify]      executing /omd/sites/monitoring/local/share/check_mk/notifications/mailses
2022-10-18 00:36:24,692 [20] [cmk.base.notify]      Output: Email sent! Message ID: 01070183e8174f9b-4a68535a-8a03-49eb-a5b0-165c4f67bcb9-000000 | Used Configuration Set: Not defined | Used AWS Region: eu-central-1
2022-10-18 00:36:26,112 [20] [cmk.base.notify] ----------------------------------------------------------------------
2022-10-18 00:36:26,113 [20] [cmk.base.notify] Got spool file 9c16471c (CheckMK;Check_MK) for local delivery via mailses
2022-10-18 00:36:26,113 [20] [cmk.base.notify]      executing /omd/sites/monitoring/local/share/check_mk/notifications/mailses
2022-10-18 00:36:26,877 [20] [cmk.base.notify]      Output: Email sent! Message ID: 01070183e817583d-819ac686-4d42-4901-be18-c1a048309c3d-000000 | Used Configuration Set: Not defined | Used AWS Region: eu-central-1

~/var/log/notify (mail plugin)

2022-10-18 00:50:10,970 [20] [cmk.base.notify] Got raw notification (CheckMK;Check_MK) context with 73 variables
2022-10-18 00:50:10,971 [20] [cmk.base.notify] Global rule ''...
2022-10-18 00:50:10,971 [20] [cmk.base.notify]  -> matches!
2022-10-18 00:50:10,971 [20] [cmk.base.notify]    - adding notification of cmkadmin, test via mail
2022-10-18 00:50:10,971 [20] [cmk.base.notify] Executing 1 notifications:
2022-10-18 00:50:10,971 [20] [cmk.base.notify]   * notifying cmkadmin, test via mail, parameters: from, elements, amazonses, graphs_per_notification, notifications_with_graphs, bulk: no
2022-10-18 00:50:10,972 [20] [cmk.base.notify] Creating spoolfile: /omd/sites/monitoring/var/check_mk/notify/spool/1b2ba235-7359-40ab-956b-705592494e6c
2022-10-18 00:50:16,255 [20] [cmk.base.notify] ----------------------------------------------------------------------
2022-10-18 00:50:16,256 [20] [cmk.base.notify] Got spool file 1b2ba235 (CheckMK;Check_MK) for local delivery via mail
2022-10-18 00:50:16,256 [20] [cmk.base.notify]      executing /omd/sites/monitoring/local/share/check_mk/notifications/mail
2022-10-18 00:50:17,277 [20] [cmk.base.notify]      Output: Email sent! Message ID: 01070183e82403f2-3502a9d6-b108-4644-b128-1418275b2bbc-000000 | Used Configuration Set: Not defined | Used AWS Region: eu-central-1

A workaround was to copy the notify.py and change the 2 lines to get mailing and graphs working…
This works but is not the best and performant solution :frowning:
It also can break the site after update

if __name__ == "__main__":

    import shutil
    homefolder=os.path.expanduser('~')
    src="/opt/omd/versions/default/lib/python3/cmk/base/notify.py"
    dst=homefolder + "/local/lib/python3/cmk/base/notify.py"

    shutil.copyfile(src, dst)

    with open(dst) as f:
        if not 'mailses' in f.read():
            search0='"mail", '
            replace0='"mail", "mailses", '
            search1='if plugin_name == "mail"'
            replace1='if plugin_name == "mail" or plugin_name == "mailses"'            
            
            
            import fileinput
            with fileinput.FileInput(dst, inplace=True, backup='.bak') as file:
                for line in file:
                    line=line.replace(search0, replace0)
                    line=line.replace(search1, replace1)
                    print(line, end='')

    main()

I wanted to edit my last post but deleted it

Hi,
i created a Pull Request, but im not sure if Checkmk will ever integrate it:

I tried to create an MKP Package but it doesnt make sense. The Plugin uses the original code from the Core Mail Plugin. If i would create an own mkp Package, i always need to implement all Changes and Security fixes to my own Plugin. I just extened the Mail Plugin with the Amazon SES definition and extened the wato plugin with all needed fields.

Anyway if someone wants to test with MKP, feel free…

KPC-HTML-Email-1.0.2.mkp (3.8 KB)

mail

#!/usr/bin/env python3
# HTML Email - K&P Computer
# Bulk: yes
# Argument 1: Full system path to the pnp4nagios index.php for fetching the graphs. Usually auto configured in OMD.
# Argument 2: HTTP-URL-Prefix to open Multisite. When provided, several links are added to the mail.
#             Example: http://myserv01/prod
#
# This script creates a nifty HTML email in multipart format with
# attached graphs and such neat stuff. Sweet!

import botocore
import boto3
from cmk.notification_plugins.mail import *


def send_mail_amazonses(  # pylint: disable=too-many-branches
    message: Message, target: str, from_address: str, context: dict[str, str]
) -> int:

    retry_possible = False
    success = False

    while not success:

        SENDER = context["PARAMETER_FROM_ADDRESS"]
        targetses = target.split(",")

        if not "PARAMETER_AMAZONSES_SESREGION" in context:
            context["PARAMETER_AMAZONSES_SESREGION"] = "eu-central-1"
            
        if not "PARAMETER_AMAZONSES_SESCONFIG" in context:
            context["PARAMETER_AMAZONSES_SESCONFIG"] = ""
            configsettext = "Not defined"
        else:
            configsettext = context["PARAMETER_AMAZONSES_SESCONFIG"]

        if not "PARAMETER_AMAZONSES_SESAPIKEY" in context:
            sys.stderr.write("Error: Amazon API Key missing in configuration")
            break
        if not "PARAMETER_AMAZONSES_SESAPIPASSWORD" in context:
            sys.stderr.write("Error: Amazon API Password missing in configuration")

    
        # Connect to Amazon SES API       
        client = boto3.client('ses',region_name=context["PARAMETER_AMAZONSES_SESREGION"],aws_access_key_id=context["PARAMETER_AMAZONSES_SESAPIKEY"],aws_secret_access_key=context["PARAMETER_AMAZONSES_SESAPIPASSWORD"])

        try:    
            response = client.send_raw_email(
                Source=SENDER,
                Destinations=targetses,
                RawMessage={'Data': message.as_string()},
                ConfigurationSetName=context["PARAMETER_AMAZONSES_SESCONFIG"]
            )
            success = True 
            print("Email sent! Message ID: " + response['MessageId'] + " | Used Configuration Set: " + configsettext + " | Used AWS Region: " + context["PARAMETER_AMAZONSES_SESREGION"]),
        except botocore.exceptions.ClientError as error:
            if error.response['Error']['Code'] == 'LimitExceededException':
                sys.stderr.write("API call limit exceeded; backing off and retrying...")
                break
            else:
                raise error
            
    if success:
        return 0
    if retry_possible:
        return 1
    return 2


# TODO: Use EmailContent parameter.
def send_mail_kpc(message: Message, target: str, from_address: str, context: dict[str, str]) -> int:
    return send_mail_amazonses(message, target, from_address, context)
    return 0



def mainkpc() -> NoReturn:
    content = (
        BulkEmailContent(utils.read_bulk_contexts)
        if bulk_mode
        else SingleEmailContent(utils.collect_context)
    )

    if not content.mailto:  # e.g. empty field in user database
        sys.stderr.write("Cannot send HTML email: empty destination email address\n")
        sys.exit(2)

    m = multipart_mail(
        content.mailto,
        content.subject,
        content.from_address,
        content.reply_to,
        content.content_txt,
        content.content_html,
        content.attachments,
    )
    
    try:
            sys.exit(
            send_mail_kpc(
                m,
                content.mailto,
                content.from_address,
                content.context,
            )
         )
    except Exception as e:
        sys.stderr.write("Unhandled exception: %s\n" % e)
        # unhandled exception, don't retry this...
        sys.exit(2)




###Use modified version only if Amazon SES is configured, else use the original code from CheckMK####

content = (
    BulkEmailContent(utils.read_bulk_contexts)
    if bulk_mode
    else SingleEmailContent(utils.collect_context)
)

if "PARAMETER_AMAZONSES_SESAPIKEY" in content.context or "PARAMETER_AMAZONSES_SESAPIPASSWORD" in content.context or "PARAMETER_AMAZONSES_SESREGION" in content.context:
    if __name__ == "__main__":
        mainkpc()
else:
    if __name__ == "__main__":
        main()

mail.py

#!/usr/bin/env python3
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
# conditions defined in the file COPYING, which is part of this source code package.

# Argument 1: Full system path to the pnp4nagios index.php for fetching the graphs. Usually auto configured in OMD.
# Argument 2: HTTP-URL-Prefix to open Multisite. When provided, several links are added to the mail.
#             Example: http://myserv01/prod
#
# This script creates a nifty HTML email in multipart format with
# attached graphs and such neat stuff. Sweet!


from cmk.gui.plugins.wato.notifications import *



####Amazon SES Fields####
amazonsesfields = [
            (
                "amazonses",
                Transform(
                    Dictionary(
                        title="Send Mails with Amazon SES API",
                        elements=[
                            (
                                "sesapikey",
                                TextInput(
                                    title=_("Amazon API Key"),
                                    size=70,
                                    allow_empty=False,
                                ),
                            ),
                            (
                                "sesapipassword",
                                Password(
                                    title=_("Amazon API Password"),
                                    size=70,
                                    allow_empty=False,
                                ),
                            ),
                            (
                                "sesregion",
                                TextInput(
                                    title=_("AWS Region"),
                                    size=20,
                                    allow_empty=False,
                                    default_value="eu-central-1",
                                ),
                            ),
                            (
                                "sesconfig",
                                TextInput(
                                    title=_("SES Configuration Set"),
                                    size=20,
                                    allow_empty=True,
                                ),
                            ),                            
                        ],
                        help=_(
                            "All E-Mails will be Sent with the Amazon SES API"
                        ),
                    ),
                ),
            ),
]



@notification_parameter_registry.register
class NotificationParameterMail(NotificationParameter):
    @property
    def ident(self) -> str:
        return "mail"

    @property
    def spec(self):
        return Dictionary(
            title=_("Create notification with the following parameters"),
            # must be called at run time!!
            elements=self._parameter_elements_kpc,
        )

    def _parameter_elements_kpc(self):
        from cmk.gui.plugins.wato.notifications import _get_url_prefix_specs
        from cmk.gui.plugins.wato.notifications import _vs_add_common_mail_elements
        from cmk.gui.plugins.wato.notifications import local_site_url
        from cmk.gui.plugins.wato.notifications import transform_to_valuespec_html_mail_url_prefix
        from cmk.gui.plugins.wato.notifications import transform_from_valuespec_html_mail_url_prefix

        elements = _vs_add_common_mail_elements(
            [
                (
                    "elements",
                    ListChoice(
                        title=_("Display additional information"),
                        choices=[
                            ("omdsite", _("Site ID")),
                            ("hosttags", _("Tags of the Host")),
                            ("address", _("IP Address of Host")),
                            ("abstime", _("Absolute Time of Alert")),
                            ("reltime", _("Relative Time of Alert")),
                            ("longoutput", _("Additional Plugin Output")),
                            ("ack_author", _("Acknowledgement Author")),
                            ("ack_comment", _("Acknowledgement Comment")),
                            ("notification_author", _("Notification Author")),
                            ("notification_comment", _("Notification Comment")),
                            ("perfdata", _("Metrics")),
                            ("graph", _("Time series graph")),
                            ("notesurl", _("Custom Host/Service Notes URL")),
                            ("context", _("Complete variable list (for testing)")),
                        ],
                        default_value=["graph", "abstime", "address", "longoutput"],
                    ),
                ),
                (
                    "insert_html_section",
                    TextAreaUnicode(
                        title=_("Add HTML section above table (e.g. title, description…)"),
                        default_value="<HTMLTAG>CONTENT</HTMLTAG>",
                        cols=76,
                        rows=3,
                    ),
                ),
                (
                    "url_prefix",
                    _get_url_prefix_specs(
                        "http://" + socket.gethostname() + url_prefix() + "check_mk/",
                        request.is_ssl_request and "automatic_https" or "automatic_http",
                    ),
                ),
                (
                    "no_floating_graphs",
                    FixedValue(
                        value=True,
                        title=_("Display graphs among each other"),
                        totext=_("Graphs are shown among each other"),
                        help=_(
                            "By default all multiple graphs in emails are displayed floating "
                            "nearby. You can enable this option to show the graphs among each "
                            "other."
                        ),
                    ),
                ),
            ]
        )
        elements += amazonsesfields
        
        
        if not cmk_version.is_raw_edition():
            import cmk.gui.cee.plugins.wato.syncsmtp  # pylint: disable=no-name-in-module

            elements += cmk.gui.cee.plugins.wato.syncsmtp.cee_html_mail_smtp_sync_option


        elements += [
            (
                "graphs_per_notification",
                Integer(
                    title=_("Graphs per notification (default: 5)"),
                    label=_("Show up to"),
                    unit=_("graphs"),
                    help=_(
                        "Sets a limit for the number of graphs that are displayed in a notification."
                    ),
                    default_value=5,
                    minvalue=0,
                ),
            ),
            (
                "notifications_with_graphs",
                Integer(
                    title=_("Bulk notifications with graphs (default: 5)"),
                    label=_("Show graphs for the first"),
                    unit=_("Notifications"),
                    help=_(
                        "Sets a limit for the number of notifications in a bulk for which graphs "
                        "are displayed. If you do not use bulk notifications this option is ignored. "
                        "Note that each graph increases the size of the mail and takes time to render"
                        "on the monitoring server. Therefore, large bulks may exceed the maximum "
                        "size for attachements or the plugin may run into a timeout so that a failed "
                        "notification is produced."
                    ),
                    default_value=5,
                    minvalue=0,
                ),
            ),
        ]
        return elements