Django send bulk emails - django

I am working on a service to send bulk emails in django.
I have this method which is working well with celery
#shared_task(bind=True)
def send_mails(self,saved_id):
text = BroadCast.objects.get(id=saved_id)
attendees = EventAttendee.objects.filter(event__id=text.id)
message = text.body
subject = text.subject
document = text.attachment
recipient_list=[]
for attend in attendees:
text_content = render_to_string(template_text, {'name': attend.client_name, 'message':message})
html_content = render_to_string(template_html, {'name': attend.client_name,'message':message})
mail.send(
[attend.client_email],
email_from,
subject=subject,
html_message=html_content,
attachments = {str(document):document}
)
my challenge is that if i have for examples 1000 attendees, I will have to open 1000 connections which I believe is a very bad.
How can I restructure it so that I only open one connection and be able to send 1000 emails..

From Django's docs
django.core.mail.send_mass_mail() is intended to handle mass emailing.
Since you are sending html, you would need an extra step, consider the following piece of code from this stackoverflow answer:
from django.core.mail import get_connection, EmailMultiAlternatives
def send_mass_html_mail(datatuple, fail_silently=False, user=None, password=None,
connection=None):
"""
Given a datatuple of (subject, text_content, html_content, from_email,
recipient_list), sends each message to each recipient list. Returns the
number of emails sent.
If from_email is None, the DEFAULT_FROM_EMAIL setting is used.
If auth_user and auth_password are set, they're used to log in.
If auth_user is None, the EMAIL_HOST_USER setting is used.
If auth_password is None, the EMAIL_HOST_PASSWORD setting is used.
"""
connection = connection or get_connection(
username=user, password=password, fail_silently=fail_silently)
messages = []
for subject, text, html, from_email, recipient in datatuple:
message = EmailMultiAlternatives(subject, text, from_email, recipient)
message.attach_alternative(html, 'text/html')
messages.append(message)
return connection.send_messages(messages)

Then you probably want to use send_mass_mail
only one connection to the mail server would be opened
So construct a tuple of messages for all the emails you want to send. (The linked official documentation does a good job explaining usage)

Related

Django - Html tags in email template

I' m using Django signals to send emails when a new record is added:
#receiver(post_save, sender=MeetingMember)
def send_invited_emails(sender, instance, **kwargs):
host = instance.meeting.host
subject = "Mizban invition"
# htmly = get_template('sendingemail.html')
domain = Site.objects.get_current().domain
context={'host':host,'meeting': instance,'domain':domain}
html_content = render_to_string('sendingemail.html',context)
send_mail(subject, html_content, settings.EMAIL_HOST_USER,[instance.email])
This code works but it sends html tags in email as well how can I solve the issue?
The second parameter message is the message you want to display as raw text. You can pass the content of a HTML message through the html_message=… parameter of the send_mail(…) function [Django-doc]:
# …
html_content = render_to_string('sendingemail.html',context)
send_mail(
subject,
html_content,
settings.EMAIL_HOST_USER,[instance.email],
html_message=html_content
)
Usually for the message, one also creates a text variant of the message: one without HTML that can be used by simple email clients to display the email on a console terminal for example.

Flask-mail: How to handle multiple email requests at once

So I wrote a dedicated flask app for handling emails for my application and deployed it on heroku. In which I have set up a route to send emails:
#app.route('/send', methods=['POST'])
def send_now():
with app.app_context():
values = request.get_json()
email = values['email']
code = values['code']
secret_2 = str(values['secret'])
mail = Mail(app)
msg = Message("Password Recovery",sender="no*****#gmail.com",recipients=[email])
msg.html = "<h1>Your Recovery Code is: </h1><p>"+str(code)+"</p>"
if secret == secret_2:
mail.send(msg)
response = {'message': 'EmailSent'}
return jsonify(response), 201
It works fine for a single user at a time, however when multiple users send a POST request, the client user needs to wait till the POST returns a 201. Thus the wait period keeps increasing (it may not even send). So how do I handle this so accommodate multiple simultaneous users. Threads? Buffer? I have no idea
You need to send mail via Asynchronous thread calls in Python. Have a look at this code sample and implement in your code.
from threading import Thread
from app import app
def send_async_email(app, msg):
with app.app_context():
mail.send(msg)
def send_email(subject, sender, recipients, text_body, html_body):
msg = Message(subject, sender=sender, recipients=recipients)
msg.body = text_body
msg.html = html_body
thr = Thread(target=send_async_email, args=[app, msg])
thr.start()
This will allow to send the mail in background.

