Python email lib – How to remove attachment from existing message?
Question:
I have an email that I’m reading with the Python email lib that I need to modify the attachments of. The email Message class has the “attach” method, but does not have anything like “detach”. How can I remove an attachment from a multipart message? If possible, I want to do this without recreating the message from scratch.
Essentially I want to:
- Load the email
- Remove the mime attachments
- Add a new attachment
Answers:
Well, from my experience, in the context you are working, everything is a Message
object. The message, its parts, attachments, everything. So, to accomplish what you want to do, you need to
- parse the message using the Parser API (this will get you the root
Message
object)
- Walk the structure, determining what you need and what you don’t (using a method of a Message instance, – .walk()), – remember, that everything is a
Message
.
- Attach whatever you need to attach to the parts you’ve extracted and you are good to go.
To reiterate, what you are working with is, essentially, a tree, where Message
objects with .is_multipart() == True are nodes and Message
objects with .is_multipart() == False are end-nodes (their payload is a string, not a bunch of Message
objects).
The way I’ve figured out to do it is:
- Set the payload to an empty list with set_payload
- Create the payload, and attach to the message.
set_payload()
may help.
set_payload(payload[, charset])
Set the entire message object’s payload to payload. It is the client’s responsibility to ensure the payload invariants.
A quick interactive example:
>>> from email import mime,message
>>> m1 = message.Message()
>>> t1=email.MIMEText.MIMEText('t1rn')
>>> print t1.as_string()
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
t1
>>> m1.attach(t1)
>>> m1.is_multipart()
True
>>> m1.get_payload()
[<email.mime.text.MIMEText instance at 0x00F585A8>]
>>> t2=email.MIMEText.MIMEText('t2rn')
>>> m1.set_payload([t2])
>>> print m1.get_payload()[0].as_string()
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
t2
>>>
For MIMEMultipart you can attach, but you can’t remove elements directly. Good news is that in this case get_payload() returns a standard python list of elements.
From https://docs.python.org/3/library/email.compat32-message.html
Return the current payload, which will be a list of Message objects when is_multipart() is True, or a string when is_multipart() is False. If the payload is a list and you mutate the list object, you modify the message’s payload in place.
To clear entire MIMEMultipart payload just assign new empty list:
multipart_related.set_payload([])
This example removes only selected items from multipart/related payload list:
for part in email.walk():
if part.get_content_type() == 'multipart/related':
list_of_messages = part.get_payload()
for message in list(list_of_messages):
if message.get_content_type() != 'text/html' and message.get_content_type() != 'text/plain':
list_of_messages.remove(part)
I have an email that I’m reading with the Python email lib that I need to modify the attachments of. The email Message class has the “attach” method, but does not have anything like “detach”. How can I remove an attachment from a multipart message? If possible, I want to do this without recreating the message from scratch.
Essentially I want to:
- Load the email
- Remove the mime attachments
- Add a new attachment
Well, from my experience, in the context you are working, everything is a Message
object. The message, its parts, attachments, everything. So, to accomplish what you want to do, you need to
- parse the message using the Parser API (this will get you the root
Message
object) - Walk the structure, determining what you need and what you don’t (using a method of a Message instance, – .walk()), – remember, that everything is a
Message
. - Attach whatever you need to attach to the parts you’ve extracted and you are good to go.
To reiterate, what you are working with is, essentially, a tree, where Message
objects with .is_multipart() == True are nodes and Message
objects with .is_multipart() == False are end-nodes (their payload is a string, not a bunch of Message
objects).
The way I’ve figured out to do it is:
- Set the payload to an empty list with set_payload
- Create the payload, and attach to the message.
set_payload()
may help.
set_payload(payload[, charset])
Set the entire message object’s payload to payload. It is the client’s responsibility to ensure the payload invariants.
A quick interactive example:
>>> from email import mime,message
>>> m1 = message.Message()
>>> t1=email.MIMEText.MIMEText('t1rn')
>>> print t1.as_string()
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
t1
>>> m1.attach(t1)
>>> m1.is_multipart()
True
>>> m1.get_payload()
[<email.mime.text.MIMEText instance at 0x00F585A8>]
>>> t2=email.MIMEText.MIMEText('t2rn')
>>> m1.set_payload([t2])
>>> print m1.get_payload()[0].as_string()
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
t2
>>>
For MIMEMultipart you can attach, but you can’t remove elements directly. Good news is that in this case get_payload() returns a standard python list of elements.
From https://docs.python.org/3/library/email.compat32-message.html
Return the current payload, which will be a list of Message objects when is_multipart() is True, or a string when is_multipart() is False. If the payload is a list and you mutate the list object, you modify the message’s payload in place.
To clear entire MIMEMultipart payload just assign new empty list:
multipart_related.set_payload([])
This example removes only selected items from multipart/related payload list:
for part in email.walk():
if part.get_content_type() == 'multipart/related':
list_of_messages = part.get_payload()
for message in list(list_of_messages):
if message.get_content_type() != 'text/html' and message.get_content_type() != 'text/plain':
list_of_messages.remove(part)