Skip to content

Commit 4a173cb

Browse files
author
Ian Bentley
committed
Merge pull request #13 from alanjcastonguay/sendEmail-feature
Add sendEmail() support.
2 parents bc80945 + f33aa27 commit 4a173cb

5 files changed

Lines changed: 168 additions & 3 deletions

File tree

README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ this time:
3636
* queryAll
3737
* undelete
3838
* describeSObject
39-
* sendEmail
4039
* describeDataCategoryGroups
4140
* describeDataCategoryGroupStructures
4241

@@ -96,6 +95,36 @@ Batches work automatically (though sfdc limits the number to 200 maximum):
9695
]
9796
res = svc.create(contacts)
9897

98+
Send a new email, optionally using templates, including attachments and creating activities for associated objects:
99+
100+
simple_email = {
101+
'subject': 'Test of Salesforce sendEmail()',
102+
'plainTextBody': 'This is a simple test message.',
103+
'toAddresses': 'johndoe@example.com,
104+
}
105+
res = svc.sendEmail( [simple_email] )
106+
res
107+
[{'errors': [], 'success': True}]
108+
109+
complex_email = {
110+
'templateId': '00X80000002h4TV', # Id of an EmailTemplate used for Subject and Body, supports field merge from whatId.
111+
'targetObjectId':'003808980000GJ', # Id of a Contact, Lead or User which the email will be sent to.
112+
'whatId':'500800000RuJo', # Id of a SObject to create an Activity in.
113+
'saveAsActivity': True,
114+
'useSignature': True,
115+
'inReplyTo': '<1234567890123456789%example@example.com>', # RFC2822, a previous email thread.
116+
'references': '<1234567890123456789%example@example.com>',
117+
'fileAttachments': [{
118+
'body': base64_encoded_png,
119+
'contentType':'image/png',
120+
'fileName':'salesforce_logo.png',
121+
'inline':True
122+
}]
123+
}
124+
res = svc.sendEmail( [complex_email] )
125+
res
126+
[{'errors': [], 'success': True}]
127+
99128
More Examples
100129
=============
101130

src/pyforce/pyforce.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import logging
2-
from xmlclient import _tPartnerNS, _tSObjectNS
2+
from xmlclient import _tPartnerNS, _tSObjectNS, _tSchemaInstanceNS
33
from xmlclient import Client as BaseClient
44
from marshall import marshall
55
from types import TupleType, ListType
66
import re
77
import copy
88
from xmltramp import Namespace
99

10-
_tSchemaInstanceNS = Namespace('http://www.w3.org/2001/XMLSchema-instance')
1110
_tSchemaNS = Namespace('http://www.w3.org/2001/XMLSchema')
1211

1312
DEFAULT_FIELD_TYPE = "string"
@@ -223,6 +222,40 @@ def convert_leads(self, lead_converts):
223222
d['opportunity_id'] = str(resu[_tPartnerNS.opportunityId])
224223
return data
225224

225+
def sendEmail(self, emails, mass_type='SingleEmailMessage'):
226+
"""
227+
Send one or more emails from Salesforce.
228+
229+
Parameters:
230+
emails - a dictionary or list of dictionaries, each representing a single email as described by https://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_sendemail.htm
231+
massType - 'SingleEmailMessage' or 'MassEmailMessage'. MassEmailMessage is used for mailmerge of up to 250 recepients in a single pass.
232+
233+
Note:
234+
Newly created Salesforce Sandboxes default to System email only. In this situation, sendEmail() will fail with NO_MASS_MAIL_PERMISSION.
235+
"""
236+
preparedEmails = _prepareSObjects(emails)
237+
if isinstance(preparedEmails,dict):
238+
# If root element is a dict, then this is a single object not an array
239+
del preparedEmails['fieldsToNull']
240+
else:
241+
# else this is an array, and each elelment should be prepped.
242+
for listitems in preparedEmails:
243+
del listitems['fieldsToNull']
244+
res = BaseClient.sendEmail(self, preparedEmails, mass_type)
245+
if type(res) not in (TupleType, ListType):
246+
res = [res]
247+
data = list()
248+
for resu in res:
249+
d = dict()
250+
data.append(d)
251+
d['success'] = success = _bool(resu[_tPartnerNS.success])
252+
if not success:
253+
d['errors'] = [_extractError(e)
254+
for e in resu[_tPartnerNS.errors:]]
255+
else:
256+
d['errors'] = list()
257+
return data
258+
226259
def retrieve(self, fields, sObjectType, ids):
227260
resultSet = BaseClient.retrieve(self, fields, sObjectType, ids)
228261
type_data = self.describeSObjects(sObjectType)[0]