Django - Attach PDF generated by a view to an email

This question has some elements here, but no final answer.
There is view generating a PDF with easy_pdf
from easy_pdf.views import PDFTemplateResponseMixin
class PostPDFDetailView(PDFTemplateResponseMixin,DetailView):
model = models.Post
template_name = 'post/post_pdf.html'
Then, I want to attach this generated PDF to the following email :
#receiver(post_save, sender=Post)
def first_mail(sender, instance, **kwargs):
if kwargs['created']:
user_email = instance.client.email
subject, from_email, to = 'New account', 'contact#example.com', user_email
post_id = str(instance.id)
domain = Site.objects.get_current().domain
post_pdf = domain + '/post/' + post_id + '.pdf'
text_content = render_to_string('post/mail_post.txt')
html_content = render_to_string('post/mail_post.html')
# create the email, and attach the HTML version as well.
msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
msg.attach_alternative(html_content, "text/html")
msg.attach_file(post_pdf, 'application/pdf')
msg.send()
I also have tried this one:
msg.attach_file(domain + '/post/' + post_id + '.pdf', 'application/pdf')
I was looking for a way to attach an easy_pdf generated PDF without saving a temp file as well. Since I couldn't find a solution elsewhere, I suggest a short and working proposal using easy_pdf.rendering.render_to_pdf:
from easy_pdf.rendering import render_to_pdf
...
post_pdf = render_to_pdf(
'post/post_pdf.html',
{'any_context_item_to_pass_to_the_template': context_value,},
)
...
msg.attach('file.pdf', post_pdf, 'application/pdf')
I hope it'll help if you are still interested by a way to do this.
Not sure if it helps, but I used EmailMessage built-in to attach PDFs I created for reporting to emails I sent out:
from django.core.mail import send_mail, EmailMessage
draft_email = EmailMessage(
#subject,
#body,
#from_email,
#to_email,
)
Option 1:
# attach a file you have saved to the system... expects the path
draft_email.attach_file(report_pdf.name)
Option 2:
# expects the name of the file object
draft_email.attach("Report.pdf")
Then, sends like you already have:
draft_email.send()
Some initial thoughts: it seems like you are trying to use attach_file to attach a file from the system, but it isn't on the system. I would try using attach instead of attach_file, if I am reading your code correctly, since the pdf is in your memory, not in LTS on the system.

recipient_list send mail just to the first address in the list

