Python email - can only sendmail to one address - python

My SendMail function looks like this:
MY_SMTP = #I can't show it here
def SendMail(authentication_data, email_from, email_to_list, email_cc_list, email_subject, email_message, list_file)
user_name = authentication_data["name"]
user_password = authentication_data["password"]
msg = MIMEMultipart(email_message)
server = smtplib.SMTP(MY_SMTP)
server.starttls()
emails_list_string = ", ".join(email_to_list)
emails_cc_list_string = ", ".join(email_cc_list)
msg["From"] = email_from
msg["To"] = emails_list_string
msg["Cc"] = emails_cc_list_string
msg["Subject"] = "TEST"
msg["Date"] = email.Utils.formatdate()
msg.attach(MIMEText(email_message))
server.login(user_name, user_password)
server.sendmail(email_from, emails_list_string + ", " + emails_cc_list_string, msg.as_string())
server.quit()
When I use this function and I pass a list of e-mail addresses for email_to_list, it only sends an e-mail to the first element of the list. Passing any list for email_cc_list simply adds addresses to the Cc field in the e-mail, however I think it's purely cosmetic, as the people in the Cc don't see the e-mail when I send it with this function. Basically only the first address in email_to_list ever receives the e-mail.

The documentation says:
The required arguments are an RFC 822 from-address string, a list of RFC 822 to-address strings (a bare string will be treated as a list with 1 address), and a message string.
When you pass emails_list_string + ", " + emails_cc_list_string to server.sendmail as the second parameter, it treats it as a single address, probably chopping off everything after the first comma. Try like this instead:
server.sendmail(email_from, email_to_list + email_cc_list, msg.as_string())

Related

Trigger an email in Python if SQL Query returns results

I've connected a SQL query to Python and am trying to automate an email with attachment if the SQL query returns results. This query will show the discrepancies between our prices and the prices our client is selling our products at.
If there are differences between the 'Unit Price' and 'Selling Price', I want to email these to my director. However, if there are no differences, then the query will return 0 results and I do not want an empty email to be sent out.
My difficulty in this is that the python script is sending the attachment even if the query is returning no results. I only want to send the attachment if my query is returning results.
Any help on this would be greatly appreciated!
sql_query = pd.read_sql_query('''
SELECT SaleH.[Order No], SaleH.[Reference], SaleL.[Product code], SaleL.[Description], SaleL.[Quantity], SaleL.[Unit Price], SaleP.[Unit Price] AS 'Selling Price'
FROM [Sales Header] SaleH
INNER JOIN [Sales Line] SaleL ON SaleH.[Order No] = SaleL.[Order No]
LEFT JOIN [Sales Price] SaleP ON SaleP.[Product Code] = SaleL.[Product Code] AND SaleH.[Customer No] = SaleP.[Customer No]
WHERE SaleH.[Customer No] = 'Cust01' AND SaleH.[Date] >DATEADD(DD,-1,getdate()) AND SaleP.[Unit Price] != SaleL.[Unit Price]
''' ,conn)
df = pd.DataFrame(sql_query)
df.to_csv (r'G:\Customer Folder\Customer_Sales_Orders.csv', index = False)
outlook = win32. Dispatch('outlook.application')
email = outlook.CreateItem(0)
mail_from = "Sender"
mail_to = "Recipient"
mail_subject = "Customer Sales Orders"
mail_attachment = 'Customer_Sales_Orders.csv'
mail_attachment_name = "Customer_Sales_Orders" +'.csv'
This will do:
results = pd.read_csv('Data.csv') # count no. of lines
if len(results))> 0:
#sendemail
from_address = "<from which email>>"
to_address = "<<to which email>>"
# Create message container - the correct MIME type is multipart/alternative.
msg = MIMEMultipart('alternative')
msg['Subject'] = "Customer ha made purchases in the last 24 hours for
{}".format(date.today())
msg['From'] = from_address
msg['To'] = to_address
# Create the message (HTML).
html = """\
This is an automated email.
We are sending an email using Python and Gmail, how fun! We can fill this with html, and gmail supports a decent range of css style attributes too - https://developers.google.com/gmail/design/css#example.
"""
# Record the MIME type - text/html.
part1 = MIMEText(html, 'html')
# Attach parts into message container
msg.attach(part1)
# Credentials
username = '<your email id>'
password = '<google_app_password>'
# Sending the email
## note - this smtp config worked for me, I found it googling around, you may have to tweak the # (587) to get yours to work
server = smtplib.SMTP('smtp.gmail.com', 587)
server.ehlo()
server.starttls()
server.login(username,password)
server.sendmail(from_address, to_address, msg.as_string())
server.quit()

