How to check number of attachments in email? - python

I'm working with exchangelib and python3 to manage an Exchange mailbox, so far my code works ok, but I wanted to check how many attachments an email has before to go forward.
This is what I have so far:
def send_email(account, subject, body, recipients, attachments=None):
to_recipients = []
for recipient in recipients:
to_recipients.append(Mailbox(email_address=recipient))
# Create message
m = Message(account=account,
folder=account.sent,
subject=subject,
body=body,
to_recipients=to_recipients)
b = attachments.count
log.info(b)
m.attach(attachments)
m.send_and_save()
I'm calling this function to create a new email from a previous one I've received with attachments. When the email has a just one attachment it works fine, however when the received email has more than one attachment it fail. That is why I wanted to check how many attachments the received email has before to proceed.
I found out this attribute for attachments object but the result I got is this:
<built-in method count of list object at 0x10a23be10>
So, how could I check if the attachments object, which is a type FileAttachment,has more than one attachment? Even better, how could I attach more than one attachment to my new email?
For the last question I have this code, which does not work:
for attachment_name, attachment_content in attachments or []:
log.info('loop attachments')
file = FileAttachment(name=attachment_name, content=attachment_content)
m.attach(file)
This is the error I'm receiving:
for attachment_name, attachment_content in attachments or []:
TypeError: cannot unpack non-iterable FileAttachment object

