Monday, October 3, 2011

Re: MAPI Email Backend available for testing

Okay, so I manages to install the Win32 extensions for Python and the
above Backend will not work, unless perhaps you use an older version
of windows or exchange. That code seems outdated. Here is code that
is verified to work with Windows 7 and Outlook 2010, it should work
with others as this code is taken from the MAPI example in the Win32
extensions and adapted for an Email backend. I will be verifying if
it works. Enjoy!

from win32com.mapi import mapi
from win32com.mapi import mapitags
import threading

from django.conf import settings
from django.core.mail.backends.base import BaseEmailBackend

class EmailBackend(BaseEmailBackend):
"""
A wrapper that manages the MAPI interface.
"""
def __init__(self, MAPIProfile=None, fail_silently=False,
**kwargs):
super(EmailBackend,
self).__init__(fail_silently=fail_silently)
self.MAPIProfile = MAPIProfile or settings.MAPIProfile
self.connection = None
self._lock = threading.RLock()

def open(self):
"""
Ensures we have a MAPI Session to the exchange server. Returns
whether or
not a new connection was required (True or False).
"""
if self.connection:
# Nothing to do if the connection is already open.
return False
try:
mapi.MAPIInitialize(None)
if self.MAPIProfile <> None:
MAPIProfile = self.MAPIProfile
else:
MAPIProfile = ""
session = mapi.MAPILogonEx(0, MAPIProfile, None, mapi.MAPI_EXTENDED
| mapi.MAPI_USE_DEFAULT)
messagestorestable = session.GetMsgStoresTable(0)
messagestorestable.SetColumns((mapitags.PR_ENTRYID,
mapitags.PR_DISPLAY_NAME_A, mapitags.PR_DEFAULT_STORE),0)
while True:
rows = messagestorestable.QueryRows(1, 0)
#if this is the last row then stop
if len(rows) != 1:
break
row = rows[0]
#if this is the default store then stop
if ((mapitags.PR_DEFAULT_STORE,True) in row):
break
# unpack the row and open the message store
(eid_tag, eid), (name_tag, name), (def_store_tag, def_store) = row
msgstore = session.OpenMsgStore(0,eid,None,mapi.MDB_NO_DIALOG |
mapi.MAPI_BEST_ACCESS)
# get the outbox
hr, props = msgstore.GetProps((mapitags.PR_IPM_OUTBOX_ENTRYID), 0)
(tag, eid) = props[0]
#check for errors
if mapitags.PROP_TYPE(tag) == mapitags.PT_ERROR:
raise TypeError('got PT_ERROR instead of PT_BINARY: %s'%eid)
self.connection =
msgstore.OpenEntry(eid,None,mapi.MAPI_BEST_ACCESS)
return True
except:
if not self.fail_silently:
raise

def close(self):
"""Closes the connection to the exchange server."""
self.connection = None

def send_messages(self, email_messages):
"""
Sends one or more EmailMessage objects and returns the number
of email
messages sent.
"""
if not email_messages:
return
self._lock.acquire()
try:
new_conn_created = self.open()
if not self.connection:
# We failed silently on open().
# Trying to send would be pointless.
return
num_sent = 0
for message in email_messages:
sent = self._send(message)
if sent:
num_sent += 1
if new_conn_created:
self.close()
finally:
self._lock.release()
return num_sent

def _sanitize(self, email):
name, domain = email.split('@', 1)
email = '@'.join([name, domain.encode('idna')])
return email

def _makeentry(recipient, recipienttype):
"""A helper method that creates the MAPI Object for the
recipient."""
return ((mapitags.PR_RECIPIENT_TYPE, recipienttype),
(mapitags.PR_SEND_RICH_INFO, False),
(mapitags.PR_DISPLAY_TYPE, 0),
(mapitags.PR_OBJECT_TYPE, 6),
(mapitags.PR_EMAIL_ADDRESS_A, recipient),
(mapitags.PR_ADDRTYPE_A, 'SMTP'),
(mapitags.PR_DISPLAY_NAME_A, recipient))

def _send(self, email_message):
"""A helper method that does the actual sending."""
if not email_message.recipients():
return False
recipients = map(self._sanitize, email_message.recipients())
message = self.connection.CreateMessage(None,0)
pal = []
for recipient in recipients:
pal.extend([self._makeentry(recipient, mapi.MAPI_TO)])
# add the resolved recipients to the message
message.ModifyRecipients(mapi.MODRECIP_ADD,pal)

message.SetProps([(mapitags.PR_BODY_A,email_message.message().as_string()),
(mapitags.PR_SUBJECT_A,'Django sent message')])
self.connection.SaveChanges(0)
message.SubmitMessage(0)
return True


