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

is_multiple=True prevents use of dynamic template data #961

Open
ghost opened this issue Dec 10, 2020 · 7 comments
Open

is_multiple=True prevents use of dynamic template data #961

ghost opened this issue Dec 10, 2020 · 7 comments
Labels
status: help wanted requesting help from the community type: bug bug in the library

Comments

@ghost
Copy link

ghost commented Dec 10, 2020

Issue Summary

Hey all,

Using the mail helper with is_multiple=True causes the dynamic_template_data on the message not to propagate to the actual message. It seems that this data has to somehow be included individually in each personalization object, but I don't think I can set the Mail object's personalizations property manually, meaning there's no way to push the dynamic_template_data down into each of those objects.

If this is a bug, I would suggest automatically copying the dynamic_template_data to each personalization object when is_multiple is set to True.

Steps to Reproduce

  1. Create a simple Mail object, like so: Mail(from_email='[email protected]', to_emails=['one', 'two', ...], is_multiple=True
  2. Set the dynamic_template_data property.
  3. Set the template_id property.
  4. Send the email. Notice that all recipients receive it as they should, but that the dynamic template data does not come along for the ride.

Code Snippet

def send_notification_email(emails: list, dynamic_data: dict, file_content: str):
    message = Mail(
        from_email='[email protected]',
        to_emails=[To(email=email) for email in emails],
        is_multiple=True)
    message.dynamic_template_data = dynamic_data
    message.template_id = TemplateId('atemplateid')
    message.attachment = Attachment(FileContent(file_content), FileName('somename.someextension'))
    message.asm = Asm(GroupId(12345), GroupsToDisplay([12345]))
    sendgrid_client = SendGridAPIClient(os.environ['SENDGRID_API_KEY'])
    response = sendgrid_client.send(message)
    if response.status_code != 202:
        logging.info(response.status_code)
        raise Exception("Email send failed.")
    return response.body

Exception/Log

N/A, code executes "successfully".

Technical details:

  • sendgrid-python version: 6.4.8
  • python version: 3.8.6 64bit
@JenniferMah JenniferMah added the type: bug bug in the library label Dec 16, 2020
@JenniferMah
Copy link
Contributor

This issue has been added to our internal backlog to be prioritized. Pull requests and +1s on the issue summary will help it move up the backlog.

@ksho
Copy link

ksho commented Dec 27, 2020

+1 on this -- the workaround I have implemented as a stopgap is looping through all emails to send to a single address at a time. Not great for latency, but works for now.

@ghost
Copy link
Author

ghost commented Dec 28, 2020

Just an FYI to anyone considering picking this up: The C# library already has this feature implemented as SendGrid.Helpers.Mail.MailHelper.CreateSingleTemplateEmailToMultipleRecipients. Effectively, it just internalizes what @ksho mentioned above -- if showAllRecipients is false (default), it creates a personalization for each recipient and copies the template data into it. This pattern might be useful for implementation in Python.

@ksho
Copy link

ksho commented Dec 28, 2020

..and just to clarify a bit further, that workaround is something like:


members = list of email strings 

for email in members:

        message = Mail(
            from_email='[email protected]',
            to_emails=email,
            subject='lalala')

        message.template_id = TemplateId('d-123456')

        request_body = message.get()
        request_body['personalizations'][0]['dynamic_template_data'] = substitution_data

        try:
            r = sg.client.mail.send.post(request_body=request_body)

@ghost
Copy link
Author

ghost commented Dec 28, 2020

I have a pretty good idea of how this could be implemented...

Here's the way the code should work without a builtin helper from sendgrid.helpers.mail:

import sendgrid
from sendgrid.helpers.mail import Mail

if __name__ == "__main__":
    # We don't want these emails to be able to see each other in the "To" bar, but they should all receive the same email.
    tos = [
        {'name': 'example', 'email': '[email protected]'}
        {'name': 'example1', 'email': '[email protected]'}
        {'name': 'example2', 'email': '[email protected]'}
    ]
    dynamic_template_data = {
        'subject': 'This will be individually copied to each message.'
    }

    message = Mail(from_email='[email protected]')
    message.template_id = "something"

    # This is how we'd do it in C#, and this pattern is embedded into helper methods on MailHelper
    # the MailHelper class' helper methods in C# could easily be modelled as class methods on the Mail object in Python
    for i, to in enumerate(tos):
        message.add_to(to['email'], p=i)
        # this is what needs to be implemented and then incorporated into a constructor/class method on Mail
        # to be abundently clear, Mail.set_template_data does not currently exist
        message.set_template_data(dynamic_template_data, p=i)

    client = sendgrid.SendGridAPIClient(key)

    client.send(message)

And now with a class method helper:

import sendgrid
from sendgrid.helpers.mail import Mail

if __name__ == "__main__":
    # We don't want these emails to be able to see each other in the "To" bar, but they should all receive the same email.
    tos = [
        {'name': 'example', 'email': '[email protected]'}
        {'name': 'example1', 'email': '[email protected]'}
        {'name': 'example2', 'email': '[email protected]'}
    ]
    dynamic_template_data = {
        'subject': 'This will be individually copied to each message.'
    }

    message = Mail.create_single_template_email_with_multiple_recipients(
        from_email='[email protected]', 
        tos=tos, # I don't remember if the object format above is appropriate for the Python library, but this at least proves the pattern
        plain_text_content="hi!",
        html_content="<p>hi!</p>"
        dynamic_template_data=dynamic_template_data)

    client = sendgrid.SendGridAPIClient(key)

    client.send(message)

To achieve this, we'd need five class methods on Mail:

# Pretend there's a subject/plaintext/html argument for each of the following -- I don't want to write them out

@classmethod
def create_single_email(cls, from_email: str, to_email: str, template_id: str, dynamic_template_data: object) {
    #return an instance of the class with the required personalization
}

@classmethod
def create_single_email_to_multiple_recipients(cls, from_email: str, to_emails: list<str>) {
    #return an instance of the class with the required personalizations (one for each to)
    #super easy -- instantiate the class, then just iterate over enumerate(to_emails), calling
    #classobject.add_to(email, i)
}

@classmethod
def create_single_template_email_to_multiple_recipients(yougettheidea) {
    #Same as the above, but call classobject.add_to(email, i) and then classobject.set_template_data(dynamic_template_data, i) (not implemented)
}

#Two more class methods with similar logic for create_multiple_emails_to_multiple_recipients and create_multiple_template_emails_to_multiple_recipients

I think the only other methods we'd have to implement are (instance methods on Mail):

def set_template_data(self, dynamic_template_data: object, p: int):
    personalization = self.GetPersonalization(p)
    personalization.TemplateData = dynamic_template_data

def get_personalization(self, p: int):
    # return self's personalization at the given index, assuming it exists. Throw index errors if we're being messy.
    # if there's no personalization, create one at index 0 and return it

I'm sure there are some implementation details that might make this slightly more difficult, but those are the bones we'd need. If I wasn't so busy, I'd try to tackle it myself.

@keatbro
Copy link

keatbro commented Feb 13, 2021

+1 on this—eating up API requests trying to workaround

@JenniferMah JenniferMah added the status: help wanted requesting help from the community label Mar 15, 2021
@prayashm
Copy link

prayashm commented May 11, 2021

Works in sendgrid==6.6.0 and python 3.7.9 as documentated here:
https://github.com/sendgrid/sendgrid-python/blob/main/use_cases/send_multiple_emails_to_multiple_recipients.md

dynamic_template_data needs to be passed in To() constructor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: help wanted requesting help from the community type: bug bug in the library
Projects
None yet
Development

No branches or pull requests

4 participants