Django don't send email message with attached zip file - python

everyone! I have a problem with sending an email with Django smtp email backend and SendGrid. My view is below.
It makes batch of pdf files with pyppeteer, zip them to archive, attaches to the message and then send it to the email. It should be, but it doesn't work!
I checked it with other view for email sending(a little bit diff logic without zip or something.) and it worked but this means that it's not the problem with sendgrid and any other stuff like secret keys in uwsgi configuration.
The fact is, emails don't stuck in send grid list.
I tried to put some dummy bites instead of pdf file like pdf_file = b'some bytes' and it worked on my local machine but didn't work on server. I got the size of zip-archive from a response. Please explain to me where I'm wrong.
### views.py
#csrf_exempt
#require_POST
def generate_batch_pdf(request, blueprint):
try:
payload = json.loads(request.body)
email = payload.get('email')
uuids = payload.get('uuids')
zip_buffer = io.BytesIO()
compression = zipfile.ZIP_DEFLATED
with zipfile.ZipFile(zip_buffer, 'a', compression, False) as f:
for uuid in uuids:
report_name = _report_name(uuid, blueprint)
pdf_file = async_to_sync(generate_pdf_file)(
request.get_host(),
uuid,
blueprint,
report_name,
)
f.writestr(
zinfo_or_arcname='{0}'.format(report_name),
data=pdf_file,
)
msg = EmailMessage(
subject='Отчеты Тест',
body='Архив выбранных отчетов',
from_email=settings.DEFAULT_FROM_EMAIL,
to=[email],
)
msg.attach('reports.zip', zip_buffer.getvalue(), 'application/zip')
msg.send(fail_silently=False)
return JsonResponse(
{
'msg': 'Ok',
# it's here just for check
'size': '{0}'.format(sys.getsizeof(zip_buffer)),
},
)
except Exception as e:
return JsonResponse({'msg': 'Err -> {e}'.format(e=e)})
I'm using python 3.6
### requirements.txt
django==3.1
...

Related

SendGrid Random Duplicate Emails (Python Flask Heroku)

I'm currently working on an app that updates artists with times they are booked for events. Randomly the app will send duplicate emails a few times a day, over 90% of the time there are not duplicates.
Currently, there are 10+ emails that can be produced, but there is only one email send function. The duplicates occur across all emails which makes me think there is an issue with the email send function or the configuration of the web server to make multiple requests to sendgrid. PLEASE HELP ME FIND THE CAUSE OF THE DUPILCATES!
Stack:
Python (v3.10.6)
Flask (v2.2.2)
Heroku (buildstack-22)
Sendgrid python library (v6.9.7)
Email Send Function:
def send_email(to, subject, template, cc='None', attachment_location='None', attachment_name='None', private_email=False, **kwargs):
## Remove Guest DJ Emails Here
guest_dj_list = get_list_of_guest_djs()
if to in guest_dj_list:
return None
message = Mail(
from_email=From('test#test.com', current_app.config['MAIL_ALIAS']),
to_emails=[to],
subject=subject,
html_content=render_template(template, **kwargs))
## cc management
if private_email == False:
cc_list = ['test#test.com']
if cc != 'None':
cc_list.append(cc)
message.cc = cc_list
if attachment_location != 'None':
with open(attachment_location, 'rb') as f:
data = f.read()
f.close()
encoded_file = base64.b64encode(data).decode()
attachedFile = Attachment(
FileContent(encoded_file),
FileName(attachment_name),
FileType('application/xlsx'),
Disposition('attachment')
)
message.attachment = attachedFile
try:
sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
response = sg.send(message)
# print(response.status_code)
# print(response.body)
# print(response.headers)
print(f'Complete: Email Sent to {to}')
except Exception as e:
print(e.message)
Heroku Procfile
web: gunicorn test_app.app:app --preload --log-level=debug
According to your comments, the emails are triggered by user actions. From the code you have shared there is nothing that would cause an email to send twice, so my guess is that users are causing the occasional double sending by submitting forms more than once.
To discover the root of this, I would first attempt to disable your forms after the first submit. You can do this with a bit of JavaScript, something like:
document.querySelectorAll('form').forEach(form => {
form.addEventListener('submit', (e) => {
// Prevent if already submitting
if (form.classList.contains('is-submitting')) {
e.preventDefault();
}
// Add class to hook our visual indicator on
form.classList.add('is-submitting');
});
});
This comes from this article which has a good discussion of the issue too.
Once you have done that, you should likely also log the various actions that cause an email to send and try to chase back through your system to find what could be causing double submissions. I would pay attention to other things like callbacks or background jobs that may be the culprit here too.

