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.
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
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 use the library pywin32 to write Outlook e-mails in Python. I have a dataframe that has some recipients' emails and attachment paths. I'd like to know how to add all attachments under each unique recipient?
print(my_df)
Receiver_Email Attachment_Path HTMLBody
john.doe#gmail.com C:/Users/Desktop/Attachment1.pdf HTMLBODY_1
john.doe#gmail.com C:/Users/Desktop/Attachment2.pdf HTMLBODY_1
john.doe#gmail.com C:/Users/Desktop/Attachment3.pdf HTMLBODY_1
sean.smith#hotmail.com C:/Users/Desktop/Attachment11.pdf HTMLBODY_2
sean.smith#hotmail.com C:/Users/Desktop/Attachment22.pdf HTMLBODY_2
sean.smith#hotmail.com C:/Users/Desktop/Attachment33.pdf HTMLBODY_2
My coding is showing below:
for index, row in my_df.iterrows():
outlook = client.Dispatch('Outlook.Application')
mail = outlook.CreateItem(0)
mail.To = row['Receiver_Email']
mail.Subject = 'Greetings from Here!'
mail.Body = 'Please find your attachment(s)'
mail.Attachments.Add(row['Attachment_Path'])
mail.HTMLBody = row['HTMLBODY']
mail.Send()
However, this would send 3 emails to john.doe#gmail.com and 3 emails to sean.smith#hotmail.com. My expected outputs would be sending 2 emails total (1 for john and 1 for sean) with 3 attachments each.
Is there a way to do that? Thank you for the help!
Do a groupby on the email addresses, which creates unique dataframes, then iterate on the mail attachments. Something like this should work:
GB = my_df.groupby("Receiver_Email")
for ID , DF in GB:
outlook = client.Dispatch('Outlook.Application')
mail = outlook.CreateItem(0)
mail.To = row['Receiver_Email']
mail.Subject = 'Greetings from Here!'
mail.Body = 'Please find your attachment(s)'
for id , row in DF.iterrows():
mail.Attachments.Add(row['Attachment_Path'])
mail.Send()
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)
I am searching an item in outlook through subject , want to do replay to all adding 3 ids in CC.
Easily able to add more recipients in To but not in CC please help
import win32com.client
outlook=win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
inbox= outlook.Folders.Item(1).Folders['Inbox']
messages = inbox.Items
message = messages.GetLast()
count=0
for i, message in enumerate(messages):
# Search Mail
# if message.subject=='Search Filter by Subject':
rplyall=message.ReplyAll()
rplyall.Recipients.Add('hitesh.kumar#bhartiaxa.com') # Sender of the mail
rplyall.Recipients.CC('one.more#abc.com') # Trying to do this
rplyall.Copy('one.more#abc.com')
rplyall.Body='Testing reply all'
rplyall.Subject = 'Subject Reply to all 2'
rplyall.Send()
You can add CC recipients by editing the CC property directly or updating the Type property of the returned from the returned Recipient object from Recipients.Add
Updating the recipient type
newCC = rplyall.Recipients.Add('one.more#abc.com')
newCC.Type = 2 #2 = olCC
Changing the CC property
rplyall.CC = 'one.more#abc.com;two.more#abc.com'
https://learn.microsoft.com/en-us/office/vba/api/outlook.recipients
Correcting the answer to my own question post getting the answer from Mike in above post.
import win32com.client
outlook=win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
inbox= outlook.Folders.Item(1).Folders['Inbox']
messages = inbox.Items
message = messages.GetFirst()
count=0
for i, message in enumerate(messages):
print(message.subject)
rplyall = message.ReplyAll()
rplyall.Recipients.Add('hitesh#abc.com') # Sender of the mail
rplyall.CC = 'deepak#abc.com'
rplyall.Body = 'Testing reply all'
rplyall.Subject = 'Subject Reply to all TEST'
rplyall.Send()