Python, email, quoted-printable partially decoded on other end

I don't understand why the body of my email I send via Python and the email library isn't being decoded by the receiving mail server. I know I've done something wrong, but don't know what. CTE is Quoted-Printable, and when viewing the raw email, it's still encoded.
I'm burning the net up, but most of my results deal with the legacy API of the email library. I use the current version, because it's recommended and more robust.
Code to package EmailMessage:
"""
#type sndr: str
#type rcvr: str
#type subj: str
#type body: tuple
#type extra: tuple
#return: bytes
"""
msg = message.EmailMessage()
if '<' in sndr:
name, sep, addr = sndr.rpartition(' ') # Partition at space between display name & address
name = name.strip() # Remove leading and trailing spaces
addr = addr.strip('<>') # Remove leading and trailing angle brackets
msg['From'] = headerregistry.Address(display_name=name, addr_spec=addr)
else:
msg['From'] = headerregistry.Address(display_name='', addr_spec=sndr)
if '<' in rcvr:
name, sep, addr = rcvr.rpartition(' ')
name = name.strip()
addr = addr.strip('<>')
msg['To'] = headerregistry.Address(display_name=name, addr_spec=addr)
else:
msg['To'] = headerregistry.Address(display_name='', addr_spec=rcvr)
msg['Subject'] = subj
msg.set_content(body[0], charset='utf-8')
msg.add_alternative(body[1], cte='quoted-printable', subtype='html')
return msg
HTML body:
"""
#type body: str
#return: str
"""
first = \
'<html><head>\r' \
'<meta http–equiv="Content-Type" content="text/html; charset=utf-8">\r' \
'</head>'
second = f'<body><div>{body}</div></body></html>'
payload = first + second
return payload
By using the argument subtype='html', Python creates the required meta tags. These tags display correctly in the raw email, yet my tags (old/troubleshooting code, no need to focus on it—the problem remains) do not. The en dash between "http" and "equiv" displays encoded. What's wrong. I want to send email using the full utf-8 range.
Also, I use the following from smtplib to send the message: send_message(msg). So, the default call of send_message().

