I've created a function in Python which will build an email in html format and send it anonymously through an Exchange server which accepts anonymous email sends.
However, no one in the CC list gets a copy and only the FIRST person in the TO list gets a copy. I've tried 'comma' and 'semicolon' as the email separator but neither works. Curiously, in the email received ALL of the email addresses can be seen (and in the correct format) despite the fact that only one person (the person listed first in the TO list) gets a copy.
This is my code
def send_email(sender, subject, sendAsDraft = True): # sendAsDraft not implemented
global toEmail
global ccEmail
# create the email message
htmlFile = open(saveFolder + '\\' + htmlReportBaseName + '.html',encoding = 'utf-8')
message = htmlFile.read().replace('\n', '')
message = message.replace(chr(8217), "'") # converts a strange single quote into the standard one
message = message.replace("'",''') # makes a single quote html friendly
htmlFile.close()
# get the email body text
emailBody = open('emailBody.txt')
bodyText = emailBody.read()
emailBody.close()
now = dt.datetime.today().replace(second = 0, microsecond = 0)
timeStr = returnReadableDate(now) # returns the date as a string in the 'normal' reading format
# insert the current date/time into the marker in the email body
bodyText = bodyText.replace('xxxx', timeStr)
# insert the email body text into the HTML
message = message.replace('xxxx', bodyText)
msg = MIMEMultipart('Report Email')
msg['Subject'] = subject
msg['From'] = sender
msg['To'] = '; '.join(toEmail)
msg['Cc'] = '; '.join(ccEmail)
msg.add_header('Content-Type','text/html')
msg.set_payload(message)
# complete the email send
message = ''
try:
smtpObj = sl.SMTP('1.2.3.4:5') # obviously IP address anonymised
smtpObj.sendmail(sender, '; '.join(toEmail), msg.as_string())
message = 'Email successfully sent'
except:
message = 'Email could not be sent due to an error'
finally:
# write the outcome to the Instance log
global InstanceLog
# write the log
log(InstanceLog, message)
# close object and exit function
smtpObj.quit()
Is it possible that the issue is at the email server? That where an un-authenticated email can only be sent to one person? That would seem strange in one way, and make sense in another.
Thanks all.
Seamie
Beware. send_message can use the headers of a message to build its recipient list, but sendmail requires a list of addresses or handle a string as a single recipicient address.
And the headers should contain comma instead of semicolons.
So you should have:
...
msg['To'] = ' '.join(toEmail)
msg['Cc'] = ','.join(ccEmail)
...
and later either:
smtpObj.sendmail(sender, toEmail + ccEmail, msg.as_string())
or:
smtpObj.send_message(msg)
Looking at the docs, this should be a comma seperated string:
msg['To'] = ', '.join(toEmail)
Related
I am using the gmail API for sending emails. This is the function I am using to create an email:
def createEmailNoAttachments(self, send_to_emails, subject_text, main_message_text, msgID=None, inReplyTo=None, html=False):
try:
fromEmail = self.from_email_total
if (type(main_message_text) == list) or (type(main_message_text) == tuple):
total_text = ""
for line in main_message_text:
if type(line) == str:
total_text = total_text + line + "\n"
main_message_text = total_text
mimeMessage = MIMEMultipart()
if type(send_to_emails) == list:
mimeMessage['to'] = ", ".join(send_to_emails)
else:
mimeMessage['to'] = send_to_emails
mimeMessage['from'] = fromEmail
mimeMessage['subject'] = subject_text
if inReplyTo != None:
mimeMessage["In-Reply-To"] = inReplyTo
mimeMessage["References"] = inReplyTo
if msgID != None:
mimeMessage['Message-ID'] = msgID
if html:
msg= MIMEText(main_message_text, 'html')
else:
msg= MIMEText(main_message_text, "plain")
mimeMessage.attach(msg)
raw = base64.urlsafe_b64encode(mimeMessage.as_bytes())
raw = raw.decode()
body = {'raw': raw}
return body
except:
self.myLogger.error("An error was encountered while attempting to create gmail email")
tb = traceback.format_exc()
self.myLogger.exception(tb)
return False
I then send the email with
def gmailAPISendEmail(self, email_message, deleteFromInbox=False, userID="me"):
try:
self.refreshGmailService()
self.myLogger.info("Attempting to send email message")
request = self.service.users().messages().send(userId=userID, body=email_message)
response = self.executeGmailAPI_withretry(request=request)
if response == False:
self.myLogger.error("An error occurred in executeGmailAPI_withretry while trying to send email message")
return False
else:
try:
responseID = str(response['id'])
if deleteFromInbox == True:
delete_result = self.deleteEmail(emailID=responseID)
if delete_result == False:
self.myLogger.error(f"An error occurred in deleteEmail with responseID ({responseID})")
self.myLogger.info("Successfully sent email message with ID (" + responseID +")")
return responseID
except:
return "CouldNotExtractID"
except:
self.myLogger.error("An error occurred in gmailAPISendEmail")
tb = traceback.format_exc()
self.myLogger.exception(tb)
return False
The problem I am noticing is that similar emails with the same email subject and same sender and recipient are not being grouped under one thread when sent using the above functions (the gmail API). In the recipient email inbox, each individual email appears separately, even though they have the same subject and same sender and receiver email addresses.
I believe the next step would be to manually assign threadid. However, this is far from ideal, as I would need to incorporate some logic to do all of this.
Before when I used SMTP, I didn't have to set a threadid or anything like that. When sending emails with SMTP, emails would automatically group together based on same email subject and same recipient.
Nothing changed between before and now, except that I am sending the same emails with the gmail API in place of SMTP.
Why doesn't the gmail API behave similar to SMTP, even though I am creating the email very similarly? Is there something I can do to have Gmail inboxes to group the emails just like SMTP, without having to build logic and keeping track of threadids?
From sending
If you're trying to send a reply and want the email to thread, make sure that:
The Subject headers match
The References and In-Reply-To headers follow the RFC 2822 standard.
For information on sending a message from a draft, see Creating Drafts.
So you need the message id of the original message you are replying to. This is not the same as thread id.
message['In-Reply-To'] = message_id
message['References'] = message_id
I want to group recipients by reason and sent out tailored emails for each reason. The data looks like this.
Email reason
0 one#outlook.com Address
1 two#outlook.com Address
2 three#outlook.com Phone Number
3 one#outlook.com Postcode
import Pandas as pd
data = { 'Email': ['one#outlook.com','two#outlook.com','three#outlook.com','one#outlook.com'],
'reason': ['Address','Address', 'Phone Number','Postcode']}
df = pd.DataFrame(data)
df
This is how I started.
for e in df['reason'].unique():
print(f"Reason: {e}")
print(df[df['reason'] == e]['Email'].to_list())
print('\n')
Reason: Address
['one#outlook.com', 'two#outlook.com']
Reason: Phone Number
['three#outlook.com']
Reason: Postcode
['one#outlook.com']
Not sure how to use this list below.
message = "Update"
email_subject = "Test"
recipients = e
# sending email function
def send_mail(text, subject, recipients):
o = win32com.client.Dispatch("Outlook.Application")
Msg = o.CreateItem(0)
Msg.to = ''
Msg.BCC = "; ".join(recipients)
Msg.Subject = subject
Msg.HTMLBody = text
Msg.Send()
# sending email
send_mail(message,email_subject,recipients)
How to set 3(or more) different email body's and loop around the grouped list to sent separate emails for each reason?
You need to repeat the following code in the loop:
Msg = o.CreateItem(0)
Msg.to = recipient;
Msg.Subject = subject
Msg.HTMLBody = text
Msg.Send()
where recipient is an email address of a single recipient.
You may find the Using Visual Basic for Applications in Outlook article helpful.
This question already has answers here:
How to send email to multiple recipients using python smtplib?
(18 answers)
Closed 1 year ago.
I have the below code to send out emails specific to certain addresses all in a pandas dataframe. The type says pandas.series. How can I be able to send each list with multiple addresses? Some have upto 8 email addresses separated by a semi-colon(;). Only possibility of sending out is to a single address. From the below dataframe, I'm able to send emails specific to the IDs to TestEmail values. Nothing works for Emails. I get the below error :
SMTPRecipientsRefused: {'': (421, b'4.7.0 Too many protocol errors (6) on this connection, closing transmission channel.')}
Dataframe df
IDs CompanyNames Emails TestEmail
2003 XXX xx#gmail.com;yy#ryt.com;dd#xx.com;ii#dd.com kk#kk.com
2004 XXXYY xx#gmail.com;yy#ryt.com;dd#xx.com kk#kk.com
2005 XXTTNN xx#gmail.com;yy#ryt.com;dd#xx.com;ii#dd.com kk#kk.com
2006 BBOOLL xx#gmail.com;yy#ryt.com kk#kk.com
The code is below. To the single address, I'm able to send attachments with names matching df['IDs'] values to the corresponding email address.
import smtplib, ssl
from email.message import EmailMessage
import getpass
ids = df['IDs']
emails_to = df['Emails'] #Pandas column with email addresses split by semi-colon(;)
namesofcompanies = df["CompanyNames"]
sendfrom = df["SenderList"]
date_7days = (datetime.now() + timedelta(days=7)).strftime('%d/%m/%Y')
date_14days = (datetime.now() + timedelta(days=13)).strftime('%d/%m/%Y')
email_pass = input() #Office 365 password
context=ssl.create_default_context()
for i in range(len(emails_to)): # iterate through the records
# for every record get the name and the email addresses
ID = str(ids[i])
Emaitstosendto = emails_to[i]
companynames = namesofcompanies[i]
tosendfrom = sendfrom[i]
if my_files_dict.get(ID): #Looks for attachments in the same folder with same name as the corresponding record
smtp_ssl_host = 'smtp.office365.com'
smtp_ssl_port = 587
email_login = "xxx#xxx.com" #Office 365 email
email_from = tosendfrom
email_to = Emaitstosendto
msg = MIMEMultipart()
msg['Subject'] = "Received Emails between "+date_7days+" - "+date_14days
msg['From'] = email_from
msg['To'] = email_to
msg['X-Priority'] = '2'
text = ("XXXX,\n"
f"xxxxxx\n\n")
msg.attach(MIMEText(text))
filename = my_files_dict.get(ID)#Files in the folder matching the ID
fo = open(filename,'rb')
s2 = smtplib.SMTP(smtp_ssl_host, smtp_ssl_port)
s2.starttls(context=context)
s2.login(email_login, email_pass)
attachment = email.mime.application.MIMEApplication(fo.read(),_subtype="xlsx")
fo.close()
attachment.add_header('Content-Disposition','attachment',filename=filename)
msg.attach(attachment)
s2.send_message(msg)
s2.quit()
Taken from this answer: How to send email to multiple recipients using python smtplib?
This is the line you have to change:
First split the email_to into a list of different emails and then join using ", "
msg['To'] = ", ".join(email_to.split(";"))
I am trying to write a very basic email sending script. Here is my code ..
import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg.set_content("Test message.")
msg['Subject'] = "Test Subject!!!"
msg['From'] = "myemail#gmail.com"
email_list = ["xyz#gmail.com", "abc#gmail.com"]
for email in email_list:
msg['To'] = email
server = smtplib.SMTP(host='smtp.gmail.com', port=587)
server.starttls()
server.login("myemail#gmail.com", "mypassword")
server.send_message(msg)
server.quit()
the script should send mail to multiple recipients so, I need to change the msg['To'] field when iterating through loop But I get the following error in traceback bellow.
Traceback (most recent call last):
File "exp.py", line 66, in <module>
msg['To'] = email
File "/usr/lib/python3.8/email/message.py", line 407, in __setitem__
raise ValueError("There may be at most {} {} headers "
ValueError: There may be at most 1 To headers in a message
How do I solve ? Please help. Thank you..
Clean the 'To' property of the message.
for email in email_list:
msg['To'] = email
server = smtplib.SMTP(host='smtp.gmail.com', port=587)
server.starttls()
server.login("myemail#gmail.com", "mypassword")
server.send_message(msg)
server.quit()
del msg['To]
Below is the code that throws the exception: (\Python385\Lib\email\message.py)
def __setitem__(self, name, val):
"""Set the value of a header.
Note: this does not overwrite an existing header with the same field
name. Use __delitem__() first to delete any existing headers.
"""
max_count = self.policy.header_max_count(name)
if max_count:
lname = name.lower()
found = 0
for k, v in self._headers:
if k.lower() == lname:
found += 1
if found >= max_count:
raise ValueError("There may be at most {} {} headers "
"in a message".format(max_count, name))
self._headers.append(self.policy.header_store_parse(name, val))
Not knowing the inner working of the EmailMessage class, what I can assume is that every call to __setitem__ writes to the head of the email message, so by calling it in a loop, the header is being written multiple times, what I'd recommend is that you make an email message for every email you'll send, but create only one server:
server = smtplib.SMTP(host='smtp.gmail.com', port=587)
server.starttls()
server.login("myemail#gmail.com", "mypassword")
email_list = ["xyz#gmail.com", "abc#gmail.com"]
for email in email_list:
msg = EmailMessage()
msg.set_content("Test message.")
msg['Subject'] = "Test Subject!!!"
msg['From'] = "myemail#gmail.com"
msg['To'] = email
server.send_message(msg)
server.quit()
Only if you need for the messages to be sent separately. If you want to send the same message to everyone at the same time you could do something like
msg['To'] = ', '.join(email_list)
If you have a list of addresses, and some of them include a name/title, then I think this is the correct way to do it. Please note that parseaddr + formataddr pair may not be needed, but parseaddr can correct some malformed recipients.
from email.header import Charset
from email.message import EmailMessage, MIMEPart
from email.utils import formataddr, parseaddr
test_recipients = [
"Mr. John Doe <johndoe#example.com>",
"Mr. Jane Doe <janedoe#example.com>",
"somebody#example.com"
]
to_header= []
for raw_address in (test_recipients):
# Parse and recreate
title, email = parseaddr(raw_address)
if title and email:
to_header.append(f"{title} <{email}>")
elif email:
to_header.append(email)
# Encode after join
message.add_header("To", Charset("utf-8").header_encode(", ".join(to_header)))
if you just delete
server.quit() from loop
and add
del msg['to']
then there is no error
I already programmed a function which sends mails with atachments, images on text and other things, but now I need the function to use de Cc (Carbon Copy) function in order to send copies to different emails.
I have done some changes on the function and it works but not as I want.
THe email is sent to the address ("toaddr") and the mail shows that there are other emails added as Cc("tocc") emails, but the Cc emails do not recieve the email.
To be more clear (because I think I am not being very clear) here is an example:
Sender: from#hotmail.com
Receiver: to#hotmail.com
Copied: cc#hotmail.com
to#hotmail.com receives the email and can see that cc#hotmail.com is copied on it.
cc#hotmail.com does not get the email.
if to#hotmail.com reply to all the email, THEN cc#hotmail gets the email.
Can anyone help me telling me what do I need to change on the function?? I guees the problem is with the server.sendmail() function
This is my function:
def enviarCorreo(fromaddr, toaddr, tocc, subject, text, file, imagenes):
msg = MIMEMultipart('mixed')
msg['From'] = fromaddr
msg['To'] = ','.join(toaddr)
msg['Cc'] = ','.join(tocc) # <-- I added this
msg['Subject'] = subject
msg.attach(MIMEText(text,'HTML'))
#Attached Images--------------
if imagenes:
imagenes = imagenes.split('--')
for i in range(len(imagenes)):
adjuntoImagen = MIMEBase('application', "octet-stream")
adjuntoImagen.set_payload(open(imagenes[i], "rb").read())
encode_base64(adjuntoImagen)
anexoImagen = os.path.basename(imagenes[i])
adjuntoImagen.add_header('Content-Disposition', 'attachment; filename= "%s"' % anexoImagen)
adjuntoImagen.add_header('Content-ID','<imagen_%s>' % (i+1))
msg.attach(adjuntoImagen)
#Files Attached ---------------
if file:
file = file.split('--')
for i in range(len(file)):
adjunto = MIMEBase('application', "octet-stream")
adjunto.set_payload(open(file[i], "rb").read())
encode_base64(adjunto)
anexo = os.path.basename(file[i])
adjunto.add_header('Content-Disposition', 'attachment; filename= "%s"' % anexo)
msg.attach(adjunto)
#Send ---------------------
server = smtplib.SMTP('localhost')
server.set_debuglevel(1)
server.sendmail(fromaddr,[toaddr,tocc], msg.as_string()) #<-- I modify this with the tocc
server.quit()
return
In your sendmail call, you're passing [toaddr, tocc] which is a list of lists, have you tried passing toaddr + tocc instead?