src/pyforce/tests/test_python_client.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,46 @@ def testRetrieveTextWithNewlines(self):
856856
contact = contacts[0]
857857
self.assertEqual(data['Description'], contact['Description'])
858858

859+
def testSendSimpleEmail(self):
860+
testemail = {
861+
'subject':'pyforce test_xmlclient.py testSendSimpleEmail of Salesforce sendEmail()',
862+
'saveAsActivity':False,
863+
'toAddresses':str(self.svc.getUserInfo()['userEmail']),
864+
'plainTextBody':'This is a test email message with HTML markup.\n\nYou are currently looking at the plain-text body, but the message is sent in both forms.',
865+
}
866+
res = self.svc.sendEmail( [testemail] )
867+
print res
868+
self.assertTrue(res[0]['success'])
869+
870+
def testSendEmailMissingFields(self):
871+
testemail = {
872+
'toAddresses':str(self.svc.getUserInfo()['userEmail']),
873+
}
874+
res = self.svc.sendEmail( [testemail] )
875+
self.assertFalse(res[0]['success'])
876+
self.assertEqual(res[0]['errors'][0]['statusCode'], 'REQUIRED_FIELD_MISSING')
877+
878+
def testSendHTMLEmailWithAttachment(self):
879+
solid_logo = {
880+
'body':'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',
881+
'contentType':'image/png',
882+
'fileName':'salesforce_logo.png',
883+
'inline':True
884+
}
885+
testemail = {
886+
'subject':'pyforce test_xmlclient.py testSendHTMLEmailWithAttachment of Salesforce sendEmail()',
887+
'useSignature':True,
888+
'saveAsActivity':False,
889+
'toAddresses':str(svc.getUserInfo()['userEmail']),
890+
'plainTextBody':'This is a test email message with HTML markup.\n\nYou are currently looking at the plain-text body, but the message is sent in both forms.',
891+
'htmlBody':'<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"><body><h1>This is a test email message with HTML markup.</h1>\n\n<p>You are currently looking at the <i>HTML</i> body, but the message is sent in both forms.</p></body></html>',
892+
'inReplyTo': '<1234567890123456789%example@example.com>',
893+
'references': '<1234567890123456789%example@example.com>',
894+
'fileAttachments': [solid_logo]
895+
}
896+
res = svc.sendEmail( [testemail] )
897+
self.assertTrue(res[0]['success'])
898+
859899
def test_suite():
860900
return unittest.TestSuite((
861901
unittest.makeSuite(TestUtils),

src/pyforce/tests/test_xmlclient.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,45 @@ def testSearch(self):
100100
res = svc.search(sosl)
101101
self.assertEqual(len(res), 1)
102102

103+
def testSendSimpleEmail(self):
104+
testemail = {
105+
'subject':'pyforce test_xmlclient.py testSendSimpleEmail of Salesforce sendEmail()',
106+
'saveAsActivity':False,
107+
'toAddresses':str(svc.getUserInfo()['userEmail']),
108+
'plainTextBody':'This is a test email message with HTML markup.\n\nYou are currently looking at the plain-text body, but the message is sent in both forms.',
109+
}
110+
res = svc.sendEmail( [testemail] )
111+
self.assertEqual(str(res[partnerns.success]), 'true')
112+
113+
def testSendEmailMissingFields(self):
114+
testemail = {
115+
'toAddresses':str(svc.getUserInfo()['userEmail']),
116+
}
117+
res = svc.sendEmail( [testemail] )
118+
self.assertEqual(str(res[partnerns.success]), 'false')
119+
self.assertEqual(str(res[partnerns.errors][partnerns.statusCode]), 'REQUIRED_FIELD_MISSING')
103120

121+
def testSendHTMLEmailWithAttachment(self):
122+
solid_logo = {
123+
'body':'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',
124+
'contentType':'image/png',
125+
'fileName':'salesforce_logo.png',
126+
'inline':True
127+
}
128+
testemail = {
129+
'subject':'pyforce test_xmlclient.py testSendHTMLEmailWithAttachment of Salesforce sendEmail()',
130+
'useSignature':True,
131+
'saveAsActivity':False,
132+
'toAddresses':str(svc.getUserInfo()['userEmail']),
133+
'plainTextBody':'This is a test email message with HTML markup.\n\nYou are currently looking at the plain-text body, but the message is sent in both forms.',
134+
'htmlBody':'<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"><body><h1>This is a test email message with HTML markup.</h1>\n\n<p>You are currently looking at the <i>HTML</i> body, but the message is sent in both forms.</p></body></html>',
135+
'inReplyTo': '<1234567890123456789%example@example.com>',
136+
'references': '<1234567890123456789%example@example.com>',
137+
'fileAttachments': [solid_logo]
138+
}
139+
res = svc.sendEmail( [testemail] )
140+
self.assertEqual(str(res[partnerns.success]), 'true')
141+
104142
def test_suite():
105143
return unittest.TestSuite((
106144
unittest.makeSuite(TestBeatbox),

src/pyforce/xmlclient.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
_partnerNs = "urn:partner.soap.sforce.com"
2020
_sobjectNs = "urn:sobject.partner.soap.sforce.com"
2121
_envNs = "http://schemas.xmlsoap.org/soap/envelope/"
22+
_schemaInstanceNs = "http://www.w3.org/2001/XMLSchema-instance"
2223
_noAttrs = AttributesNSImpl({}, {})
2324

2425
DEFAULT_SERVER_URL = 'https://login.salesforce.com/services/Soap/u/20.0'
@@ -27,6 +28,7 @@
2728
_tPartnerNS = xmltramp.Namespace(_partnerNs)
2829
_tSObjectNS = xmltramp.Namespace(_sobjectNs)
2930
_tSoapNS = xmltramp.Namespace(_envNs)
31+
_tSchemaInstanceNS = xmltramp.Namespace(_schemaInstanceNs)
3032

3133
# global config
3234
gzipRequest=True # are we going to gzip the request ?
@@ -128,6 +130,19 @@ def getUserInfo(self):
128130
def convertLeads(self, convertLeads):
129131
return ConvertLeadsRequest(self.__serverUrl, self.sessionId, convertLeads).post(self.__conn)
130132

133+
def sendEmail(self, emails, massType='SingleEmailMessage'):
134+
"""
135+
Send one or more emails from Salesforce.
136+
137+
Parameters:
138+
emails - a dictionary or list of dictionaries, each representing a single email as described by https://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_sendemail.htm
139+
massType - 'SingleEmailMessage' or 'MassEmailMessage'. MassEmailMessage is used for mailmerge of up to 250 recepients in a single pass.
140+
141+
Note:
142+
Newly created Salesforce Sandboxes default to System email only. In this situation, sendEmail() will fail with NO_MASS_MAIL_PERMISSION.
143+
"""
144+
return SendEmailRequest(self.__serverUrl, self.sessionId, emails, massType).post(self.__conn)
145+
131146
# fixed version of XmlGenerator, handles unqualified attributes correctly
132147
class BeatBoxXmlGenerator(XMLGenerator):
133148
def __init__(self, destination, encoding):
@@ -253,10 +268,12 @@ def __init__(self):
253268
self.startPrefixMapping("s", _envNs)
254269
self.startPrefixMapping("p", _partnerNs)
255270
self.startPrefixMapping("o", _sobjectNs)
271+
self.startPrefixMapping("x", _schemaInstanceNs)
256272
self.startElement(_envNs, "Envelope")
257273

258274
def endDocument(self):
259275
self.endElement() # envelope
276+
self.endPrefixMapping("x")
260277
self.endPrefixMapping("o")
261278
self.endPrefixMapping("p")
262279
self.endPrefixMapping("s")
@@ -501,6 +518,14 @@ def __init__(self, serverUrl, sessionId, sLeads):
501518
def writeBody(self, s):
502519
s.writeElement(_partnerNs, "leadConverts", self.__sLeads)
503520

521+
class SendEmailRequest(AuthenticatedRequest):
522+
def __init__(self, serverUrl, sessionId, emails, massType="SingleEmailMessage"):
523+
AuthenticatedRequest.__init__(self, serverUrl, sessionId, "sendEmail")
524+
self.__emails = emails
525+
self.__massType = massType
526+
527+
def writeBody(self, s):
528+
s.writeElement(_partnerNs, "messages", self.__emails, attrs={(_schemaInstanceNs, 'type'):'p:'+self.__massType})
504529

505530
class ResetPasswordRequest(AuthenticatedRequest):
506531
def __init__(self, serverUrl, sessionId, userId):

0 commit comments

Comments
 (0)