Im trying to upload a folder full of emails to Salesforce, one email per case. The script does everything fine, but the emails it uploads are totally blank, and are 0 bytes.
Im pretty sure its something to do with the base64 encoding required to upload attachments, but I've tried everything to make it work.
I read this Uploading Attachments to Salesforce API via Beatbox, Python, which was helpful, but I cannot solve the problem.
Any help will be greatly appreciated.
Here is my code:
import wx
import email.parser
import os
import re
#import time
import beatbox
import base64
def browse(parent = None, message = 'Browse for the dealsheet folder'):
app = wx.App()
dialog = wx.DirDialog(None, "Choose a directory:",style=wx.DD_DEFAULT_STYLE | wx.DD_NEW_DIR_BUTTON)
if dialog.ShowModal() == wx.ID_OK:
return dialog.GetPath()
dialog.Destroy()
service = beatbox.PythonClient()
service.serverUrl = 'https://login.salesforce.com/services/Soap/u/20.0'
loginresponse = service.login('USERNAME', 'PASSWORD')
filespath = browse()
files = os.listdir(filespath)
global bodytext
global subject
global toAdd
global uploadfile
uploadfile = ''
for f in files:
with open(filespath + '\\' + f, 'rb') as emailfile:
message = email.message_from_file(emailfile)
uploadfile = base64.b64encode(emailfile.read())
subject = str(message.get('subject'))
toAdd = str(message.get('to'))
toAdd = re.findall(r'[\w\-][\w\-\.]+#[\w\-][\w\-\.]+[a-zA-Z]{1,4}', toAdd)[0]
toAdd = toAdd[:-10] # cut the email bits
toAdd = toAdd.replace('.', ' ')
toAdd = toAdd.title()
print toAdd
for part in message.walk():
if part.get_content_type() == 'text/plain':
bodytext = part.get_payload()
bodytext = str(bodytext)
bodytext = unicode(bodytext, errors = 'replace')
# print bodytext
# LOG CASE
details = {'type': 'Case', 'Reason': 'SFDC Admin', 'Origin': 'Email', 'Status': 'Awaiting Analysis', 'Description': ''}
result = service.create(details)
print 'log result = ', result[0]['success']
#TRIAGE CASE
details_triage = {'type': 'Case', 'id': result[0]['id'], 'Case_Triaged__c': 'True'}
triage_result = service.update(details_triage)
print 'triage result = ', triage_result[0]['success']
url = 'https://naX.salesforce.com/{}'.format(triage_result[0]['id'])
print url
attachement_dict = {'type': 'Attachment', 'ParentId': triage_result[0]['id'], 'name': f, 'Body': uploadfile }
if result[0]['success'] == True:
res = service.create(attachement_dict)
print res
Related
I have a script to send emails using Gmail's API:
if __name__ == '__main__':
service = get_service()
user_id = 'me'
sender = 'myemail#gmail.com'
recipients_list = ['to#gmail.com']
var1 = input('Type whatever here: ')
subject = f'Reasonable subject in {var1}'
body = f'<h1>Hello World<\h1><p>{var1}'
attached_file = r'C:\Somefile.pdf'
for item in recipients_list:
msg = create_message_with_attachment(sender, subject=subject, body=body, file=attached_file, to=item)
send_message(service, user_id, msg)
I've managed to get the string of the variable body from a .txt file using Pathlib but I can't figure out how to make so the email sent in interpreted as HTML as well as var1 be interpreted as a variable instead of part of the sting. How could I achieve this?
EDIT:
Sorry, I realize I've misexplained. I meant the HTML part is fine, but the .txt file with the body of the email includes variables (i.e. var1) that I need to be interpreted as such instead of part of the string.
I.e.:
Hello World
How can make this a {var1}
Also here's the function that creates the email:
def create_message_with_attachment(sender, to, subject, body, file):
message = MIMEMultipart()
message['to'] = to
message['from'] = sender
message['subject'] = subject
msg = MIMEText(body, 'html')
message.attach(msg)
(content_type, encoding) = mimetypes.guess_type(file)
if content_type is None or encoding is not None:
content_type = 'application/octet-stream'
(main_type, sub_type) = content_type.split('/', 1)
if main_type == 'text':
with open(file, 'rb') as f:
msg = MIMEText(f.read().decode('utf-8'), _subtype=sub_type)
elif main_type == 'image':
with open(file, 'rb') as f:
msg = MIMEImage(f.read(), _subtype=sub_type)
elif main_type == 'audio':
with open(file, 'rb') as f:
msg = MIMEAudio(f.read(), _subtype=sub_type)
else:
with open(file, 'rb') as f:
msg = MIMEBase(main_type, sub_type)
msg.set_payload(f.read())
filename = os.path.basename(file)
msg.add_header('Content-Disposition', 'attachment', filename=filename)
email.encoders.encode_base64(msg)
message.attach(msg)
raw_msg = base64.urlsafe_b64encode(message.as_string().encode('utf-8'))
return {'raw': raw_msg.decode('utf-8')}
When creating the message, you should add an additional html parameter to the MIMEText class.
Therefore, your code will look something like this
subject = 'Reasonable subject in' + var2
body = '<h1>Hello World<\h1>'+ var1
And when creating the email message, it should have a structure similar to this - notice the addition of the html parameter.
message = MIMEText(message, 'html')
message['to'] = to
message['from'] = sender
message['subject'] = subject
encodedmsg = urlsafe_b64encode(bytes(message))
result = {
'raw': encodedmsg.decode()
}
Reference
Python email: Examples.
The following code uses imap to find emails by subject line and returns all parts of the email and downloads the attachments. However i am ONLY needing it to download the attachments of the email not the entire body also. I understand this has to do with the for part in email_message.walk(): that is iterating the entire email. Could someone please help me have this code download only the attachment of the email? Im sure this is a simple code change but im just not sure how to make it!
import imaplib
import email.header
import os
import sys
import csv
# Your IMAP Settings
host = 'imap.gmail.com'
user = 'User email'
password = 'User password'
# Connect to the server
print('Connecting to ' + host)
mailBox = imaplib.IMAP4_SSL(host)
# Login to our account
mailBox.login(user, password)
boxList = mailBox.list()
# print(boxList)
mailBox.select()
searchQuery = '(SUBJECT "CDR Schedule output from schedule: This is a test to see how it works")'
result, data = mailBox.uid('search', None, searchQuery)
ids = data[0]
# list of uids
id_list = ids.split()
i = len(id_list)
for x in range(i):
latest_email_uid = id_list[x]
# fetch the email body (RFC822) for the given ID
result, email_data = mailBox.uid('fetch', latest_email_uid, '(RFC822)')
# I think I am fetching a bit too much here...
raw_email = email_data[0][1]
# converts byte literal to string removing b''
raw_email_string = raw_email.decode('utf-8')
email_message = email.message_from_string(raw_email_string)
# downloading attachments
for part in email_message.walk():
if part.get_content_maintype() == 'multipart':
continue
if part.get('Content-Disposition') is None:
continue
fileName = part.get_filename()
if bool(fileName):
filePath = os.path.join('C:/install files/', fileName)
if not os.path.isfile(filePath) :
fp = open(filePath, 'wb')
fp.write(part.get_payload(decode=True))
fp.close()
subject = str(email_message).split("Subject: ", 1)[1].split("\nTo:", 1)[0]
print('Downloaded "{file}" from email titled "{subject}" with UID {uid}.'.format(file=fileName, subject=subject, uid=latest_email_uid.decode('utf-8')))
mailBox.close()
mailBox.logout()
I am new to AWS Lambda function.
I wanted to send email to multiple recipients. I am able to send email to single email address but not multiple email ids and shows error.
I just refered the amazon documentation page and wrote the below code.
I am using environmental variable runteam and it has values like ['aaa#xyz.com','bbb#xyz.com','ccc#xyz.com']
import boto3
import os
import os, sys, subprocess
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
def lambda_handler(event,context):
ses = boto3.client("ses")
s3 = boto3.client("s3")
runemail = os.environ['runteam']
for i in event["Records"]:
action = i["eventName"]
#ip = i["requestParameters"]["soruceIPAddress"]
bucket_name = i["s3"]["bucket"]["name"]
object = i["s3"]["object"]["key"]
fileObj = s3.get_object(Bucket = bucket_name, Key = object)
file_content = fileObj["Body"].read()
sender = "test#xyz.com"
to = runemail
subject = str(action) + 'Event from ' + bucket_name
body = """
<br>
This email is to notify regarding {} event
This object {} is created
""".format(action,object)
msg = MIMEMultipart('alternative')
msg["Subject"] = subject
msg["From"] = sender
msg["To"] = ', '.join(runemail)
body_txt = MIMEText(body, "html")
attachment = MIMEApplication(file_content)
attachment.add_header("Content-Disposition","attachment", filename = "ErrorLog.txt")
msg.attach(body_txt)
msg.attach(attachment)
response = ses.send_raw_email(Source = sender, Destinations = rumemail, RawMessage = {"Data": msg.as_string()})
return "Thanks"
I think everything seems to be right regarding the email sending code. The error lies in your program where the way you store your environ variable.
It should be stored as runteam="aaa#xyz.com bbb#xyz.com ccc#xyz.com" (notice the space between each email)
Then use this variable as
rumemail = os.environ['runteam'].split()
msg["To"] = ', '.join(runemail)
response = ses.send_raw_email(Source = sender, Destinations = rumemail, RawMessage = {"Data": msg.as_string()})
This line:
msg["To"] = ', '.join(runemail)
is expecting a Python list, not a string. I suggest you add a debug line after it to see what you are actually sending the system.
I would recommending passing your environment variable as:
person#address.com, person2#address.com, person3#address.com
Then, use:
msg["To"] = runemail
Removing/commenting out the following line should fix the issue:
msg["To"] = ', '.join(runemail)
By the above line, you are converting a list to a string. However, Destinations attribute is looking for a list.
I am wondering how to get pure text form python email using imaplib.
What i have so far:
from datetime import datetime
import imaplib ,email
IMAP_SERVER = 'imap.gmail.com'
EMAIL_ACCOUNT = "example#gmail.com"
PASSWORD = "password"
rv, data = M.search(None, "ALL")
if rv != 'OK':
print("No messages found!")
return
if data != ['']: # if not empty list means messages exist
for num in data[0].split():
rv, data = M.fetch(num, '(RFC822)') #(BODY[HEADER.FIELDS (SUBJECT FROM)])
if rv != 'OK':
print("ERROR getting message", num)
return
message = email.message_from_bytes(data[0][1])
text = ""
if message.is_multipart():
for payload in message.get_payload():
text = payload.get_payload()
else:
text = message.get_payload()
res = {
'From': email.utils.parseaddr(message['From'])[1],
'From name': email.utils.parseaddr(message['From'])[0],
'Time': datetime.fromtimestamp(email.utils.mktime_tz(email.utils.parsedate_tz(message['Date']))),
'To': message['To'],
'Subject': email.header.decode_header(message["Subject"])[0][0],
'Text': text
}
print(res['Text'])
else:
print("Nothing to work with.")
If i do it this way, the code works, but i get
<div dir="ltr">test 3 body</div>
as an output.
Is there any way to get purely "test 3 body" out?
Look for the plain text part of the email message.
for payload in message.walk():
if payload.get_content_type().lower() == 'text/plain':
print(payload.get_payload())
If you just stack on removing html tags from string you have to use regular expression like here:
import re
s = '<div dir="ltr">test 3 body</div>'
print(re.sub('<[^<]+?>', '', s))
Output: test 3 body
s has to be your res['Text'].
I'd like to fetch the whole message from IMAP4 server.
In python docs if found this bit of code that works:
>>> t, data = M.fetch('1', '(RFC822)')
>>> body = data[0][1]
I'm wondering if I can always trust that data[0][1] returns the body of the message. When I've run 'RFC822.SIZE' I've got just a string instead of a tuple.
I've skimmed through rfc1730 but I wasn't able to figure out the proper response structure for the 'RFC822'. It is also hard to tell the fetch result structure from imaplib documentation.
Here is what I'm getting when fetching RFC822:
('OK', [('1 (RFC822 {858569}', 'body of the message', ')')])
But when I fetch RFC822.SIZE I'm getting:
('OK', ['1 (RFC822.SIZE 847403)'])
How should I properly handle the data[0] list?
Can I trust that when it is a list of tuples the tuples has exactly 3 parts and the second part is the payload?
Maybe you know any better library for imap4?
No... imaplib is a pretty good library, it's imap that's so unintelligible.
You may wish to check that t == 'OK', but data[0][1] works as expected for as much as I've used it.
Here's a quick example I use to extract signed certificates I've received by email, not bomb-proof, but suits my purposes:
import getpass, os, imaplib, email
from OpenSSL.crypto import load_certificate, FILETYPE_PEM
def getMsgs(servername="myimapserverfqdn"):
usernm = getpass.getuser()
passwd = getpass.getpass()
subject = 'Your SSL Certificate'
conn = imaplib.IMAP4_SSL(servername)
conn.login(usernm,passwd)
conn.select('Inbox')
typ, data = conn.search(None,'(UNSEEN SUBJECT "%s")' % subject)
for num in data[0].split():
typ, data = conn.fetch(num,'(RFC822)')
msg = email.message_from_string(data[0][1])
typ, data = conn.store(num,'-FLAGS','\\Seen')
yield msg
def getAttachment(msg,check):
for part in msg.walk():
if part.get_content_type() == 'application/octet-stream':
if check(part.get_filename()):
return part.get_payload(decode=1)
if __name__ == '__main__':
for msg in getMsgs():
payload = getAttachment(msg,lambda x: x.endswith('.pem'))
if not payload:
continue
try:
cert = load_certificate(FILETYPE_PEM,payload)
except:
cert = None
if cert:
cn = cert.get_subject().commonName
filename = "%s.pem" % cn
if not os.path.exists(filename):
open(filename,'w').write(payload)
print "Writing to %s" % filename
else:
print "%s already exists" % filename
The IMAPClient package is a fair bit easier to work with. From the description:
Easy-to-use, Pythonic and complete
IMAP client library.
Try my package:
https://pypi.org/project/imap-tools/
example:
from imap_tools import MailBox
# get list of email bodies from INBOX folder
with MailBox('imap.mail.com').login('test#mail.com', 'password', 'INBOX') as mailbox:
bodies = [msg.text or msg.html for msg in mailbox.fetch()]
Features:
Parsed email message attributes
Query builder for searching emails
Work with emails in folders (copy, delete, flag, move, append)
Work with mailbox folders (list, set, get, create, exists, rename, delete, status)
No dependencies
This was my solution to extract the useful bits of information. It's been reliable so far:
import datetime
import email
import imaplib
import mailbox
EMAIL_ACCOUNT = "your#gmail.com"
PASSWORD = "your password"
mail = imaplib.IMAP4_SSL('imap.gmail.com')
mail.login(EMAIL_ACCOUNT, PASSWORD)
mail.list()
mail.select('inbox')
result, data = mail.uid('search', None, "UNSEEN") # (ALL/UNSEEN)
i = len(data[0].split())
for x in range(i):
latest_email_uid = data[0].split()[x]
result, email_data = mail.uid('fetch', latest_email_uid, '(RFC822)')
# result, email_data = conn.store(num,'-FLAGS','\\Seen')
# this might work to set flag to seen, if it doesn't already
raw_email = email_data[0][1]
raw_email_string = raw_email.decode('utf-8')
email_message = email.message_from_string(raw_email_string)
# Header Details
date_tuple = email.utils.parsedate_tz(email_message['Date'])
if date_tuple:
local_date = datetime.datetime.fromtimestamp(email.utils.mktime_tz(date_tuple))
local_message_date = "%s" %(str(local_date.strftime("%a, %d %b %Y %H:%M:%S")))
email_from = str(email.header.make_header(email.header.decode_header(email_message['From'])))
email_to = str(email.header.make_header(email.header.decode_header(email_message['To'])))
subject = str(email.header.make_header(email.header.decode_header(email_message['Subject'])))
# Body details
for part in email_message.walk():
if part.get_content_type() == "text/plain":
body = part.get_payload(decode=True)
file_name = "email_" + str(x) + ".txt"
output_file = open(file_name, 'w')
output_file.write("From: %s\nTo: %s\nDate: %s\nSubject: %s\n\nBody: \n\n%s" %(email_from, email_to,local_message_date, subject, body.decode('utf-8')))
output_file.close()
else:
continue