I´m using signals to send mails to the users depending of some actions. In one of my signals I need to send the same mail to multiple users, my signal use post_save so for the parameter [recipient_list] I use this instance.email_list email_list is where I store the list of email addresses to send the mail, they are stored in this format user1#mail.com, user2#mail.com, user3#mail.com.
To send the emails I use EMAIL_HOST = 'smtp.gmail.com' the problem is that the only user who recive the email is the first one in the list. So I 'log in' in the gmail account to check the send emails section and actually in the "to" area of the email shows that was send to all the users in the list but the emails never arrive just for the first email address in the list.
I read that if google detect that the account sends a lot of messages could be blocked but only send like 5 emails at the same time and when I'm in the account never shows me some alert or something.
So the problem is how I send the emails or maybe some bad configuration of the gmail account?
Any help is really appreciated.
Sorry for my bad grammar.
EDIT: Here's my code.
forms.py
class MyForm(forms.Form):
userslist = forms.ModelChoiceField(queryset = User.objects.filter(here goes my condition to show the users), empty_label='List of users', label='Users', required=False)
emailaddress = forms.CharField(max_length=1000, label='Send to:', required=False)
comment = forms.CharField(widget=CKEditorUploadingWidget(), label="Comment:")
That form display a list of users to select the email address in the field emailaddress store the values. This is my Ajax to bring the email address:
views.py
class mails(TemplateView):
def get(self, request, *args, **kwargs):
id_user = request.GET['id']
us = User.objects.filter(id = id_user)
data = serializers.serialize('json', us, fields=('email'))
return HttpResponse(data, content_type='application/json')
And here's the <script> I use to populate the emailaddres field:
<script>
$('#id_userlist').on('change', concatenate);
function concatenate() {
var id = $(this).val();
$.ajax({
data: { 'id': id },
url: '/the_url_to_get_data/',
type: 'get',
success: function (data) {
var mail = ""
for (var i = 0; i < data.length; i++) {
mail += data[i].fields.email;
}
var orig = $('#id_emailaddress').val();
$('#id_emailaddress').val(orig + mail + ',');
}
})
}
</script>
The signal I use to send the mail is this:
#receiver(post_save, sender=ModelOfMyForm, dispatch_uid='mails_signal')
def mails_signal(sender, instance, **kwargs):
if kwargs.get('created', False):
if instance.emailaddress:
#Here goes the code for the subject, plane_message,
#from_email and template_message.
send_mail(subject, plane_message, from_email, [instance.emailaddress], fail_silently=False, html_message=template_message)
So if I select 4 users the info is save in this way in the database:
Then I 'log in' in the account to check the 'Sent Mail' section and check the detail of the mail and shows that was send to the 4 users but the only user who recibe the mail was the first in the list.
Your problem is that you are passing a comma-separated string of email addresses inside instance.emailaddress (first#gmail.com, second#hotmail.com, third#hotmail.com etc). Django expects a Python list of addresses, not a comma separated string. It will just ignore everything after the first comma.
Change your code as follows and it will work:
def mails_signal(sender, instance, **kwargs):
if kwargs.get('created', False):
if instance.emailaddress:
#Here goes the code for the subject, plane_message,
#from_email and template_message.
recipients = [r.strip() for r in instance.emailaddress.split(',')]
send_mail(subject, plane_message, from_email, recipients, fail_silently=False, html_message=template_message)

Delaying the sending emails with Sendgrid and Django, SMTP api and send_at header

I've spent a lot of time figuring out how to send email at a specified time in Django, so I am posting it with answer here to save others some time.
My use case is sending email during working hours. Using celery for that is a bad idea. But Sendgrid can send emails with a delay of up to 3 days. That's what we need.
That what I made:
from django.core.mail import EmailMultiAlternatives
from django.template.context import Context
from django.template.loader import get_template
from smtpapi import SMTPAPIHeader
def send_email(subject, template_name, context, to, bcc=None, from_email=settings.DEFAULT_FROM_EMAIL, send_at=None):
header = SMTPAPIHeader()
body = get_template(template_name).render(Context(context))
if send_at:
send_at = {"send_at": send_at}
header.set_send_at(send_at)
email = EmailMultiAlternatives(
subject=subject,
body=body,
from_email=from_email,
to=to,
bcc=bcc,
headers={'X-SMTPAPI': header.json_string()}
)
email.attach_alternative(body, 'text/html')
email.send()
Don't forget to set it in header X-SMTPAPI cause I couldn't find it anywhere..
And send_at should be a timestamp
Also here you could see how to add headers or anything but with sendgrid.SendGridClient:
https://sendgrid.com/docs/Utilities/code_workshop.html/scheduling_parameters.html
import sendgrid
...
sg = sendgrid.SendGridClient('apiKey')
message = sendgrid.Mail()
message.add_to('John Doe <example#mailinator.com>')
message.set_subject('Example')
message.set_html('Body')
message.set_text('Body')
message.set_from('Doe John <example#example.com>')
message.smtpapi.set_send_at(timestamp)
sg.send(message)
send_at = {"send_at": send_at} produces X-SMTPAPI: {"send_at": {"send_at": 1643934600}} when you print the headers.
Instead, just use send_at = send_at or you can as well delete the line entirely.