On Oct 3, 4:01 am, Kevin <kveron...@gmail.com> wrote:
> I noticed that Django does not include a MAPI Backend for sending
> Email, for some environments having this type of backend is essential
> for testing and debugging.  I do not recommend it as a production
> backend unless you are a full exchange shop, as some are and do not
> offer SMTP for internal applications.
>
> I do not recommend using this as a production backend as it has not
> yet been tested, it is recommended that you have experience coding
> MAPI with Python before using this backend.  This backend is only
> compatible on Windows machines using the Python extensions as it talks
> to Outlook directly, there are no sockets involved, so it makes it
> very useful in environments with a tight firewall.  Without further
> delay here is the backend, as I said I haven't gotten around to
> testing it and I do need some help adding support for the subject line
> as this is my first Email backend attempt:
>
> import win32com.client.dynamic, sys, re
> import threading
>
> from django.conf import settings
> from django.core.mail.backends.base import BaseEmailBackend
>
> class EmailBackend(BaseEmailBackend):
>     """
>     A wrapper that manages the MAPI network connection.
>     """
>     def __init__(self, MAPIProfile=None, fail_silently=False,
> **kwargs):
>         super(EmailBackend,
> self).__init__(fail_silently=fail_silently)
>         self.MAPIProfile = MAPIProfile or settings.MAPIProfile
>         self.connection = None
>         self._lock = threading.RLock()
>
>         def open(self):
>         """
>         Ensures we have a MAPI Session to the exchange server. Returns
> whether or
>         not a new connection was required (True or False).
>         """
>         if self.connection:
>             # Nothing to do if the connection is already open.
>             return False
>         try:
>                         self.connection = win32com.client.dynamic.Dispatch("MAPI.session")
>                         if self.MAPIProfile <> None:
>                                 self.connection.Logon(self.MAPIProfile)
>                         else:
>                                 self.connection.Logon("MS Exchange Settings")
>                         return True
>                 except:
>             if not self.fail_silently:
>                 raise
>
>     def close(self):
>         """Closes the connection to the exchange server."""
>         try:
>             try:
>                 self.connection.Logoff()
>             except:
>                 if self.fail_silently:
>                     return
>                 raise
>         finally:
>             self.connection = None
>
>     def send_messages(self, email_messages):
>         """
>         Sends one or more EmailMessage objects and returns the number
> of email
>         messages sent.
>         """
>         if not email_messages:
>             return
>         self._lock.acquire()
>         try:
>             new_conn_created = self.open()
>             if not self.connection:
>                 # We failed silently on open().
>                 # Trying to send would be pointless.
>                 return
>             num_sent = 0
>             for message in email_messages:
>                 sent = self._send(message)
>                 if sent:
>                     num_sent += 1
>             if new_conn_created:
>                 self.close()
>         finally:
>             self._lock.release()
>         return num_sent
>
>     def _sanitize(self, email):
>         name, domain = email.split('@', 1)
>         email = '@'.join([name, domain.encode('idna')])
>         return email
>
>     def _send(self, email_message):
>         """A helper method that does the actual sending."""
>         if not email_message.recipients():
>             return False
>         recipients = map(self._sanitize, email_message.recipients())
>                 outbox = self.connection.OutBox.Messages.Add('Django sent
> message',email_message.message().as_string(),'CMC: IPM')
>                 for recipient in recipients:
>                         recip = outbox.Recipients.Add(Name=recipient, Type=1)
>                         recip.Resolve()
>         try:
>                         outbox.Update()
>                         outbox.Send()
>                         self.connection.DeliverNow()
>         except:
>             if not self.fail_silently:
>                 raise
>             return False
>         return True
>
> As you can see I used the SMTP backend as a base, and modified all the
> functions to replace them with MAPI session and sending functions
> instead.  Hopefully this helps some Windows django developers or
> encourages Windows developers to take the leap.
>
> It's actually a funny story on how I even got around to creating this
> backend.  I am working on a tool in my workplace to improve my own
> productivity, and since I use Python and Django at home for web
> projects, I thought I'd use it at work to develop a tool.  This tool
> automates a lot of my work, and all that's left is to implement an
> Email sending function.  Since I need special developer access to use
> the internal SMTP server, I thought of the nifty idea of using my
> Outlook client as a host to send out Email on behalf of my internal
> application.  I work in the I.T. sector, so I have admin access to my
> machine, which allows me to develop my own tools.  My job does not
> include developing web application, it's a side passion of mine....
>
> Anyways, let me know what you think of this backend, it's my first
> attempt, so any revisions would be great.

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to django-users@googlegroups.com.
To unsubscribe from this group, send email to django-users+unsubscribe@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

No comments:

Post a Comment