Product SiteDocumentation Site

4.6. Getting attachments

Each message (email) has a list of attachments. To get the list of attachments for an email, we can use the GetAttachmentTable() method on the message. As the name implies, this will return a MAPI table, exactly like the one we used before, but this time the rows will be the attachments, and the columns will be the properties (like before)
t = message.GetAttachmentTable(0)

t.SetColumns([PR_ATTACH_FILENAME, PR_ATTACH_NUM], 0)
rows = t.QueryRows(10,0)
As before, we can read the attachments by querying a certain column set, and then reading the rows with QueryRows(). The PR_ATTACH_NUM column can be used to open the actual attachment:
attach = message.OpenAttach(rows[0][1].Value, None, 0)

props = attach.GetProps([PR_ATTACH_FILENAME], 0)
Again, the attachment object itself can be queried with GetProps() to retrieve properties.
The data inside the attachment is just another property: PR_ATTACH_DATA_BIN. Logically you’d expect to be able to call GetProps() passing that property tag (PR_ATTACH_DATA_BIN) to get the attachment data. However, this would be a bad idea - if the attachment is big, you’d get a huge SPropValue value with several megabytes of data in the Value property of the SPropValue object.
To avoid this, MAPI doesn’t allow you to get more than 8k of data via the GetProps() interface. If you have any more than that, you will get an error. Not just any error though, the GetProps() call will succeed as usual. However, the SPropValue returned will contain special information:
To get the data, we have to use a stream. The MAPI stream is just like any other stream, allowing read/write/seek access to a sequential stream of data. We open the stream with the OpenProperty() call:
import MAPI.Guid
stream = attach.OpenProperty(PR_ATTACH_DATA_BIN, IID_IStream, 0, 0)
The OpenProperty() method takes the property tag you wish to open, an interface identifier (in this case we want to open a stream), and some flags.
We can then use the stream to read the attachment data in 4k blocks:
data = ''
while True:
  d = stream.Read(4096)
  if len(d) == 0: break
  data += d
This negates the effect of not having all that data in memory, but it illustrates the use of the stream pretty well.