Cannot add file as inline attachment python flask using CID - python

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

Related

How to separate flask app.route order of events

I have a small program that opens up an outlook email when a button is clicked on an html page. The problem is, I don't know the order of my app.route objects and the posted data from my html page.
My html (openemailJS.html):
my flask app:
import win32com.client
from flask import Flask, render_template, jsonify, json, url_for, request, redirect
import pythoncom, time, os, sys
app = Flask(__name__)
#app.route('/',methods = ['POST', 'GET'])
def index():
if request.method == 'POST':
emailid = request.args.get('nm')
time.sleep(1)
pythoncom.CoInitialize()
outlook = win32com.client.dynamic.Dispatch("Outlook.Application").GetNameSpace('MAPI')
inbox = outlook.GetDefaultFolder(6)
sentbox = outlook.GetDefaultFolder(5)
all_sentbox = sentbox.Items
all_inbox = inbox.Items
tryopen = outlook.GetItemFromID(emailid)
tryopen.display()
return render_template('openemailJS.html')
print(emailid)
else:
time.sleep(1)
emailid = request.args.get('nm')
#emailid = request.form.get('nm')
time.sleep(1)
pythoncom.CoInitialize()
outlook = win32com.client.dynamic.Dispatch("Outlook.Application").GetNameSpace('MAPI')
inbox = outlook.GetDefaultFolder(6)
sentbox = outlook.GetDefaultFolder(5)
all_sentbox = sentbox.Items
all_inbox = inbox.Items
tryopen = outlook.GetItemFromID(emailid)
tryopen.display()
print(user)
return render_template('openemailJS.html')
I think I need to separate the ' return render_template('openemailJS.html') 'into its own app.route('/something here/') but I don't know how to do that.
Currently, when I run it how it is, the emailid does not get posted before the def index(): runs, causing a 'parameter not found' where it should be. If I comment out everything but the 'return render_template', run the program, and then uncommment it out and click the button, then it gets posted as it should.
I added an if statement to retrieve the data only if the form is posted. I still think it makes sense to render the template first before doing anything, but I don't know.

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 don't send email message with attached zip file

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
...

Can I rename an attachment of a received email via exchangelib?

I want to rename the attachments of some files I receive on an Exchange server. Is this possible?
What I've tried
from exchangelib import ServiceAccount, Configuration, Account, DELEGATE
from exchangelib import FileAttachment
from config import cfg
# Login
credentials = ServiceAccount(username=cfg['imap_user'],
password=cfg['imap_password'])
config = Configuration(server=cfg['imap_server'], credentials=credentials)
account = Account(primary_smtp_address=cfg['smtp_address'], config=config,
autodiscover=False, access_type=DELEGATE)
# Go through all emails, to find the example email
latest_mails = account.inbox.filter()
for msg in latest_mails:
for attachment in msg.attachments:
if attachment.name == 'numbers-test-document.pdf':
print("Rename the example attachment")
# does not work :-( - but no error either
attachment = FileAttachment(name='new-name.pdf',
content=attachment.content)
msg.attachments = [attachment]
msg.save()
print("#" * 80)
I don't get an error message. But it doesn't rename either. The code executes (I see Rename the example attachment), but it doesn't do so. How can this be done with exchangelib?
I have to take msg.detach and msg.attach
Complete working script
from exchangelib import ServiceAccount, Configuration, Account, DELEGATE
from exchangelib import FileAttachment
from config import cfg
def rename_attachment(msg, old_name, new_name):
"""
Rename an attachment `old_name` to `new_name`.
Parameters
----------
msg : Message object
old_name : str
new_name : str
Returns
-------
renames_executed : int
"""
renames_executed = 0
for attachment in msg.attachments:
if attachment.name == old_name:
renames_executed += 1
new_attachment = FileAttachment(name=new_name,
content=attachment.content)
msg.detach(attachment)
msg.attach(new_attachment)
msg.save()
return renames_executed
# Login
credentials = ServiceAccount(username=cfg['imap_user'],
password=cfg['imap_password'])
config = Configuration(server=cfg['imap_server'], credentials=credentials)
account = Account(primary_smtp_address=cfg['smtp_address'], config=config,
autodiscover=False, access_type=DELEGATE)
# Go through all emails, to find the example email
latest_mails = account.inbox.filter()
for msg in latest_mails:
rename_attachment(msg, 'numbers-test-document.pdf', 'new-name.pdf')
print("#" * 80)

Adding multiple recipents EWS API

I'm trying to send a simple email message to multiple recipients using EWS API via Python but I can't send to more than single address at a time.
import clr
clr.AddReferenceToFileAndPath("C:\\Microsoft\\Exchange\\Web Services\\2.1\\Microsoft.Exchange.WebServices.dll")
from Microsoft.Exchange.WebServices.Data import *
def main():
ex_service = ExchangeService(ExchangeVersion.Exchange2010)
ex_service.UseDefaultCredentials = True
ex_service.AutodiscoverUrl("me#companycom", redirect_url_validation_callback)
email = EmailMessage(ex_service)
email.ToRecipients.Add('r1#company.com')
email.Subject = "New TIP Report"
email.Body = '''A new TIP report has been published.
Please visit https://google.com and login to complete the form.'''
email.Send()
def redirect_url_validation_callback(redirect_url):
redirect_uri = Uri(redirect_url)
return redirect_uri.Scheme == "https"
if __name__ == "__main__":
main()
I read that I need to Mime Content properties, but I was not able to succeed. Any clue how to add multiple recipients using EWS API?
Just add a new line
email.ToRecipients.Add('r1#company.com')

Categories

Resources