I'm working with a python script to dynamically send HTML emails to 400 users using Amazon Simple Email Service. The script reads the emails addresses from an Excel file with openpyxl.
But the script is randomly crashing around 260 to 280 users while sending. My current workaround is to set the Excel file at the position where the script previously crashed then run it again. There is my code:
import email.utils
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
msg = MIMEMultipart('alternative')
msg['From'] = sender_email
msg['Subject'] = 'Your email subject'
context = ssl.create_default_context()
server = smtplib.SMTP_SSL(smtp_server, port, context=context)
server.login(smtp_login, password)
for dest_email in email_list:
message_html = '<html><head><title></title></head><body>Dynamic content here...</body></html>'
message_plain = 'Content here...'
part1 = MIMEText(message_plain, 'plain')
part2 = MIMEText(message_html, 'html')
msg.attach(part1)
msg.attach(part2)
server.sendmail(sender_email, dest_email, msg.as_string())
server.close()
I tried catching exceptions during execution but I'm not getting much details about what's wrong here:
Exception: (554, b'Transaction failed: MIME section count exceeds
500.')
msg.items(): [('Content-Type', 'multipart/alternative;
boundary="===============2309883816392967535=="'), ('MIME-Version',
'1.0'), ('From', 'Sender sender_email#address.com'), ('Subject',
"Your subject")]
I've been looking around for some time but can't get my hands on the issue. Could you please help me figure this out? What am I doing wrong? Is there a header I need to reset after each successful email sent? Let me know if you need additional details. Thanks.
From the top of my head I would say that only the server.sendmail() part should be inside the loop. Right now you seem to be re-sending a single e-mail (msg), which gets new parts attached in each iteration, and that's what the error message says too.
Alternatively, if that does not work: re-create the entire object in the loop, so move the block starting with msg = ... inside.
Related
This question already has an answer here:
How to send different content email to different recipient in Python?
(1 answer)
Closed 11 months ago.
I want my task to be 1 by 1 not bulk so I'm sending from list of emails. I want it sending to first one then repeats task and sends to second one. The code already works properly but it sends as bulk all emails together so when recipient gets the email it's like all the emails list included in header. That's why I want it to send it one by one from my list.
import smtplib, ssl
from time import strftime
from email.header import Header
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from colorama import *
from datetime import datetime
context = ssl.create_default_context()
def get_contacts(filename):
emails = []
with open(filename, mode='r', encoding='utf-8') as contacts_file:
for a_contact in contacts_file:
emails.append(a_contact.split()[0])
return emails
mails = get_contacts('smtp/contacts.txt')
smtp_user = 'mYsmtp#smtp.com'
def smtp(smtp_server, port, user, password):
context = ssl.create_default_context()
with smtplib.SMTP_SSL(smtp_server, port) as server:
try:
message = MIMEMultipart('alternative')
message['Subject'] = 'TESTING'
message['From'] = str(Header(f'INFO <{smtp_user}>'))
message['To'] = ', '.join(mails)
myfile = open('smtp/letter.txt', 'r')
data = myfile.read()
part = MIMEText(data, 'html')
message.attach(part)
now = datetime.now()
current_time = now.strftime("%H:%M:%S")
server.login(user, password)
server.sendmail(user, mails, message.as_string())
for email in mails:
print('\n',email + ''' SENT! ''')
except smtplib.SMTPAuthenticationError or smtplib.SMTPConnectError :
print('Dead SMTP')
Response:
You just have to move put inside your for email in mails:
for email in mails:
print('\n',email + ''' SENT! ''')
message['To'] = email
server.sendmail(user, mails, message.as_string())
Remove from your original script:
message['To'] = ', '.join(mails)
and
server.sendmail(user, mails, message.as_string())
Notes:
Next time consider undersanding your script before posting your question as this is a very simple thing to do and the answer is already all over the internet.
your try have too many instructions you should only have the instruction that may fail with the handled errors inside it, as the errors that you're handling are smtplib.SMTPAuthenticationError and smtplib.SMTPConnectError: server.login and server.sendmail
Do not post your entire script or big code chunks this is rude and you'll not getting appropriated answers this way.
Your code very explicitly adds all recipients to the To: header and then sends a single message.
As an aside, your code seems to be written for Python 3.5 or earlier. The email library was overhauled in 3.6 and is now quite a bit more versatile and logical. Probably throw away what you have and start over with the examples from the email documentation. Something like this, maybe:
from email.message import EmailMessage
from email.headerregistry import Address
...
mails = get_contacts('smtp/contacts.txt')
smtp_user = 'mYsmtp#smtp.com'
def generate_messages(recipients):
with open('smtp/letter.txt', 'r') as myfile:
data = myfile.read()
for recipient in recipients:
message = EmailMessage()
message['Subject'] = 'TESTING'
message['From'] = Address("INFO", *smtp_user.split("#"))
message['To'] = recipient
message.set_content(data, 'html')
yield message
def smtp(smtp_server, port, user, password, messages):
with smtplib.SMTP_SSL(smtp_server, port) as server:
server.login(user, password)
for message in messages:
now = datetime.now()
current_time = now.strftime("%H:%M:%S")
server.send_message(message)
smtp("smtp.example.com", 25, "me#example.com", "xyzzy", generate_messages(mails))
Untested, and I ripped out the exception handling because I'm probably unable to guess what sort of exception handling would make sense with this different flow.
The use of a generator to generate the messages is perhaps a bit confusing if you are new to Python. I'm thinking you want to minimize the number of separate login events to the SMTP server, and so the code basically has a callback to produce the next message from within the smtp function.
I can send the plain text but unable to send html text in html format.
import email, smtplib, ssl
import os
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
body = """
this is first mail by using python
"""
port_email = 587
smtp_server = "smtp.gmail.com"
password = "your password"
subject = "An email with attachment from Python"
sender_email = "sender#gmail.example.com"
receiver_email = "receiver#example.net"
# Create a multipart message and set headers
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = subject
message["Bcc"] = receiver_email # Recommended for mass emails
# Add body to email
message.attach(MIMEText(body, "plain"))
filename = "file name" # In same directory as script
with open(filename.html, 'r', encoding="utf-8") as attachment:
part1 = attachment.read()
part2 = MIMEText(part1, "html")
message.attach(part2)
text = message.as_string()
context = ssl.create_default_context()
with smtplib.SMTP_SSL(smtp_server, 465 , context=context) as server:
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, text)
This will send attach file but i want to see the html text in email body.
filename is content html table so code should send the html text which will automatic available in html body with html table.
Why are you passing a bogus body if that's not what you want?
Your code seems to be written for Python 3.5 or earlier. The email library was overhauled in 3.6 and is now quite a bit more versatile and logical. Probably throw away what you have and start over with the examples from the email documentation.
Here's a brief attempt.
from email.message import EmailMessage
...
message = EmailMessage()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = subject
# No point in using Bcc if the recipient is already in To:
with open(filename) as fp:
message.set_content(fp.read(), 'html')
# no need for a context if you are just using the default SSL
with smtplib.SMTP_SSL(smtp_server, 465) as server:
server.login(sender_email, password)
# Prefer the modern send_message method
server.send_message(message)
If you want to send a message in both plain text and HTML, the linked examples show you how to adapt the code to do that, but then really, the text/plain body part should actually contain a useful message, not just a placeholder.
As commented in the code, there is no reason to use Bcc: if you have already specified the recipient in the To: header. If you want to use Bcc: you will have to put something else in the To: header, commonly your own address or an address list like :undisclosed-recipients;
Tangentially, when opening a file, Python (or in fact the operating system) examines the user's current working directory, not the directory from which the Python script was loaded. Perhaps see also What exactly is current working directory?
Mime has a variety of formats. By default MIMEMultipart builds a multipart/mixed message, meaning a simple text body and a bunch of attachments.
When you want an HTML representation of the body, you want a multipart/alternative message:
...
message = MIMEMultipart('alternative')
...
But you are using the old compat32 API. Since Python 3.6 you'd better use email.message.EmailMessage...
I'm trying to send an HTML email, however, when I run this code, literally nothing happens. I sit and wait as the program never finishes. What did I do wrong?
import smtplib, ssl, getpass
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
port = 465
smtp_server = "smtp.gmail.com"
sender_email = "REDACTED"
receiver_email = "REDACTED"
password = getpass.getpass()
html = """
<html>
<head></head>
<body>
<h1>HEADER</h1>
<br>
body<br>
</body>
</html>
"""
msg = MIMEMultipart()
attach = MIMEText(html, 'html')
msg.attach(attach)
context = ssl.create_default_context()
with smtplib.SMTP_SSL(smtp_server, port, context=context) as server:
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, msg)
server.close()
The msg needs to be flattened to a string before passing it to the sendmail method:
server.sendmail(sender_email, receiver_email, str(msg))
Also, you'll probably want to set some headers on the msg object:
msg.add_header('From', sender_email)
msg.add_header('Subject', 'whatever')
Enabling the debug output for the SMTP object would help to track down these problems, try inserting this line above the server.login call:
server.set_debuglevel(1)
If you're going to be working with email though it may be worth looking in to the email.message.EmailMessage class.
I've found sending mail using gmails SMTP server hard and difficult to work out (The only way I have even gotten it to work is by using a temporary thing that lets me send emails through my email address and only works half the time)
Instead, I suggest sending your email through SendGrid, using their python API, or another service that offers a similar function (Not being biased just have used SendGrid before and found it very easy and reliable) and just send a request to that every time you want to send an email.
I know its a dodgy way to get it to work but its the only way I've been able to send emails through my Gmail account using python :)
(Id comment this as it isn't really an answer but a workaround however I don't have enough reputation)
Let's say I have a script that checks this one folder to see if there is anything in there that I want.
I'd like to have this script run somewhere in the background (this would be cooler) or execute every 2 hours or so and send me an email if there is anything found.
how can I do this?
So you essentially have two parts:
1) How to set up my a scheduled script?
Assuming you're on Windows you can follow the steps here: Schedule Python Script - Windows 7
to set up a scheduled script either through the gui or the command line.
The output of that script can be boolean in nature and prompt the execution of the second part of your question.
2) How to send an email?
For this you could try this code:
# Import smtplib for the actual sending function
import smtplib
# Import the email modules we'll need
from email.mime.text import MIMEText
# Open a plain text file for reading. For this example, assume that
# the text file contains only ASCII characters.
fp = open(textfile, 'rb')
# Create a text/plain message
msg = MIMEText(fp.read())
fp.close()
# me == the sender's email address
# you == the recipient's email address
msg['Subject'] = 'The contents of %s' % textfile
msg['From'] = me
msg['To'] = you
# Send the message via our own SMTP server, but don't include the
# envelope header.
s = smtplib.SMTP('localhost')
s.sendmail(me, [you], msg.as_string())
s.quit()
More information for this step can be found here: How to send an email with Python?
I am trying to run a python script at bootup which will take a ~10 second video on applying an external input (such as push button, IR sensor etc, and in our case an ultrasonic sensor), and then mail this video to specified email addresses using the SMTPlib library of Python.
All of this is working fine. However, when using this input multiple times, the Raspberry Pi sends multiple videos (taken by pushing the button in the past) to the email address instead of the one initiated by the last input. Hence, pushing the button 1 time would send 1 video; pushing it 2 times would send a new one along with the last one; pushing it 3 times would send a new one and the last two as well, and so on.
I even tried inserting an os.remove() right after the mail is sent in the python script. After running the program, running an ls shows the file is indeed deleted. Yet somehow, these deleted videos make its way into the email. Could it be it is stored somewhere in the memory when smtplib is used?
The script in mention is below :
from email.mime.multipart import MIMEMultipart
from email import encoders
from email.message import Message
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.text import MIMEText
emailfrom = "xyz#abc.com"
emailto = "xyz123#abc.com"
username = "xyz#abc.com"
password = "pass"
msg = MIMEMultipart()
msg["From"] = emailfrom
msg["To"] = emailto
msg["Subject"] = "Email Subject -- "
msg.preamble = "Email Body --"
while(True):
d=0
# Possibly the relevant section
d = pulse_d*17150
d= round(d, 2)
if(d<100):
with picamera.PiCamera() as camera:
camera.start_preview()
camera.start_recording('/home/pi/video.h264')
time.sleep(5)
camera.stop_recording()
camera.stop_preview()
time.sleep(5)
fileToSend = "/home/pi/video.h264"
ctype, encoding = mimetypes.guess_type(fileToSend)
if ctype is None or encoding is not None:
GPIO.output(test, True)
ctype = "application/octet-stream"
maintype, subtype = ctype.split("/", 1)
fp = open(fileToSend, "rb")
GPIO.output(test,False)
attachment = MIMEBase(maintype, subtype)
attachment.set_payload(fp.read())
fp.close()
encoders.encode_base64(attachment)
attachment.add_header("Content-Disposition", "attachment", filename=fileToSend)
msg.attach(attachment)
server = smtplib.SMTP("smtp.gmail.com:587")
server.starttls()
server.login(username,password)
server.sendmail(emailfrom, emailto, msg.as_string())
server.quit()
os.remove("/home/pi/video.h264")
I think you should create new message instead of reuse the old one.
Move the code where you create msg inside while loop
The problem is here:
msg.attach(attachment)
You attach files to the same message, but don't remove old attachments.