I am trying to use flufl.bounce to scan emails downloaded with poplib and detect bounced e-mail addresses. So far, what I'm getting is a lot of empty sets. Here is some sample code:
import getpass, poplib, email
from flufl.bounce import scan_message
user = 'redacted#redacted.com'
mail = poplib.POP3_SSL('redacted.redacted.com', '995')
mail.user(user)
mail.pass_('redacted')
num_messages = len(mail.list()[1])
for i in range(num_messages):
for msg in mail.retr(i+1)[1]:
msg = email.message_from_string(msg)
bounce = scan_message(msg)
print bounce
mail.quit()
And print bounce is giving me an empty set:
set([])
There are various types of bounce messages in this mailbox, and I can even select one with mail.retr that I know is a bounce message, but when I feed it into scan_message, I still get an empty set back. What am I doing wrong? The flufl.bounce docs don't seem to be very helpful here.
OK, I figured it out.msg is a list of the elements of the e-mail. So, instead of iterating through mail.retr(i+1)[1], I had to join it together with \n before feeding it into email.message_from_string() which gave me a proper message that scan_message could use. Here's the working code
import getpass, poplib, email
from flufl.bounce import scan_message
user = 'redacted#redacted.com'
mail = poplib.POP3_SSL('mail.redacted.com', '995')
mail.user(user)
mail.pass_('redacted')
num_messages = len(mail.list()[1])
for i in range(num_messages):
x = mail.retr(i+1)[1]
msg = email.message_from_string("\n".join(x))
bounce = scan_message(msg)
print bounce
mail.quit()
Related
I wrote an email-sending program in Python. The idea is to just add Excel data to the emails, press one button, and send the email to everyone.
Now, I got everything done, but one thing is failing: it actually reads only one email at a time. If I write the emails horizontally in different splits, it combines them into one email but only sends it to the first one. If I write it vertically, it only reads one email at a time; that means I have to change the program every time, moving or deleting the highest one (vertical) so it will send the email to the next one in the row. My goal is for it to read every email and send it to every individual.
This is my code:
import smtplib, ssl
import pandas as pd
from email.message import EmailMessage
sender = "..."
password = "..."
subject = "..."
bodymessage = "..."
context = ssl.create_default_context()
server = smtplib.SMTP_SSL("smtp.gmail.com", 465, context = context)
server.login(sender, password)
with open("C:\\Users\\Lol\\Desk\\Python bulk emails\\Maily.csv", "r", encoding="UTF8") as csvfile:
datareader = df = pd.read_csv(csvfile)
for row in datareader:
em = EmailMessage()
em["From"] = sender
em["to"] = row
em["subject"] = subject
em.set_content(bodymessage)
server.send_message(em)
print("The message sent")
server.close()
print("Done")
I've tried using different encodings (Latin-1, UTF-16, and UTF-8); UTF-8 was the only one that worked; also, I tried different readers (csv_reader and pandas); now I am using pandas. It might help to let him read the data in chunks (pd.read_csv("sample.csv", chunksize=${2:10)), but I don't know how to install that, because every time I tried this, it said: "It has to be an integer".
I'm sending emails with pyramid_mailer and found this weird issue that when I use Office365 as my SMTP server it adds random = characters into my message. I don't get that issue with any other mail server (I tested this with gmail and also with my own postfix server)
I send emails like below:
from pyramid_mailer.mailer import Mailer
from pyramid_mailer.message import Attachment, Message
mailer = Mailer()
mailer.smtp_mailer.hostname = "test.mail.at.office365"
mailer.smtp_mailer.username = "my_user"
mailer.smtp_mailer.password = "secret"
mailer.smtp_mailer.port = 587
mailer.smtp_mailer.tls = True
message = Message(
subject="Test",
sender="my_user#my_domain.com",
recipients="test_user#test_domain.com",
body="very long text, at least 75 characters long so Office 365 will break it and insert annoying '=' into message",
html="very long text, at least 75 characters long so Office 365 will break it and insert annoying '=' into message",
)
mailer.send_immediately(message)
I searched on google and found this has something to do with line breaks and Transfer-Content-Encoding. And indeed, if I add \r\n every ~50 characters I won't see = added. But the problem is that I might want to send a hyperlink that will be longer than that...
Pyramid documentation (https://docs.pylonsproject.org/projects/pyramid_mailer/en/latest/) says I can use Attachment rather than plain string. And indeed as soon as I do that I can set this Transfer-Content-Encoding to something like base64 (as suggested here: https://jeremytunnell.com/2009/01/04/really-hairy-problem-with-seemingly-random-crlf-and-spaces-inserted-in-emails/) but my message then shows as attachment, not as regular message...
There seems to be no way to add this Transfer-Content-Encoding to Message object... I tried to use Message.extra_headers = {'Transfer-Content-Encoding': 'base64'} but this did not help.
I'm totally out of ideas, would appreciate any help...
-- Edit --
Thanks to answer below from #Mess:
from pyramid_mailer.message import Attachment
my_message = "very long text, at least 75 characters long so Office 365 will break it and insert annoying '=' into message"
body_html = Attachment(data=my_message, transfer_encoding="base64", disposition='inline')
body_text = Attachment(data=my_message, transfer_encoding="base64", disposition='inline')
Then pass body_html and body_text to Message constructor.
This is "Content-Disposition" header you need to set to control how the content is available to the recipient.
Set it to "attachment" to let download the file, use "inline" to be able to include the content, for example a logo, directly to your email, etc:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
I hope it will point you to the right direction.
EDIT:
Using pyramid_mailer package it would be something like:
from pyramid_mailer.message import Attachment
attachment = Attachment(data=some_data, transfer_encoding="base64", disposition='inline')
I am trying to write a python script to send an email that uses html formatting and involves a lot of non-breaking spaces. However, when I run it, some of the   strings are interrupted by spaces that occur every 171 characters, as can be seen by this example:
#!/usr/bin/env python
import smtplib
import socket
from email.mime.text import MIMEText
emails = ["my#email.com"]
sender = "test#{0}".format(socket.gethostname())
message = "<html><head></head><body>"
for i in range(20):
message += " " * 50
message += "<br/>"
message += "</body>"
message = MIMEText(message, "html")
message["Subject"] = "Test"
message["From"] = sender
message["To"] = ", ".join(emails)
mailer = smtplib.SMTP("localhost")
mailer.sendmail(sender, emails, message.as_string())
mailer.quit()
The example should produce a blank email that consists of only spaces, but it ends up looking something like this:
  ;
&nb sp;
& nbsp;
&nbs p;
&n bsp;
Edit: In case it is important, I am running Ubuntu 15.04 with Postfix for the smtp client, and using python2.6.
I can replicate this in a way but my line breaks come every 999 characters. RFC 821 says maximum length of a line is 1000 characters including the line break so that's probably why.
This post gives a different way to send a html email in python, and i believe the mime type "multipart/alternative" is the correct way.
Sending HTML email using Python
I'm the developer of yagmail, a package that tries to make it easy to send emails.
You can use the following code:
import yagmail
yag = yagmail.SMTP('me#gmail.com', 'mypassword')
for i in range(20):
message += " " * 50
message += "<br/>"
yag.send(contents = message)
Note that by default it will send a HTML message, and that it also adds automatically the alternative part for non HTML browsers.
Also, note that omitting the subject will leave an empty subject, and without a to argument it will send it to self.
Furthermore, note that if you set yagmail up correctly, you can just login using yag.SMTP(), without having to have username & password in the script (while still being secure). Omitting the password will prompt a getpass.
Adding an attachment is as simple as pointing to a local file, e.g.:
yag.send(contents = [message, 'previously a lot of whitespace', '/local/path/file.zip']
Awesome isn't it? Thanks for the allowing me to show a nice use case for yagmail :)
If you have any feature requests, issues or ideas please let me know at github.
Our office uses 2 IMAP servers for e-mail, one is the incoming server and holds the recent e-mails and the other is an archive server. We mainly use Outlook 2010 and our current process is to periodically drag sent messages from the incoming server to the archive.
Today I was asked into looking into writing a script and that would periodically (probably using crontab) grab all sent messages and move them to archive.
I've looked into some example of SSL or telnet to access the server and poke around. However I don't know the best way to script this or how to move files cross server within the IMAP environment.
What's the best way to accomplish this? I'd prefer to use Python just from comfort level, but if there is already an existing solution in another language, I could deal with it.
Update:
Ok, here's some code. Currently It copies the messages just fine, however, it will duplicate exisiting messages on the archive server.
import imaplib
import sys
#copy from
f_server = 'some.secret.ip.address'
f_username = 'j#example.com'
f_password = 'password'
f_box_name = 'Sent Messages'
#copy to
t_server = 'archive.server.i.p'
t_username = 'username'
t_password = 'password'
t_box_name = 'test'
To = imaplib.IMAP4(t_server)
To.login(t_username, t_password)
print 'Logged into mail server'
From = imaplib.IMAP4(f_server)
From.login(f_username, f_password)
print 'Logged into archive'
From.select(f_box_name) #open box which will have its contents copied
print 'Fetching messages...'
typ, data = From.search(None, 'ALL') #get all messages in the box
msgs = data[0].split()
sys.stdout.write(" ".join(['Copying', str(len(msgs)), 'messages']))
for num in msgs: #iterate over each messages id number
typ, data = From.fetch(num, '(RFC822)')
sys.stdout.write('.')
To.append(t_box_name, None, None, data[0][1]) #add a copy of the message to the archive box specified above
sys.stdout.write('\n')
try:
From.close()
From.logout()
try:
To.close()
To.logout()
Some sources:
Doug Hellman's Blog: imaplib - IMAP4 Client Library
Tyler Lesmann's Blog: Copying IMAP Mailboxes with Python and imaplib
I still need to:
delete/expunge messages on the live server
not copy duplicates (actually this would be fixed by deleting originals after copying, but...)
error trapping
Update 2:
Anyone have any ideas on how to not create duplicates when copying? (excluding the option of deleting originals, for now) I thought about searching text, but realized nested replies could throw that off.
Here's what I ended up using. I don't claim that it's perfect, the way it uses msg_num and not id is a little risky. But this is fairly low volume moves, maybe a couple an hour (on cron).
import imaplib
#copy from
from_server = {'server': '1.1.1.1',
'username': 'j#example.com',
'password': 'pass',
'box_names': ['Sent', 'Sent Messages']}
#copy to
to_server = {'server': '2.2.2.2',
'username': 'archive',
'password': 'password',
'box_name': 'Sent'}
def connect_server(server):
conn = imaplib.IMAP4(server['server'])
conn.login(server['username'], server['password'])
print 'Logged into mail server # %s' % server['server']
return conn
def disconnect_server(server_conn):
out = server_conn.logout()
if __name__ == '__main__':
From = connect_server(from_server)
To = connect_server(to_server)
for box in from_server['box_names']:
box_select = From.select(box, readonly = False) #open box which will have its contents copied
print 'Fetching messages from \'%s\'...' % box
resp, items = From.search(None, 'ALL') #get all messages in the box
msg_nums = items[0].split()
print '%s messages to archive' % len(msg_nums)
for msg_num in msg_nums:
resp, data = From.fetch(msg_num, "(FLAGS INTERNALDATE BODY.PEEK[])") # get email
message = data[0][1]
flags = imaplib.ParseFlags(data[0][0]) # get flags
flag_str = " ".join(flags)
date = imaplib.Time2Internaldate(imaplib.Internaldate2tuple(data[0][0])) #get date
copy_result = To.append(to_server['box_name'], flag_str, date, message) # copy to archive
if copy_result[0] == 'OK':
del_msg = From.store(msg_num, '+FLAGS', '\\Deleted') # mark for deletion
ex = From.expunge() # delete marked
print 'expunge status: %s' % ex[0]
if not ex[1][0]: # result can be ['OK', [None]] if no messages need to be deleted
print 'expunge count: 0'
else:
print 'expunge count: %s' % len(ex[1])
disconnect_server(From)
disconnect_server(To)
I'm not sure what volume of messages you're dealing with, but you could extract the Message-ID from each one and use that to find out if it's a duplicate. Either generate a list of IDs already on the target server each time you prepare to move messages, or add them to a simple database as they are archived.
You could narrow things down by an additional message property like Date if the lookups are too expensive, then drop the older lists when you no longer need them.
Presumably too late to be helpful to the OP, but hopefully useful for anyone following along after now.
This looks like a generic requirement. You probably shouldn't be custom coding anything.
You would probably be better off using an MTA configured to send copies of everything to an archive as well as sending stuff to your IMAP server. If this is hard for you to set up, consider using a third party service, who would manage your archives, and forward mail on to your existing mail server.
If you really do want to do this by copying from IMAP, I'd suggest looking at offlineimap.
If you really do want to do it yourself, the way to track the messages you've already seen is by using the Message-ID header.
I have a python script that has to fetch unseen messages, process it, and mark as seen (or read)
I do this after login in:
typ, data = self.server.imap_server.search(None, '(UNSEEN)')
for num in data[0].split():
print "Mensage " + str(num) + " mark"
self.server.imap_server.store(num, '+FLAGS', '(SEEN)')
The first problem is that, the search returns ALL messages, and not only the UNSEEN.
The second problem is that messages are not marked as SEEN.
Can anybody give me a hand with this?
Thanks!
import imaplib
obj = imaplib.IMAP4_SSL('imap.gmail.com', '993')
obj.login('user', 'password')
obj.select('Inbox') <--- it will select inbox
typ ,data = obj.search(None,'UnSeen')
obj.store(data[0].replace(' ',','),'+FLAGS','\Seen')
I think the flag names need to start with a backslash, eg: \SEEN
I am not so familiar with the imaplib but I implement this well with the imapclient module
import imapclient,pyzmail,html2text
from backports import ssl
context=ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
iobj=imapclient.IMAPClient('outlook.office365.com', ssl=True, ssl_context=context)
iobj.login(uname,pwd)# provide your username and password
iobj.select_folder('INBOX',readonly=True)# Selecting Inbox.
unread=iobj.search('UNSEEN')# Selecting Unread messages, you can add more search criteria here to suit your purpose.'FROM', 'SINCE' etc.
print('There are: ',len(unread),' UNREAD emails')
for i in unread:
mail=iobj.fetch(i,['BODY[]'])#I'm fetching the body of the email here.
mcontent=pyzmail.PyzMessage.factory(mail[i][b'BODY[]'])#This returns the email content in HTML format
subject=mcontent.get_subject()# You might not need this
receiver_name,receiver_email=mcontent.get_address('from')
mail_body=html2text.html2text(mcontent.html_part.get_payload().decode(mcontent.html_part.charset))# This returns the email content as text that you can easily relate with.
Let's say I want to just go through the unread emails, reply the sender and mark the email as read. I'd call the smtp function from here to compose and send a reply.
import smtplib
smtpobj=smtplib.SMTP('smtp.office365.com',587)
smtpobj.starttls()
smtpobj.login(uname,pwd)# Your username and password goes here.
sub='Subject: '+str(subject)+'\n\n'# Subject of your reply
msg='Thanks for your email! You're qualified for the next round' #Some random reply :(
fullmsg=sub+new_result
smtpobj.sendmail(uname,test,fullmsg)# This sends the email.
iobj.set_flags(i,['\\Seen','\\Answered'])# This marks the email as read and adds the answered flag
iobj.append('Sent Items', fullmsg)# This puts a copy of your reply in your Sent Items.
iobj.logout()
smtpobj.logout()
I hope this helps