Getting 'float' object has no attribute 'encode' while sending email from python - 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"

Related

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)

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

python3 - imaplib: randomly(?) can' fetch received email

def get_email_body(self, email):
user = self.email_usr
password = self.app_email_pwd
connection = imaplib.IMAP4_SSL('imap.gmail.com')
connection.login(user, password)
connection.list()
connection.select('"INBOX"')
time.sleep(5)
result_search, data_search = connection.search(None, 'TO', email, 'SUBJECT', '"some subject"')
required_email = data_search[0]
result_fetch, data_fetch = connection.fetch(required_email, '(RFC822)')
email_body_string = data_fetch[0][1].decode('utf-8')
confirmation_link = self.parse_confirmation_link(email_body_string)
return confirmation_link
This function works like 2 times of 4 runs. Usually it fails with:
self = <imaplib.IMAP4_SSL object at 0x7fa853614b00>, name = 'FETCH'
tag = b'JAAL5'
def _command_complete(self, name, tag):
# BYE is expected after LOGOUT
if name != 'LOGOUT':
self._check_bye()
try:
typ, data = self._get_tagged_response(tag)
except self.abort as val:
raise self.abort('command: %s => %s' % (name, val))
except self.error as val:
raise self.error('command: %s => %s' % (name, val))
if name != 'LOGOUT':
self._check_bye()
if typ == 'BAD':
raise self.error('%s command error: %s %s' % (name, typ, data))
E imaplib.error: FETCH command error: BAD [b'Could not parse command']
/usr/lib/python3.4/imaplib.py:964: error
My suggestion was that sometimes email isn't delivered at the moment of .search that's why I added time.sleep (I'm searching for the email immediately after it was sent).
Else I did try search while result_fetch is not 'OK' but is also didn't help.
Any other suggestions?
oooops, my suggestion was correct, but time.sleep was in the incorrect place. Moved sleep before connection and all go smooth

Python email - can only sendmail to one address

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())

Categories