File not found when deployed on server, but works fine when app is tested locally

I've been creating a simple invoicing web app using Python and Flask. One of its functionalities is that it automatically generates the PDF of the invoice once and sends it to the designated email address. Here is the route and function for that specific part: (Which is the only route and function I'm running into errors with)
#forms.route("/sendasemail/<int:id>", methods=['GET'])
#login_required
def sendasemail(id):
order = Order.query.get(id)
products = order.products
rendered = render_template("invoice.html", order = order, products = products)
save_location = url_for('static', filename=f'Order_{id}.pdf')
pdf = pdfkit.from_string(rendered, save_location)
msg = EmailMessage()
msg['Subject'] = f"Order Form No. {id}"
msg['From'] = 'DESIGNATED EMAIL'
if current_user.username == 'USERNAME':
msg['To'] = 'DESIGNATED_EMAIL'
else:
msg['To'] = 'DESIGNATED_EMAIL'
msg.set_content(f"Hi, \n\nKindly find Order Form No.{id} attached to this email.\n\nRegards,\nShibam S.P. Trading")
with open(f'Order_{id}.pdf', 'rb') as f:
file_data = f.read()
file_name = f.name
msg.add_attachment(file_data, maintype='application', subtype='octet-stream', filename=file_name)
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
smtp.login('DESIGNATED_EMAIL', 'DESIGNATED_PASSWORD')
smtp.send_message(msg)
os.unlink(f'Order_{id}.pdf')
flash("Email sent!", "success")
return render_template('show_data.html', order = order, products = products)
This snippet of code depends on the wkhtmltopdf and PDFKit libraries to function. When I run this code locally, it works perfectly fine. However, when I try running the same function within the application deployed on a production server, it throws an Internal Server Error. I think it might be because the PDF file, once created, cannot be found by the program due to some directory restructuring that I'm missing out on or plainly don't understand.
In the save_location = url_for('static', filename=f'Order_{id}.pdf') you create an absolute URL path, e.g. /static/Order_1.pdf instead of a local relative file path, like Order_1.pdf. But later in the script you use the local file path.
So the quick fix is just to use the local file path for save_location as well:
save_location = f'Order_{id}.pdf'
This assumes that the root directory of the Flask app is writable by the process. If not, you have to use a temporary directory, e.g. /tmp/ or an equivalent on your production server and update each file path in the script accordingly.

Django-herald: Can't send mails with attachments

Anyone using Django herald for sending notifications?
I've been struggling for days to make it work but the lack of documentation and silent failures made it impossible to debug the issues. It seems that the mails are not being sent if I include an attachment in it.
from herald.base import EmailNotification
def sendMail():
SendThisMail(user, my_modal).send(user=my_user) # creates an error on this line as the file object is closed and inaccessible.
#registry.register_decorator()
class SendThisMail(SomeBaseClass, EmailNotification):
def __init__(self, user, my_modal: models.MyModal):
super().__init__(user, my_modal)
self.subject = "abc"
file = open('.staticfiles/assets/some.pdf', 'rb')
self.attachments = [('attachment_1', File(file))]
self.context = {
**self.context,
'subject': self.subject,
'attachment': self.attachments,
}
self.to_emails = [user.email]
What's wrong with it?
From the project docs:
Each attachment in the list can be one of the following:
A tuple which consists of the filename, the raw attachment data, and the mimetype. It is up to you to get the attachment data
So the relevant parts of your code code should be something like:
data = open('.staticfiles/assets/some.pdf', 'rb').read()
self.attachments = [('attachment_1', data, 'application/pdf')]

Unable to send POST request from postman to API made using Flask