There are multiple misconceptions here.
First, you need to check first which type your attachments argument is. Judging by the output, you are passing a list (the built-in list type). Later on, you are printing attachments.count which is actually the list.count() method (see https://docs.python.org/3.8/tutorial/datastructures.html), which does not make sense to print. If you want to get the size of the attachments argument, use len(attachments), since it's just a list.
A single FileAttachment is one attachment, not a list of attachments. Instead, I assume that your attatchments argument is a list of FileAttachment objects.
Regarding the last TypeError, you are iterating over a list of FileAttachment objects, but then treating each object as if it were a tuple by trying to unpack the object. That won't work. If you want to access the name and content attributes of each FileAttachment, do this instead:
for a in attachments:
print(a.name, a.content)

Related

Python imaplib can't select() custom gmail labels

I have a bot I'm writing using imaplib in python to fetch emails from gmail and output some useful data from them. I've hit a snag on selecting the inbox, though; the existing sorting system uses custom labels to separate emails from different customers. I've partially replicated this system in my test email, but imaplib.select() throws a "imaplib.IMAP4.error: SELECT command error: BAD [b'Could not parse command']" with custom labels. Screenshot attatched My bot has no problem with the default gmail folders, fetching INBOX or [Gmail]/Spam. In that case, it hits an error later in the code that deals with completely different problem I have yet to fix. The point, though, is that imaplib.select() is succsessful with default inboxes and just not custom labels.
The way my code works is it works through all the available inboxes, compares it to a user-inputted name, and if they match, saves the name and sets a boolean to true to signal that it found a match. It then checks, if there was a match (the user-inputted inbox exists) it goes ahead, otherwise it throws an error message and resets. It then attempts to select the inbox the user entered.
I've verified that the variable the program's saving the inbox name to matches what's listed as the name in the imap.list() command. I have no idea what the issue is.
I could bypass the process by iterating through all mail to find the email's I'm looking for, but it's far more efficient to use the existing sorting system due to the sheer number of emails on the account I'll be using.
Any help is appreciated!
EDIT: Code attached after request. Thank you to the person who told me to do so.
'''
Fetches emails from the specified inbox and outputs them to a popup
'''
def fetchEmails(self):
#create an imap object. Must be local otherwise we can only establish a single connection
#imap states are kinda bad
imap = imaplib.IMAP4_SSL(host="imap.gmail.com", port="993")
#Login and fetch a list of available inboxes
imap.login(username.get(), password.get())
type, inboxList = imap.list()
#Set a reference boolean and iterate through the list
inboxNameExists = False
for i in inboxList:
#Finds the name of the inbox
name = self.inboxNameParser(i.decode())
#If the given inbox name is encountered, set its existence to true and break
if name.casefold().__eq__(inboxName.get().casefold()):
inboxNameExists = True
break
#If the inbox name does not exist, break and give error message
if inboxNameExists != True:
self.logout(imap)
tk.messagebox.showerror("Disconnected!", "That Inbox does not exist.")
return
'''
If/else to correctly feed the imap.select() method the inbox name
Apparently inboxes containing spaces require quoations before and after
Selects the inbox and pushes it to a variable
two actually but the first is unnecessary(?)
imap is weird
'''
if(name.count(" ") > 0):
status, messages = imap.select("\"" + name + "\"")
else:
status, messages = imap.select(name);
#Int containing total number of emails in inbox
messages = int(messages[0])
#If there are no messages disconnect and show an infobox
if messages == 0:
self.logout(imap)
tk.messagebox.showinfo("Disconnected!", "The inbox is empty.")
self.mailboxLoop(imap, messages)
Figured the issue out after a few hours banging through it with a friend. As it turns out the problem was that imap.select() wants quotations around the mailbox name if it contains spaces. So imap.select("INBOX") is fine, but with spaces you'd need imap.select("\"" + "Label Name" + "\"")
You can see this reflected in the code I posted with the last if/else statement.
Python imaplib requires mailbox names with spaces to be surrounded by apostrophes. So imap.select("INBOX") is fine, but with spaces you'd need imap.select("\"" + "Label Name" + "\"").

Gmail API: Python Email Dict appears to be Missing Keys

I'm experiencing a strange issue that seems to be inconsistent with google's gmail API:
If you look here, you can see that gmail's representation of an email has keys "snippet" and "id", among others. Here's some code that I use to generate the complete list of all my emails:
response = service.users().messages().list(userId='me').execute()
messageList = []
messageList.extend(response['messages'])
while 'nextPageToken' in response:
pagetoken = response['nextPageToken']
response = service.users().messages().list(userId='me', pageToken=pagetoken).execute()
messageList.extend(response['messages'])
for message in messageList:
if 'snippet' in message:
print(message['snippet'])
else:
print("FALSE")
The code works!... Except for the fact that I get output "FALSE" for every single one of the emails. 'snippet' doesn't exist! However, if I run the same code with "id" instead of snippet, I get a whole bunch of ids!
I decided to just print out the 'message' objects/dicts themselves, and each one only had an "id" and a "threadId", even though the API claims there should be more in the object... What gives?
Thanks for your help!
As #jedwards said in his comment, just because a message 'can' contain all of the fields specified in documentation, doesn't mean it will. 'list' provides the bare minimum amount of information for each message, because it provides a lot of messages and wants to be as lazy as possible. For individual messages that I want to know more about, I'd then use 'messages.get' with the id that I got from 'list'.
Running get for each email in your inbox seems very expensive, but to my knowledge there's no way to run a batch 'get' command.

Looping through to create a tuple

I'm attempting to implement the mass mail send out.
Here is the mass mail doc: Just a link to the Django Docs
In order to achieve this I need create this tuple:
datatuple = (
('Subject', 'Message.', 'from#example.com', ['john#example.com']),
('Subject', 'Message.', 'from#example.com', ['jane#example.com']),
)
I query the ORM for some recipients details. Then I would imagine there's some looping involved, each time adding another recipient to the tuple. All elements of the message are the same except for username and email.
So far I have:
recipients = notification.objects.all().values_list('username','email')
# this returns [(u'John', u'john#example.com'), (u'Jane', u'jane#example.com')]
for recipient in recipients:
to = recipient[1] #access the email
subject = "my big tuple loop"
dear = recipient[0] #access the name
message = "This concerns tuples!"
#### add each recipient to datatuple
send_mass_mail(datatuple)
I've been trying something along the lines of this :
SO- tuple from a string and a list of strings
If I understand correctly, this is pretty simple with a comprehension.
emails = [
(u'Subject', u'Message.', u'from#example.com', [address])
for name, address in recipients
]
send_mass_mail(emails)
Note that we leverage Python's ability to unpack tuples into a set of named variables. For each element of recipients, we assign its zeroth element to name and its first element to address. So in the first iteration, name is u'John' and address is u'john#example.com'.
If you need to vary the 'Message.' based on the name, you can use string formatting or any other formatting/templating mechanism of your choice to generate the message:
emails = [
(u'Subject', u'Dear {}, Message.'.format(name), u'from#example.com', [address])
for name, address in recipients
]
Since the above are list comprehensions, they result in emails being a list. If you really need this to be a tuple instead of a list, that's easy, too:
emails = tuple(
(u'Subject', u'Message.', u'from#example.com', [address])
for name, address in recipients
)
For this one, we're actually passing a generator object into the tuple constructor. This has the performance benefits of using a generator without the overhead of creating an intermediate list. You can do that pretty much anywhere in Python where an iterable argument is accepted.
Just a little bit of cleanup needed here:
1) actually build the tuple in the loop (this is a bit tricky since you need the extra comma to ensure that a tuple is appended and not the values from the tuple)
2) move the send_mass_mail call outside the loop
This should be working code:
recipients = notification.objects.all().values_list('username','email')
# this returns [(u'John', u'john#example.com'), (u'Jane', u'jane#example.com')]
datatuple = []
for recipient in recipients:
to = recipient[1] #access the email
subject = "my big tuple loop"
dear = recipient[0] #access the name
message = "This concerns tuples!"
#### add each recipient to datatuple
datatuple.append((subject, message, "from#example.com", [to,]),)
send_mass_mail(tuple(datatuple))
EDIT:
jpmc26's technique is definitely more efficient, and if you're planning to have a large email list to send to you should use that. Most likely you should use whichever code makes the most sense to you personally so that when your requirements change you can easily understand how to update.