How do I fix SMTPRecipientsRefused: {'': (421, b'4.7.0 Too many protocol errors (6) on this connection, closing transmission channel error Python?

I get this error when sending emails via smptplib in Python to a certain list
SMTPRecipientsRefused: {'': (421, b'4.7.0 Too many protocol errors (6) on this connection, closing transmission channel.')}?
I'm using Office365 SMTP details and a snippet of the code is below:-
import smtplib, ssl
from email.message import EmailMessage
import getpass
ids = df['IDs']
emails_to = df['Emails']
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()
On average, I will be sending emails to a list separated by a semi-colon(;) of about 8 emails per record. This means that for each attachment, I'll send to about 8 emails and I'm to do it for about 70 of such contacts. In total, that will be about 560 emails. Nothing gets sent out I get the above error the moment I log in. On the contrary, when I try test sending it to a list of 3 emails in the test emails column, the same emails go out very well. Can anyone point to where I may not be getting it right? I suspect either the list of emails is too long or is something with the email addresses thus the protocol error? Is this an SMTPlib limitation?
What you specify in MIMEMultipart() is what appears in the message header, but this is not always equal to the list of recipients. You can try to change server.send_message(msg) to server.sendmail(sender,recipients,msg.as_string())
Keep in mind that sendmail() requires a list of recipients, whereas msg['To'] should be set as one string, so if your variable email_to is comma separated, you should write it like:
s2.sendmail(email_from,email_to.split(','),msg.as_string())
More details about sendmail() can be found here.
I resolved this error by creating a list and joined all addresses from a tuple into a string, using a comma character as separator.
family = the list of all recipients' email addresses
family = [
'name1#example.com',
'name2#example.com',
'name3#example.com',
'name4#example.com',
'name5#example.com',
'name6#example.com',
'name7#example.com',
'name8#example.com'
]
msg['To'] =', '.join(family)

Getting 'float' object has no attribute 'encode' while sending email from python

I am getting an error 'float' object has no attribute 'encode' while I am sending email from python.
This was running successfully was past 6-7 days without any issues.
def create_message(send_from, send_to, cc_to, subject, plain_text_body):
message = MIMEMultipart('alternative')
message['From'] = send_from
message['To'] =send_to
message['Cc'] = cc_to
message['Date'] = formatdate(localtime=True)
message['Subject'] = subject
message.attach(MIMEText(plain_text_body, 'plain'))
return message
def add_attachment_from_local_disk(message, path):
with open(path, "rb") as file:
part = MIMEApplication(file.read(),Name=basename(path))
part['Content-Disposition'] = 'attachment; filename="%s"' % basename(path)
message.attach(part)
def send_message(message):
print(message)
client = boto3.client("ses",region_name='eu-west-1')
response = client.send_raw_email(RawMessage = {"Data": message.as_string()})
for i, row in final_email.iterrows():
subject = row["Subject"]
to_address = row['fba_to__notifications'] or row['lsp_escalation_back_up'] or "no_address#rs-components.com"
cc_list = row['cc_list']
send_from="ukrd#kuedid.com"
message = create_message(send_from,to_address, cc_list, subject, plain_text_body=body)
send_message(message)
Error
~\AppData\Local\Continuum\anaconda3\lib\email\_policybase.py in _fold(self, name, value, sanitize)
367 if self.max_line_length is not None:
368 maxlinelen = self.max_line_length
--> 369 parts.append(h.encode(linesep=self.linesep, maxlinelen=maxlinelen))
370 parts.append(self.linesep)
371 return ''.join(parts)
AttributeError: 'float' object has no attribute 'encode'
How to fix this?
The error says that the library receives a float where it would expect a string. From your code, I would expect that either body or one field from final_email contains a float.
I would not be surprised for the float to be a NaN because of an empty value in the dataframe. To make sure (or to make your code more robust) you could try to filter the exception and display the offending values:
for i, row in final_email.iterrows():
subject = row["Subject"]
to_address = row['fba_to__notifications'] or row['lsp_escalation_back_up'] or "no_address#rs-components.com"
cc_list = row['cc_list']
send_from="ukrd#kuedid.com"
try:
message = create_message(send_from,to_address, cc_list, subject, plain_text_body=body)
except AttributeError as e:
print('Error composing email', send_from,to_address, cc_list, subject, body, '\n', e)
# raise # optionaly re-raise the exception if you want to stop processing
send_message(message)
Anyway there is another problem here. NaN is seen as True when converted to a boolean in Python code. So the to_address assignation will not fallback to a or expression if it is NaN. So you should either combine_first the relevant columns if it makes sense (final_email['fba_to__notifications'].combine_first(final_email['lsp_escalation_back_up'].fillna('no_address#rs-components.com')), or explicitely test for NaN values:
to_address = row['fba_to__notifications'] if not np.isnan(row['fba_to__notifications']) \
else row['lsp_escalation_back_up'] if not isnan(row['lsp_escalation_back_up']) \
else "no_address#rs-components.com"

Python Sendgrid add CC to email

I am using SendGrid for Python. I want to CC some people in an email. It seems like they may no longer support CC'ing on emails, though I'm not positive if that's true? But surely there is a work around to it somehow, but I am surprised I can't find much support on this.
Here is my basic code:
sg = sendgrid.SendGridAPIClient(apikey='*****')
from_email = Email(sender_address, sender_name)
to_email = Email(email_address)
subject = subject
content = Content("text/plain", email_message)
mail = Mail(from_email, subject, to_email, content)
response = sg.client.mail.send.post(request_body=mail.get())
How can I modify this so it will CC someone on an email?
Using the SendGrid's Personalization() or Email() class did not work for me. This is how I got it to work:
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail, Cc
# using a list of tuples for emails
# e.g. [('email1#example.com', 'email1#example.com'),('email2#example.com', 'email2#example.com')]
to_emails = []
for r in recipients:
to_emails.append((r, r))
# note the Cc class
cc_emails = []
for c in cc:
cc_emails.append(Cc(c, c))
message = Mail(
from_email=from_email,
to_emails=to_emails,
subject='My Subject',
html_content=f'<div>My HTML Email...</div>'
)
if cc_emails:
message.add_cc(cc_emails)
try:
sg = SendGridAPIClient(os.getenv('SENDGRID_API_KEY'))
sg.send(message)
except Exception as e:
print(f'{e}')
Hopefully this helps someone.
I resolved it. Santiago's answer got me mostly there, but here is what I needed to do:
sg = sendgrid.SendGridAPIClient(apikey='****')
from_email = Email(sender_address, sender_name)
to_email = Email(to_email)
cc_email = Email(cc_email)
p = Personalization()
p.add_to(to_email)
p.add_cc(cc_email)
subject = subject
content = Content("text/plain", email_message)
mail = Mail(from_email, subject, to_email, content)
mail.add_personalization(p)
response = sg.client.mail.send.post(request_body=mail.get())
If you don't include the p.add_to(to_email) it rejects it because there is no "to email" in the personalization object. Also, if you don't include the "to_email" inside the mail object it rejects it because it is looking for that argument, so you have to be a bit redundant and define it twice.
I've been looking at the code: https://github.com/sendgrid/sendgrid-python/blob/master/examples/mail/mail.py
And it looks like you can do that by adding a personalization to the mail, for example:
cc_email = Email(cc_address)
p = Personalization()
p.add_cc(cc_email)
mail.add_personalization(p)
Based on the answers here you can CC to email if you add another email to 'to_email'.
If you want to cc multiple user then in djanogo using sendgrid you need to import the below line
the function that will be used to send the mail
and finally how you ned to send the data paramters to the above function so that it can CC the person
email = send_sandgridmail(sender=sender,receiver=receivers,subject=subject,content=message,reply_to=sender,cc=[admin_mail_account_mail,"rawatanup918#gmail.com"],attachment=None)
i hope this'll help.simplified from #anurag image script
import os
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import To,Mail,ReplyTo,Email,Cc
def send_sandgridmail (sender, receiver, subject, content, reply_to=None, cc=[], attachment=None) :
# content = convert_safe_text(content)
# to email = To(receiver)
message = Mail(
from_email=str(sender),
to_emails=receiver,
subject= str(subject),
html_content = content)
if reply_to:
message.reply_to= ReplyTo(reply_to)
if attachment:
message.add_attachment (attachment)
if len(cc):
cc_mail = []
for cc_person in cc:
cc_mail.append(Cc(cc_person, cc_person))
message.add_cc (cc_mail)
try:
SENDGRID_API_KEY = 'your sendgrid api key'
sg= SendGridAPIClient (SENDGRID_API_KEY)
response= sg.send(message)
print (response.status_code)
# print (response.body)
# print (response.headers)
except Exception as e:
print(e)
return response

Categories