Hi I am new to writing web APIs in python. And my understanding of REST is limited
I have a simple Flask API that takes in a python dict {'pdf':pdf_as_bytes, 'filename':string}
The below is my server script:
#app.route("/predict", methods=["POST"])
def predict():
data = {"success": False}
if flask.request.method == "POST":
pdf = flask.request.files["pdf"].read()
filename = flask.request.files["filename"].read().decode("utf-8")
assert isinstance(filename, str), "Got {}".format(type(filename))
assert isinstance(pdf, bytes), "Got {}".format(type(pdf))
# further processing happens and returns a json
This works as intended when I write a python client as follows:
import requests
import os
ip = "localhost"
port = 8605
url = "http://{}:{}/predict".format(ip,port)
path_to_pdf = "./617339931.pdf"
with open(path_to_pdf, "rb") as f:
pdf = f.read() # pdf is a bytes
# the payload must have the following fields: "pdf": bytes, "filename": string object
payload = {"pdf":pdf,"filename":os.path.basename(path_to_pdf).split(".")[0]}
# post request
result = requests.post(url=url, files=payload).json()
# the resulting json always has a field called as success, which returns True or False accordingly
if result["success"] == True:
print(result["data"].keys())
But, When I send a request using Postman I get a 400 Error! Below is the screen shot of the error
I don't understand. How can I change my server code so that it works with Postman and also Python client programs
I just did the same thing, and I think it's because of the double quotes you are putting in key and value, try to take them out.

Cannot add file as inline attachment python flask using CID

I have spent hours searching for a solution to no avail. I am trying to include some images in automatically generated emails, and have come up against some problems. I Cannot include the actual url as gmail blocks the images totally, so I am trying to send as attachments and then use Cids to reference the attachments. The issue is I haven't found a way to do this. Any help would be ace.
I am running python 3.6 on with Apache2 on an Ubuntu server. I have tried encoding images in base64 but that didnt work at all. the images in the email simply didnt show up.
def createVoucher(email, expiry):
voucherId = str(uuid.uuid4())
email = email
value = 1
expiryDate = expiry
redeemed = 1
connection = mysql.get_db()
cursor = connection.cursor()
cursor.execute("INSERT INTO vouchers (VoucherID, Value, ExpiryDate, Redeemed, Email) VALUES (%s,%s,%s,%s,%s)", (voucherId, value, expiryDate, redeemed, email))
msgBody = render_template('admin/eVoucherEmail.html', voucherId=voucherId, expiry=expiry)
msg = Message('New Sunday Funday eVoucher Received', sender = MAIL_USERNAME, recipients = [email])
msg.html = msgBody
with app.open_resource("static/img/Facebook.jpg") as fp:
msg.attach("Facebook.jpg", "image/jpg", fp.read())
mail.send(msg)
connection.commit()
So the code posted works fine attaching the file, it's just assigning a content id that I can use where I am struggling.
Here's how I insert an inline jpg using flask_mail.
versions: python=3.7.6, flask=1.1.2, and flask-mail=0.9.1
The "disposition" in message.attach needs to be "inline", and the html for the img should contain "cid:my_cid" as the src.
import uuid
from flask import Flask
from flask_mail import Mail, Message
from pathlib import Path
app = Flask(__name__)
app.config.update(
MAIL_SERVER='smtp.gmail.com', MAIL_PORT=465, MAIL_USE_SSL=True,
MAIL_USERNAME="myfakeemail54858939#gmail.com", MAIL_PASSWORD="myfakepw54858939")
flask_mail = Mail(app)
def test_send_voucher():
to_addr = "myfakeemail54858939#gmail.com"
expiry = "29 February"
voucher_png_path = Path(__file__).parent / "static/Facebook.jpg"
sendVoucherEmail(app, to_addr, expiry, voucher_png_path)
def sendVoucherEmail(app: Flask, to_addr: str, expiry: str, voucher_png_path: Path):
voucher_id = str(uuid.uuid4())
html = f"""<html><head></head><body>
<p>Congratulations on your voucher!<br>Your voucher code is {voucher_id}.
The offer expires on {expiry}.<br>
<img src="cid:voucher_png" width=200>
</p></body></html>"""
with app.app_context():
mail = Mail(app)
message: Message = Message(subject="my subject", sender="myfakeemail54858939#gmail.com",
recipients=[to_addr], html=html)
with app.open_resource(voucher_png_path) as fp:
message.attach(filename="myfilename.png", content_type="image/png", data=fp.read(),
disposition="inline", headers=[['Content-ID', '<voucher_png>']])
mail.send(message)
This example successfully sent an email via gmail, with an inline image "Facebook.jpg," as in the original question.
screenshot of received email

Categories

Resources