From 73735b378f69857ec836024b06b533ac21817032 Mon Sep 17 00:00:00 2001 From: Benjamin Renard Date: Tue, 14 Mar 2023 17:00:31 +0100 Subject: [PATCH] Email: add support for CC & BCC recipients --- .pylintrc | 2 + mylib/email.py | 53 +++++++++++++++++++++---- mylib/scripts/email_test.py | 33 ++++++++++++++- mylib/scripts/email_test_with_config.py | 27 ++++++++++++- 4 files changed, 104 insertions(+), 11 deletions(-) diff --git a/.pylintrc b/.pylintrc index b505075..13f584d 100644 --- a/.pylintrc +++ b/.pylintrc @@ -8,6 +8,8 @@ disable=invalid-name, too-many-nested-blocks, too-many-instance-attributes, too-many-lines, + too-many-statements, + logging-too-many-args, duplicate-code, [FORMAT] diff --git a/mylib/email.py b/mylib/email.py index 11ee4be..2562183 100644 --- a/mylib/email.py +++ b/mylib/email.py @@ -187,6 +187,7 @@ class EmailClient( sender_email=None, encoding=None, template=None, + cc=None, **template_vars, ): """ @@ -203,6 +204,8 @@ class EmailClient( :param sender_email: Custom sender email (default: as defined on initialization) :param encoding: Email content encoding (default: as defined on initialization) :param template: The name of a template to use to forge this email + :param cc: Optional list of CC recipient addresses. + List of tuple(name, email) or just the email of the recipients. All other parameters will be consider as template variables. """ @@ -214,6 +217,16 @@ class EmailClient( for recipient in recipients ] ) + + if cc: + cc = [cc] if not isinstance(cc, list) else cc + msg["Cc"] = ", ".join( + [ + email.utils.formataddr(recipient) if isinstance(recipient, tuple) else recipient + for recipient in cc + ] + ) + msg["From"] = email.utils.formataddr( ( sender_name or self._get_option("sender_name"), @@ -286,7 +299,9 @@ class EmailClient( msg.attach(part) return msg - def send(self, recipients, msg=None, subject=None, just_try=False, **forge_args): + def send( + self, recipients, msg=None, subject=None, just_try=False, cc=None, bcc=None, **forge_args + ): """ Send an email @@ -297,12 +312,41 @@ class EmailClient( using msg parameter) :param just_try: Enable just try mode (do not really send email, default: as defined on initialization) + :param cc: Optional list of CC recipient addresses. List of tuple(name, email) or + just the email of the recipients. + :param bcc: Optional list of BCC recipient addresses. List of tuple(name, email) or + just the email of the recipients. All other parameters will be consider as parameters to forge the message (only if the message is not provided using msg parameter). """ recipients = [recipients] if not isinstance(recipients, list) else recipients - msg = msg if msg else self.forge_message(recipients, subject, **forge_args) + msg = msg if msg else self.forge_message(recipients, subject, cc=cc, **forge_args) + catch_addr = self._get_option("catch_all_addr") + if catch_addr: + log.debug( + "Catch email originaly send to %s (CC:%s, BCC:%s) to %s", + ", ".join(recipients), + ", ".join(cc) if isinstance(cc, list) else cc, + ", ".join(bcc) if isinstance(bcc, list) else bcc, + catch_addr, + ) + recipients = catch_addr if isinstance(catch_addr, list) else [catch_addr] + else: + if cc: + recipients.extend( + [ + recipient[1] if isinstance(recipient, tuple) else recipient + for recipient in (cc if isinstance(cc, list) else [cc]) + ] + ) + if bcc: + recipients.extend( + [ + recipient[1] if isinstance(recipient, tuple) else recipient + for recipient in (bcc if isinstance(bcc, list) else [bcc]) + ] + ) if just_try or self._get_option("just_try"): log.debug( @@ -312,11 +356,6 @@ class EmailClient( ) return True - catch_addr = self._get_option("catch_all_addr") - if catch_addr: - log.debug("Catch email originaly send to %s to %s", ", ".join(recipients), catch_addr) - recipients = catch_addr if isinstance(catch_addr, list) else [catch_addr] - smtp_host = self._get_option("smtp_host") smtp_port = self._get_option("smtp_port") try: diff --git a/mylib/scripts/email_test.py b/mylib/scripts/email_test.py index e619f2e..a5419f9 100644 --- a/mylib/scripts/email_test.py +++ b/mylib/scripts/email_test.py @@ -51,6 +51,24 @@ def main(argv=None): # pylint: disable=too-many-locals,too-many-statements help="Test mako templating", ) + test_opts.add_argument( + "--cc", + action="store", + type=str, + dest="test_cc", + help="Test CC email recipient(s)", + nargs="+", + ) + + test_opts.add_argument( + "--bcc", + action="store", + type=str, + dest="test_bcc", + help="Test BCC email recipient(s)", + nargs="+", + ) + options = parser.parse_args() if not options.test_to: @@ -65,8 +83,19 @@ def main(argv=None): # pylint: disable=too-many-locals,too-many-statements email_client = init_email_client(options) - log.info("Send a test email to %s", ", ".join(options.test_to)) - if email_client.send(options.test_to, template="test", sent_date=datetime.datetime.now()): + log.info( + "Send a test email to %s (CC: %s / BCC: %s)", + ", ".join(options.test_to), + ", ".join(options.test_cc) if options.test_cc else None, + ", ".join(options.test_bcc) if options.test_bcc else None, + ) + if email_client.send( + options.test_to, + cc=options.test_cc, + bcc=options.test_bcc, + template="test", + sent_date=datetime.datetime.now(), + ): log.info("Test email sent") sys.exit(0) log.error("Fail to send test email") diff --git a/mylib/scripts/email_test_with_config.py b/mylib/scripts/email_test_with_config.py index e137575..0073639 100644 --- a/mylib/scripts/email_test_with_config.py +++ b/mylib/scripts/email_test_with_config.py @@ -56,14 +56,37 @@ def main(argv=None): # pylint: disable=too-many-locals,too-many-statements help="Test mako templating", ) + test_opts.add_argument( + "--cc", + action="store", + type=str, + dest="test_cc", + help="Test CC email recipient(s)", + nargs="+", + ) + + test_opts.add_argument( + "--bcc", + action="store", + type=str, + dest="test_bcc", + help="Test BCC email recipient(s)", + nargs="+", + ) + options = config.parse_arguments_options() if not options.test_to: parser.error("You must specify at least one test email recipient using -t/--to parameter") sys.exit(1) - logging.info("Send a test email to %s", ", ".join(options.test_to)) - if email_client.send(options.test_to, template="test", sent_date=datetime.datetime.now()): + if email_client.send( + options.test_to, + cc=options.test_cc, + bcc=options.test_bcc, + template="test", + sent_date=datetime.datetime.now(), + ): logging.info("Test email sent") sys.exit(0) logging.error("Fail to send test email")