I am currently using Python 2.7 and trying to send a raw email with an attachment (CSV to be exact) to multiple addresses with Boto SES. I can send a normal email with send_email(), but I keep getting an error when trying to send to multiple people via send_raw_email().
This is the error that I get with a comma-separated string of recipients:
Error sending email: SESIllegalAddressError: 400 Illegal address
<ErrorResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/">
<Error>
<Type>Sender</Type>
<Code>InvalidParameterValue</Code>
<Message>Illegal address</Message>
</Error>
<RequestId>[the request ID]</RequestId>
</ErrorResponse>
That's from using this code:
to_emails = "me#example.com, them#example.com"
# create raw email
msg = MIMEMultipart()
msg['Subject'] = 'Email subject'
msg['From'] = 'me#example.com'
msg['To'] = to_emails
part = MIMEText('Attached is an important CSV')
msg.attach(part)
part = MIMEApplication(open(fname, 'rb').read())
part.add_header('Content-Disposition', 'attachment', filename=fname)
msg.attach(part)
# end create raw email
conn = boto.ses.connect_to_region(
'[my region]',
aws_access_key_id=s3_access_key,
aws_secret_access_key=s3_secret_key
)
conn.send_raw_email(msg.as_string(),
source=msg['From'],
destinations=msg['To']
)
Also, here is the error I get from using an array of strings for recipients:
Error sending email: 'list' object has no attribute 'lstrip'
It works fine if I have just one recipient, so it just throws the error when I have an array of recipients and a comma-separated string of recipients. Anyone have any experience with this?
I ended it getting it after looking at some docs and some more trial & error. It turns out that I just had to join the array of email strings for the msg['To'], and then I was able to pass in the email array for destinations parameter.
Here's what I did:
to_emails = "me#example.com, them#example.com"
COMMASPACE = ', '
# create raw email
msg = MIMEMultipart()
msg['Subject'] = 'Email subject'
msg['From'] = 'me#example.com'
msg['To'] = COMMASPACE.join(to_emails) ## joined the array of email strings
# edit: didn't end up using this ^
part = MIMEText('Attached is an important CSV')
msg.attach(part)
part = MIMEApplication(open(fname, 'rb').read())
part.add_header('Content-Disposition', 'attachment', filename=fname)
msg.attach(part)
# end create raw email
conn = boto.ses.connect_to_region(
'[my region]',
aws_access_key_id=s3_access_key,
aws_secret_access_key=s3_secret_key
)
conn.send_raw_email(msg.as_string(),
source=msg['From'],
destinations=to_emails ## passed in an array
)
I believe instead of a comma separated string with your recipients you have have to use a a list of strings.
Recipients = ['1#email.com', '2#email.com']
conn.send_raw_email(msg.as_string(),
source=msg['From'],
destinations= Recipients)
So something along those lines.
Source: http://boto.readthedocs.org/en/latest/ref/ses.html?highlight=send_raw_email#boto.ses.connection.SESConnection.send_raw_email
The official documentation says a list of strings or simply a string. This is why it works with only one recipient.
Second attempt::
to_emails = ['me#example.com', 'them#example.com']
# create raw email
msg = MIMEMultipart()
msg['Subject'] = 'Email subject'
msg['From'] = 'me#example.com'
msg['To'] = to_emails
conn.send_raw_email(msg.as_string(),
source=msg['From'],
destinations=msg['To'])
Am I right in assuming your code now looks like this? If not, try this.
The solution is set a string separated by comma to the header and a list to destinations field.
Something like:
to_emails = ['me#example.com', 'them#example.com']
msg['To'] = ', '.join( to_emails )
and
...
conn.send_raw_email(msg.as_string(),
source=msg['From'],
destinations=to_emails ## passed in an array
)
When sending without attachment just assigning list works. But in other case the below code helped..Thanks #Ezequiel Salas
to_emails = ['me#example.com', 'them#example.com']
or
to_emails = some_list
msg['To'] = ', '.join( to_emails )
Related
I have four python functions that I am using to send mail. If the program does one thing, it mails a set of results to a multiple recipients, if it does another thing, a separate function mails the results to one recipient.
def smail(to,sub,body):
addr_from = 'alert#example.com'
msg = MIMEMultipart()
msg['From'] = addr_from
msg['To'] = to
msg['Subject'] = sub
msg.attach(body)
s = smtplib.SMTP('webmail.example.com')
s.sendmail(addr_from, [to], msg.as_string())
s.quit()
def email_format2(results):
text = ""
text += 'The following applications in <environment> are non-compliant\n'
text += '<br>'
text += 'Here are there names and locations. Please inform the developer.'
text += '<br>'
text += '<br>'
table = pd.DataFrame.from_records(results)
table_str = table.to_html()
text += "%s" % table_str
return text
def mail_results_aq(results):
body = email_format2(results)
msg = MIMEText(body, 'html')
sub = "Placeholder subject"
to = 'email1#example.com, email2#example.com, email3#example.com'
smail(to, sub, msg)
def mail_results_prd(results):
body = email_format2(results)
msg = MIMEText(body, 'html')
sub = "Placeholder subject"
to = 'email4#example.com'
smail(to, sub, msg)
The mail_results_aq function will only email results to the first recipient (email1#example.com).
In other questions similar to this one, I've seen a the recipients being entered into a list i.e
import smtplib
from email.mime.text import MIMEText
s = smtplib.SMTP('smtp.uk.xensource.com')
s.set_debuglevel(1)
msg = MIMEText("""body""")
sender = 'me#example.com'
recipients = ['john.doe#example.com', 'john.smith#example.co.uk']
msg['Subject'] = "subject line"
msg['From'] = sender
msg['To'] = ", ".join(recipients)
s.sendmail(sender, recipients, msg.as_string())
However, this use case only seems to work when used in the same function. How can I implement this feature across the four functions I have above?
Thanks in advance!
I searched about this quite a lot, but could not fix the issue in my script. So finally, I decided to post it here.
Here's the code snippet:
fromaddr = "someValidAddress#xyz.com"
cc = ['SomeEmailAlias#xyz.com']
toaddr = ""
msg = MIMEMultipart()
toaddrlist = list(toaddr.split(',')) #As sendmail() accepts the list of recipients only in list form.
toaddrlist += (cc,)
msg['From'] = fromaddr
msg['To'] = toaddr
msg['Cc'] = ', '.join(cc)
msg['Date'] = formatdate(localtime=True)
msgHtml = MIMEText(html, 'html')
msg.attach(msgHtml)
msg['Subject'] = "Test mail"
server = "someMailServer.xyz.com"
smtp = smtplib.SMTP(server, 25)
smtp.sendmail(fromaddr, toaddrlist, msg.as_string())
smtp.close() #Close the SMTP server connection.
I'm aware and I've ensured that msg['To'] accepts a string value (toaddr), whereas toaddrlist in sendmail() should be a list.
Catch: If I remove the line toaddrlist += (cc,), then the mail does not get delivered twice to the recipients in "To" field, but the mail does not get delivered to the Cc alias.
Please help.
When the line toaddrlist += (cc,) is evaluated, the value of toaddrlist in your case is :
["", ["SomeEmailAlias#xyz.com"]]
and it's wrong because toaddrlist must be a list of strings not a list containing some lists.
So the solution is to change :
toaddrlist += (cc,)
to
toaddrlist += cc
or the recommended form (the pythonic way) :
toaddrlist.extend(cc)
I'm having big time problems with this issue-- another question on SO that didn't solve it is here: Send Raw Email (with attachment) to Multiple Recipients
My code (that works) is simple:
def send_amazon_email_with_attachment(html, subject, now, pre):
dummy = 'test#example.com'
recipients = ['test1#exampl.ecom', 'test2#example.com', 'test3#example.com']
connS3 = S3Connection('IDENTIFICATION','PASSWORD')
b = connS3.get_bucket('BUCKET_NAME')
key = b.get_key('FILE_NAME.pdf')
temp = key.get_contents_as_string()
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = 'My Name <test#example.com>'
msg.preamble = 'Multipart message.\n'
part1 = MIMEText(u"Attached is the report", 'plain')
part2 = MIMEText(html, 'html')
msg.attach(part1)
msg.attach(part2)
part = MIMEApplication(temp) #read binary
part.add_header('Content-Disposition', 'attachment', filename='FILE_NAME.pdf')
msg.attach(part)
conn = boto.ses.connect_to_region('us-east-1', aws_access_key_id='AUTH_ID', aws_secret_access_key='AUTH_PW')
for recipient in recipients:
print recipient
msg['To'] = recipient
result = conn.send_raw_email(msg.as_string(), source=msg['From'], destinations=recipient)
but, there's a caveat... this is looping for each recipient. Any variation of this does not work. Passing a list to msg['Bcc'] or msg['BCC'] will return an error that the list can't be stripped (same exact error as the posted question). Passing a string separated by commas gives an Amazon SES issue saying 'Illegal Email' in the returned XML. Because I only get an error from Amazon in specific situations, I'm led to believe this is an error with the program before it hits their API call.
Any MIMEMultipart experts have some ideas?
Basically you need to specify the email recipients in 2 different places using 2 different formats.
def send_amazon_email_with_attachment(html, subject, now, pre):
dummy = 'test#example.com'
recipients = ['test1#exampl.ecom', 'test2#example.com', 'test3#example.com']
connS3 = S3Connection('IDENTIFICATION','PASSWORD')
b = connS3.get_bucket('BUCKET_NAME')
key = b.get_key('FILE_NAME.pdf')
temp = key.get_contents_as_string()
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = 'My Name <test#example.com>'
msg['To'] = ', '.join(recipients)
msg.preamble = 'Multipart message.\n'
part1 = MIMEText(u"Attached is the report", 'plain')
part2 = MIMEText(html, 'html')
msg.attach(part1)
msg.attach(part2)
part = MIMEApplication(temp) #read binary
part.add_header('Content-Disposition', 'attachment', filename='FILE_NAME.pdf')
msg.attach(part)
conn = boto.ses.connect_to_region('us-east-1', aws_access_key_id='AUTH_ID', aws_secret_access_key='AUTH_PW')
result = conn.send_raw_email(msg.as_string(), source=msg['From'], destinations=recipients)
msg['To'] = ', '.join(recipients) isn't working for me (throws encoding error). Simply comment that line and your 'destinations' in "send_raw_email" should contain a list. That will work like a charm.
I am trying to use the MIME and send the email using the following code. But the python is not able to change the value of the msg['Subject']. What I am doing wrong? Any suggestions?
Thanks
msg = MIMEText('MAIL from my python programme')
msg['Subject'] = 'Testing '
msg['From'] = USERNAME
msg['To'] = MAILTO
test_var=1
if(test_var):
msg['Subject'] = 'Test variable is 1'
else:
msg['Subject'] = 'Test variable is 0'
server = smtplib.SMTP('smtp.gmail.com:587')
server.ehlo_or_helo_if_needed()
server.starttls()
server.ehlo_or_helo_if_needed()
server.login(USERNAME,PASSWORD)
server.sendmail(USERNAME, MAILTO, msg.as_string())
server.quit()
print "sending email"
From what I can tell, you have to delete the Subject field (or any other) before you replace it. Otherwise, it just appends more subjects - which might be ignored, looking like the value doesn't change.
test_var=1
del msg['Subject']
if(test_var):
msg['Subject'] = 'Test variable is 1'
else:
msg['Subject'] = 'Test variable is 0'
I'm sending a string to an email, and I want one sentence per line like this:
"Loaded LLARY_AR with 0 features
Loaded LLARY_LN with 44 features
Loaded LLARY_PT with 23 features"
But when I add one newline to the string concatenation I get two newlines instead of one, like this:
"Loaded LLARY_AR with 0 features
Loaded LLARY_LN with 44 features
Loaded LLARY_PT with 23 features"
And if I do not include a newline I get this:
"Loaded LLARY_AR with 0 features Loaded LLARY_LN with 44 features Loaded LLARY_PT with 23 features"
Here's the code:
msgemail = ""
for fcl in trnlist:
try:
tofc = param["tsde"]+"\\"+param["trema"]+fcl
fromfc = param["msde"]+"\\"+param["mchema"]+fcl
arcpy.DeleteFeatures_management(tofc)
arcpy.Append_management(fromfc, tofc)
msgemail +="\nLoaded "+fcl+" with "+str(arcpy.GetCount_management(fromfc))+" features"
del fcl, tofc, fromfc
except:
msgemail +="\nUnsuccessful!! "+fcl
emailto = ["email#to","email#to"]
server = smtplib.SMTP('server.here')
header = "Results:\n"
subject = "Data"
mailmessage = header+msgemail+"\n\nCheck log for details"
message = 'Subject: %s\n\n%s' %(subject, mailmessage)
for e in emailto:
try:
server.sendmail("email#from",e, message)
except:
arcpy.AddMessage(e+" was not sent an email.")
server.quit()
I don't understand why the newline acts in this manner..and a newbie..obviously missing something here.
I found that this works to produce an email that is nicely formated (but does not include the necessary information from the ..GetCount..process):
msgemail +="\nLoaded"+fcl
While these do not result in a nicely formated email:
msgemail +="\nLoaded "+fcl+" with "+str(arcpy.GetCount_management(fromfc))+" features"
msgemail +="\nLoaded "+fromcnt
msgemail +="\nLoaded "+fromcnt+" testing for string at end"
There does not appear to be an issue with the code, there should only be one newline between each of your 'Loaded...' lines.
It is possible that the email client you are using to view the email is interpreting newlines as new paragraphs and inserting that spacing automatically. Try replacing the \n with <br> and see if that results in the single spacing you expect.
I ran into the same problem using the smtplib by itself. I found that a better way to do it is this:
from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email.Utils import COMMASPACE, formatdate
from email import Encoders
import os
import smtplib
def send_mail(send_from, send_to, subject, text, files=[], server='mail.server.com'):
assert type(send_to)==list
assert type(files)==list
msg = MIMEMultipart()
msg['From'] = send_from
msg['To'] = COMMASPACE.join(send_to)
msg['Date'] = formatdate(localtime=True)
msg['Subject'] = subject
msg.attach( MIMEText(text) )
for f in files:
part = MIMEBase('application', "octet-stream")
part.set_payload( open(f,"rb").read() )
Encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(f))
msg.attach(part)
smtp = smtplib.SMTP(server)
mydict = smtp.sendmail(send_from, send_to, msg.as_string())
smtp.close()
return mydict
Note the return of the dictionary! The smtp.sendmail will return it with a list of failure tuples if some emails fail to send. If all emails fail to send, an exception is thrown, and if they all succeed, then the dictionary is empty.
You will save yourself grief if you check it.
It may be that the '\n' should come at the end of a line.
It sounds dumb and may not work, but it might be worth a shot.
msgemail = "\n"
for fcl in trnlist:
try:
tofc = param["tsde"]+"\\"+param["trema"]+fcl
fromfc = param["msde"]+"\\"+param["mchema"]+fcl
arcpy.DeleteFeatures_management(tofc)
arcpy.Append_management(fromfc, tofc)
msgemail +="Loaded "+fcl+" with "+str(arcpy.GetCount_management(fromfc))+" features\n"
del fcl, tofc, fromfc
except:
msgemail +="\nUnsuccessful!! "+fcl
emailto = ["email#to","email#to"]
server = smtplib.SMTP('server.here')
header = "Results:\n"
subject = "Data"
mailmessage = header+msgemail+"\n\nCheck log for details"
message = 'Subject: %s\n\n%s' %(subject, mailmessage)
for e in emailto:
try:
server.sendmail("email#from",e, message)
except:
arcpy.AddMessage(e+" was not sent an email.")
server.quit()