Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for sending email to multiple recipients. #15

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## [Version 1.0.2](https://github.com/dataiku/dss-plugin-sendmail/releases/tag/v1.0.2) - Feature release - 2024-04

- Support for sending emails to multiple recipients

## [Version 1.0.1](https://github.com/dataiku/dss-plugin-sendmail/releases/tag/v1.0.1) - Feature release - 2024-04

- Please read if upgrading from version 1.0.0 of the plugin
Expand Down
22 changes: 18 additions & 4 deletions custom-recipes/send-mails-from-contacts-dataset/recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from dku_attachment_handling import build_attachment_files, attachments_template_dict
from email_utils import build_email_subject, build_email_message_text
from jinja2 import Environment, StrictUndefined
import json
import collections.abc

logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')

Expand Down Expand Up @@ -33,6 +35,17 @@ def to_real_channel_id(channel_id):
def does_channel_have_sender(channel_id):
return channel_id is not None and channel_id.endswith(SENDER_SUFFIX)

def parse_recipients(recipients):
try:
value = json.loads(recipients)
if isinstance(value, collections.abc.Sequence) and not isinstance(value, (str)):
#value is an array
return value
else:
return [value]
except json.decoder.JSONDecodeError:
pass
return recipients.split(",")

# Get handles on datasets
output_A_names = get_output_names_for_role('output')
Expand Down Expand Up @@ -155,8 +168,8 @@ def does_channel_have_sender(channel_id):
fail = 0
try:
for contact in people.iter_rows():
recipient = contact[recipient_column]
if recipient:
recipients = contact[recipient_column]
if recipients:
logging.info("Sending to %s" % contact[recipient_column])
else:
logging.info("No recipient for row - emailing will fail - row data: %s" % contact)
Expand All @@ -165,10 +178,11 @@ def does_channel_have_sender(channel_id):
email_subject = build_email_subject(use_subject_value, subject_template, subject_column, contact_dict)
email_body_text = build_email_message_text(use_body_value, body_template, attachments_templating_dict, contact_dict, body_column,
use_html_body_value)
recipient = contact_dict[recipient_column]
#recipients = contact_dict[recipient_column]
recipients = parse_recipients(recipients)
# Note - if the channel has a sender configured, the sender value will be ignored by the email client here
sender = sender_value if use_sender_value else contact_dict.get(sender_column, "")
email_client.send_email(sender, recipient, email_subject, email_body_text, attachment_files)
email_client.send_email(sender, recipients, email_subject, email_body_text, attachment_files)

contact_dict['sendmail_status'] = 'SUCCESS'
success += 1
Expand Down
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "sendmail",
"version": "1.0.1",
"version": "1.0.2",
"meta": {
"label": "Send emails",
"description": "Send emails based on a dataset containing a list of contacts, with optional attachments (other datasets)",
Expand Down
14 changes: 7 additions & 7 deletions python-lib/dku_email_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ def __init__(self, plain_text):
self.plain_text = plain_text

@abstractmethod
def send_email(self, sender, recipient, email_body, email_subject, attachment_files):
def send_email(self, sender, recipiens, email_body, email_subject, attachment_files):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def send_email(self, sender, recipiens, email_body, email_subject, attachment_files):
def send_email(self, sender, recipients, email_body, email_subject, attachment_files):

"""
:param sender: sender email, str - is ignored if a sender configured for the channel
:param recipient: recipient email, str
:param recipients: recipients email, Seq
:param email_subject: str
:param email_body: body of either plain text or html, str
:param attachment_files:attachments as list of AttachmentFile
Expand Down Expand Up @@ -79,12 +79,12 @@ def __init__(self, plain_text, channel_id):
logging.info(f"Configured channel messaging client with channel {channel_id} - type: {self.channel.type}, "
f"sender: {self.channel.sender}, plain_text? {self.plain_text}")

def send_email(self, sender, recipient, email_subject, email_body, attachment_files):
def send_email(self, sender, recipients, email_subject, email_body, attachment_files):

files = [(a.file_name, a.data, f"{a.mime_type}/{a.mime_subtype}") for a in attachment_files]

sender_to_use = None if self.channel.sender else sender
self.channel.send(self.project_id, [recipient], email_subject, email_body, attachments=files, plain_text=self.plain_text, sender=sender_to_use)
self.channel.send(self.project_id, recipients, email_subject, email_body, attachments=files, plain_text=self.plain_text, sender=sender_to_use)


class SmtpEmailClient(AbstractMessageClient):
Expand Down Expand Up @@ -113,10 +113,10 @@ def login(self):
logging.info(f"Authenticated against STMP mail client")


def send_email(self, sender, recipient, email_subject, email_body, attachment_files):
def send_email(self, sender, recipients, email_subject, email_body, attachment_files):
msg = MIMEMultipart()
msg["From"] = sender
msg["To"] = recipient
msg["To"] = ", ".join(recipients)
msg["Subject"] = email_subject
body_encoding = "utf-8"
text_type = 'plain' if self.plain_text else 'html'
Expand All @@ -130,7 +130,7 @@ def send_email(self, sender, recipient, email_subject, email_body, attachment_fi
raise Exception(f'Cannot handle mime type {attachment_file.mime_type}')
mime_app.add_header("Content-Disposition", 'attachment', filename=attachment_file.file_name)
msg.attach(mime_app)
self.smtp.sendmail(sender, [recipient], msg.as_string())
self.smtp.sendmail(sender, recipients, msg.as_string())

def quit(self):
""" Do any disconnection needed"""
Expand Down