How to obtain the recipient list from email using IMAPClient in Python

I am using the IMAPClient library in Python. I am able to download the attached document in the email. I am interested in only Excel files.
I am interested to extract the recipient list from the email. Any idea how to do it in Python ?
Here is the code snippet which might be useful
for ind_mail in emails:
msg_string = ind_mail['RFC822'].decode("utf-8")
#print(msg_string.decode("utf-8"))
email_msg = email.message_from_string(msg_string)
for part in email_msg.walk():
# Download only Excel File
filetype = part.get_content_type()
if(filetype == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'):
#download
The straightforward answer to your question is to get the corresponding headers' values, i.e.:
to_rcpt = email_msg.get_all('to', [])
cc_rcpt = email_msg.get_all('cc', [])
, inside that first loop. The MIME standard doesn't enforce uniqueness on the headers (though strongly suggests it), thus get_all; if not present, you'll still have an empty list for a consecutive loop.
But as tripleee has rightfully pointed out, the mime headers can be easily censored, spoofed or simply removed.
Yet this is the only info persisted and returned by a server, and all mail clients use to present to us :)
Calling msg.get_all will return a list containing one entry per one header, so if you have multiple header, you'll get a list per header
BUT
If one header has multiple emails in a coma-separated way, you will only get one string and you'll have to split it.
The best way to have the list of all the emails from a specific header is to use getaddresses (https://docs.python.org/3/library/email.utils.html#email.utils.getaddresses)
from email.utils import getaddresses
to_rcpt = getaddresses(email_msg.get_all('to', []))
get_all will return an array of all the "To:" headers, and getaddresses will parse each entry and return as many emails as present on each headers. For instance:
message = """
To: "Bob" <email1#gmail.com>, "John" <email2#gmail.com>
To: email3#gmail.com, email4#gmail.com
"""
to_rcpt = getaddresses(email_msg.get_all('to', []))
=> [('Bob', 'email1#gmail.com'), ('John', 'email2#gmail.com'), ('', 'email3#gmail.com'), ('', 'email4#gmail.com')]

Send mail with python using bcc

I'm working with django, i need send a mail to many emails, i want to do this with a high level library like python-mailer, but i need use bcc field, any suggestions?
You should look at the EmailMessage class inside of django, supports the bcc.
Complete docs availble here:
http://docs.djangoproject.com/en/dev/topics/email/#the-emailmessage-class
Quick overview:
The EmailMessage class is initialized with the following parameters (in the given order, if positional arguments are used). All parameters are optional and can be set at any time prior to calling the send() method.
subject: The subject line of the e-mail.
body: The body text. This should be a plain text message.
from_email: The sender's address. Both fred#example.com and Fred forms are legal. If omitted, the DEFAULT_FROM_EMAIL setting is used.
to: A list or tuple of recipient addresses.
bcc: A list or tuple of addresses used in the "Bcc" header when sending the e-mail.
connection: An e-mail backend instance. Use this parameter if you want to use the same connection for multiple messages. If omitted, a new connection is created when send() is called.
attachments: A list of attachments to put on the message. These can be either email.MIMEBase.MIMEBase instances, or (filename, content, mimetype) triples.
headers: A dictionary of extra headers to put on the message. The keys are the header name, values are the header values. It's up to the caller to ensure header names and values are in the correct format for an e-mail message.

Categories

Resources