I'm having some trouble getting Amazon SES to display a friendly FROM header. The issue seems to be that SES is inserting an additional from header before mine.
I'm using Python boto, and sending a MIME message using the send_raw_email method.
to_str = 'Name <name#domain.com>'
from_str = 'Name2 <name2#domain.com>'
msg = MIMEMultipart('alternative')
msg['Subject'] = 'Hello World'
msg['From'] = from_str
msg['To'] = to_str
msg.attach(MIMEText(em_plain, 'plain'))
msg.attach(MIMEText(em_html, 'html'))
ses_connection.send_raw_email(msg.as_string(), source=from_str, destinations=to_str)
What I get in the headers of the message that's delivered looks like this:
Subject: Hello World
Date: Thu, 9 Apr 2015 20:47:31 +0000
Message-ID: <blah-blah-blah#email.amazonses.com>
From: name2#domain.com
X-SES-Outgoing: 2015.blah.blah
Feedback-ID: 1.us-east-1.blah//blah=:AmazonSES
From: "Name2" <name2#domain.com>
To: "Name" <name#domain.com>
The result is that the user's mailbox does not display the friendly name. Plenty of info on the web says this is possible, but I can't get this working.
Looking into this further, if I omit the optional source parameter on the call to send_raw_email, the call fails with an error about a missing From header ... despite the fact that it has been set within the MimeMultipart object.
So, I think there are two issues - first, if I pass a friendly From header in the source parameter, it gets stripped down to just the email address. Second, the From that's in the MimeMultipart isn't recognized.
Figured it out.
Order of operations on the MIMEMultipart object matters.
You have to set the From header first, then the To, then the Subject.
If you do it in this order, then you can safely leave out the optional source and destination parameters on the send_raw_email call.
Would seem to be a limitation/issue with SES, as many other mail servers do not require the From header to come before the Subject.
Related
I've tried with no conclusions to resend emails with Python.
Once I've logged in SMTP and IMAP with TLS, this is what I have written:
status, data = self._imapserver.fetch(id, "(RFC822)")
email_data = data[0][1]
# create a Message instance from the email data
message = email.message_from_string(email_data)
# replace headers (could do other processing here)
message.replace_header("From", 'blablabla#bliblibli.com')
message.replace_header("To", 'blobloblo#blublublu.com')
self._smtpserver.sendmail('blablabla#bliblibli.com', 'blobloblo#blublublu.com', message.as_string())
But the problem is that the variable data doesn't catch the information from the email, even if the ID is the one I need.
It tells me:
b'The specified message set is invalid.'
How can I transfer an email with Python?
Like the error message says, whatever you have in id is invalid. We don't know what you put there, so all we can tell you is what's already in the error message.
(Also, probably don't use id as a variable name, as you will shadow the built-in function with the same name.)
There are additional bugs further on in your code; you need to use message_from_bytes if you want to parse it, though there is really no need to replace the headers just to resend it.
status, data = self._imapserver.fetch(correct_id, "(RFC822)")
self._smtpserver.sendmail('blablabla#bliblibli.com', 'blobloblo#blublublu.com', data[0][1])
If you want to parse the message, you should perhaps add a policy argument; this selects the modern EmailMessage API which was introduced in Python 3.6.
from email.policy import default
...
message = email.message_from_bytes(data[0][1], policy=default)
message["From"] = "blablabla#bliblibli.com"
message["To"] = "blobloblo#blublublu.com"
self._smtpserver.send_message(message)
The send_message method is an addition to the new API. If the message could contain other recipient headers like Cc:, Bcc: etc, perhaps using the good old sendmail method would be better, as it ignores the message's headers entirely.
I'm working on a script that creates both an HTML and Plaintext email. This didn't seem like a hassle, but I can't hardcode the e-mail into the script I'm using. Allow me to explain why.
I'm using the Zabbix Monitoring system to keep an eye out for my systems. Allow me to use a printer for example. Once one of the toners is close to dying, I want both an HTML and plain text mail to be sent. Zabbix uses several macros however, which can be used to identify what system is being affected by what.
So, the HTML body needs to received before the script is run. Once the HTML body has been received with the correct values, it needs to have a plain copy too. As much as I'd like to have the plain-text contain the correct parameters (copied from the macros) too, I don't consider this a necessity.
This is what my script currently looks like.
#!/usr/bin/env python
list1=[0, 1, 2, 3, 4];
import mimetypes, os, smtplib, sys
from email import encoders
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
me = "x"
you = "y"
DESTINATION=sys.argv[1]
SUBJECT=sys.argv[2]
MESSAGE=sys.argv[3]
PASS='password'
# Create message container - the correct MIME type is multipart/alternative.
msg = MIMEMultipart('alternative')
msg['Subject'] = "Link"
msg['From'] = me
msg['To'] = you
text = "A problem has been detected on {HOST.NAME} \n \n Trigger: {TRIGGER.NAME} \n Status: {TRIGGER.STATUS} \n Severity: {TRIGGER.SEVERITY} "
#html =
# Record the MIME types of both parts - text/plain and text/html.
part1 = MIMEText(text, 'plain')
#part2 = MIMEText(html, 'html')
# Attach parts into message container.
# According to RFC 2046, the last part of a multipart message, in this case
# the HTML message, is best and preferred.
msg.attach(part1)
#msg.attach(part2)
# Send the message via local SMTP server.
s = smtplib.SMTP('mysmtpserver')
s.ehlo()
s.starttls()
s.ehlo()
s.login(me, PASS)
# sendmail function takes 3 arguments: sender's address, recipient's address
# and message to send - here it is sent as one string.
s.sendmail(me, you, msg.as_string())
s.quit()
The reason why I can't have them being sent seperately or sending the HTML copy alone is because my e-mails get nuked by SpamAssassin. I'm intending to send certain error messages to my customers. So they are immediately notified when their toner is nearly empty, so they can place a new order. The HTML-email has been set up and has been working without failure.
So to put it onto perspective one more time.
Zabbix recieves an error.
There is a default HTML message written, macros become filled, this then needs to be imported to the script.
The script is then run.
There's an e-mail sent with both HTML content as well as the
plain-text for users that can't read the HTML-emails.
The reason the script is a mess is because I've already tried several options, but I can't seem to figure this one out.
I'm trying to use the amazon SES simulator as documented here: http://docs.aws.amazon.com/ses/latest/DeveloperGuide/mailbox-simulator.html
The body of the message looks like this:
MIME-Version: 1.0
Content-Type: multipart/alternative; charset="utf-8";
boundary="===============123456789=="
Content-Transfer-Encoding: base64
Subject: hello test message!
Reply-To: my_address#my_provider.com
To: complaint#simulator.amazonses.com
Return-Path: my_address#my_provider.com
Bounces-to: my_address#my_provider.com
Errors-to: my_address#my_provider.com
From: my_address#my_provider.com
--===============123456789==
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: base64
Ym9keSB3aXRoIG5vbmNlOiAw
--===============123456789==
MIME-Version: 1.0
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: base64
Ym9keSB3aXRoIG5vbmNlOiAw
--===============123456789==--
I'm sending this as the body, and using the boto3 interface ses_client.send_raw_message.
I'm generating this message body with something like this
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import re
msg = MIMEMultipart('alternative')
msg.set_charset('utf8')
text_message='Ym9keSB3aXRoIG5vbmNlOiAw'
p = re.compile('<.*?>')
text_message = 'body with nonce: {}'.format(str(nonce))
text = MIMEText(p.sub('', text_message), 'plain', 'utf8')
html = MIMEText(text_message, 'html', 'utf8')
msg.attach(text)
msg.attach(html)
source = 'my_email#my_provider.com'
msg['Subject'] = 'hello test message!'
msg['Reply-To'] = source
msg['To'] = to_mail
msg['Return-Path'] = source
msg['Bounces-to'] = source
msg['Errors-to'] = source
So I can send emails through SES and it works just fine.
I can also send emails to complaint#simulator.amazonses.com, and that works.
I haven't set up any SNS messaging, but I expected, through all the header fields that I set, to get a bounce back at the desired address. However, nothing happens if i use bounce#simulator.amazonses.com.
This is a list of header fields that amazon acknowledges:
http://docs.aws.amazon.com/ses/latest/DeveloperGuide/header-fields.html
I have also enabled email feedback forwarding, as described here:
http://docs.aws.amazon.com/ses/latest/DeveloperGuide/notifications-via-email.html
Also, for sending the emails I use this logic:
amazon_client = boto3.client('ses', 'region', **credentials)
amazon_client.send_raw_email(
Source=from_mail,
Destinations=to_mails,
RawMessage={'Data': body}
)
According to the page documenting how to receive notifications, I should be getting mails back at the Source address specified - which I do set, and I do have the email feedback forwarding enabled for....
Any ideas what I might be missing?
The headers you are putting in suggest that the real problem is false expectations. Errors-To: will be ignored pretty much by everything you can find, and Return-Path: will be replaced with the envelope sender by the receiving MTA. To control bounces, you need to control the envelope sender (traditionally, with sendmail -f) and what you put in the headers is completely irrelevant.
Solved.
The bounce address was different from the 'From' address. Therefore, the bounces ended up in the SPAM of that other address.
Interestingly, that address had a filter to forward everything to the address I had used as for 'From' address, when sending the email.
So my confusion was caused because I expected to get mail forwarded from that other address, but the bounces never got forwarded.
After a bunch of fiddling, I finally hit upon the magical sequence to attach a text file to an email (many thanks to previous posts on this service).
I'm left wondering what the lines:
attachment.add_header('Content-Disposition'. . .)
--and--
e_msg = MIMEMultipart('alternative')
actually do.
Can someone unsilence the Mimes for me please (sorry couldn't resist)
import smtplib
from email import Encoders
from email.message import Message
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
smtp_server = "1.2.3.4"
smtp_login = "account"
smpt_password = "password"
server = smtplib.SMTP(smtp_server)
server.login(smtp_login,smtp_password)
f = file("filename.csv")
attachment = MIMEText(f.read())
attachment.add_header('Content-Disposition', 'attachment', filename="filename.csv")
e_msg = MIMEMultipart('alternative')
e_msg.attach(attachment)
e_msg['Subject'] = 'Domestic Toll Monitor'
e_msg['From'] = smtp_account
body = 'Some nifty text goes here'
content = MIMEText(body)
e_msg.attach(content)
server.sendmail(smtp_from, smtp_to, e_msg.as_string())
Basically, MIME is the specification defining email structure. The Multipart structure is designed to allow for multiple types of messages and attachments to be sent within the same message. For example, an email might have a plain text version for backwards compatibility and a rich text or html formatted message for modern clients. Attachments count as a "part", and thus require their own header. In this case, you're adding a "Content-Disposition" type header for the attachment. If you're really interested in what that means, you can read the specification here. As for the "Alternative portion, you're setting the message to multipart and defining the types of parts that you have attached and how the client needs to handle them. There are some standard presets defining various scenarios, but Alternative is something of a wildcard, used when there is a part whose type might not be recognized or handled by most clients. For the record, I believe you also could have used a "Mixed" type. The nice thing about MIME is that while it is complicated, its thoroughly defined and its very easy to look up the specification.
I'm generating email with the help of Python email module.
Here are few lines of code, which demonstrates my question:
msg = email.MIMEMultipart.MIMEMultipart('alternative')
msg['From'] = "somemail#somedomain.com"
msg.as_string()
Out[7]: 'Content-Type: multipart/alternative;\n boundary="===============9006870443159801881=="\nMIME-Version: 1.0\nFrom: somemail#somedomain.com\n\n--===============9006870443159801881==\n\n--===============9006870443159801881==--'
As you can see, everything is okay here, From field contains email ant it is cool. But what if I want to add some name before email? Especially unicode one:
In [8]: u.get_full_name()
Out[8]: u'\u0414\u0438\u043c\u0430 \u0426\u0443\u043a\u0430\u043d\u043e\u0432'
In [9]: msg = email.MIMEMultipart.MIMEMultipart('alternative')
In [10]: msg['From'] = "%s <%s>" % (u.get_full_name(), "email#at.com")
In [11]: msg.as_string()
Out[11]: 'Content-Type: multipart/alternative;\n boundary="===============5792069034892928634=="\nMIME-Version: 1.0\nFrom: =?utf-8?b?0JTQuNC80LAg0KbRg9C60LDQvdC+0LIgPGVtYWlsQGF0LmNvbT4=?=\n\n--===============5792069034892928634==\n\n--===============5792069034892928634==--'
Here you can see, that all the string (name, email) was encoded in base64 (and it is even quite logical, how MIMEMultipart will know that string contains unicode and non-unicode parts).
So, my question is: how do I have to tell email module to make me pretty "From" header like:
From: =?UTF-8?B?0JLQmtC+0L3RgtCw0LrRgtC1?= <admin#notify.vk.com> ?
Also, I've learned a little RFC2822 (http://www.faqs.org/rfcs/rfc2822.html , p.3.6.2). It tells:
The originator fields indicate the mailbox(es) of the source of the
message. The "From:" field specifies the author(s) of the message,
that is, the mailbox(es) of the person(s) or system(s) responsible
for the writing of the message. The "Sender:" field specifies the
mailbox of the agent responsible for the actual transmission of the
message. For example, if a secretary were to send a message for
another person, the mailbox of the secretary would appear in the
"Sender:" field and the mailbox of the actual author would appear in
the "From:" field. If the originator of the message can be indicated
by a single mailbox and the author and transmitter are identical, the
"Sender:" field SHOULD NOT be used. Otherwise, both fields SHOULD
appear.
Does it mean that I should combine these two headers? (From and Sender). I'm a bit confused, because I noticed a lot of emails in my gmail (looking through "Show original") where in From field name and email are presented.
Thanks for help.
You need to encode the name part separately using email.header.Header:
from email.MIMEMultipart import MIMEMultipart
from email.header import Header
from email.utils import formataddr
author = formataddr((str(Header(u'AlaĆ', 'utf-8')), "somemail#somedomain.com"))
msg = MIMEMultipart('alternative')
msg['From'] = author
print msg
I hope this will help.