InfiniTec - Henning Krauses Blog

Don't adjust your mind - it's reality that is malfunctioning

Exchange Developer Roadmap - WebDAV and StoreEvents gone

Microsoft just released a post about the technologies being removed from the next version of Exchange. I'm ok with WebDAV being removed, given that EWS will be extended to support access to hidden messages and providing strong-typed access to mailbox settings. I can also live with the fact that CdoEx and ExOleDB will be remove. But store events are another thing.

Sure, they are not really easy to implement and the whole access using ADO is a terrible mess. CdoEx makes it not exactly better as there is no support for tasks.

The proposed replacement for store event sinks are transport agents and EWS notification. While transport agents are fine when dealing with email messages, they are useless with respect to appointments, contacts and tasks. This leaves the EWS notifications as the sole option here. Why is this bad?

  • Synchronous execution (access to old data as well): Synchronous event sinks (OnSyncSave, OnSyncDelete) provide direct access to the item being modified (the record is directly available). And during the Begin phase, the event sink can even open the old record and execute actions based on how fields where changed. This feature will be lost completely with EWS notifications.
  • Register once, even for new users (storewide eventsinks): Store wide event sinks are registered once on a store and it will be triggered for every mailbox in the store - even new users. EWS notifications must be registered for each mailbox and the application receiving the mails is required to monitor Exchange for new mailboxes.
  • Access to all properties, even during deletion of an object: With a synchronous OnSyncDelete event sink, all properties of an item can be examined before it gets deleted. With notifications I merely get a notification that and item with a specific ItemId has been deleted. The client application is responsible to track the deleted item - whether is was soft-deleted or moved to the recycle bin. The properties can then be accessed from there. But if the item was hard-deleted (in case of the dumpster being disabled on public folders, for example), one is out of luck. The real problem is this: The ItemId is based on the email address of the mailbox owner as well as the MAPI EntryId of the item (see Exchange 2007 SP1 Item ids). Both, the mailbox address as well as the MAPI entry id are not guaranteed to be stable (Since Exchange 2003 SP2, Exchange recreates meeting items under certain circumstances: CDOEX Calendaring Differences Between Exchange 2003 and Exchange 2003 SP2). This has the effect that the ItemId is not suitable as a long term identifier which should be stored in a database. In scenarios where Exchange data is being replicated to a relational databases, this can become a problem.
  • Order of execution: Synchronous event sinks are called in the order the items were changed. With asynchronous notifications, this cannot be guaranteed.
    To clarify this: The order in which notifications are sent to the client application cannot guaranteed to reflect the order in which the changes were made. To work around this issue, the each notification carries a watermark and a previous watermark. This way a client application can restore the correct order. But with synchronous store events, this comes for free.
  • Silent updates: Due to the possibility to modify an item while it's being changed, the synchronous store events allow some sort of silent update. This works like this:
    1. Along with all the other modifications, a client program sets a special user defined field to an arbitrary value.
    2. The event sink checks this field during the Begin phase, and if it's set it won't process the item. Instead it just removes the property from the element.
  • Modify changes made by a user / Block modifications: A synchronous event sink can modify changes while they are being saved. It can even abort the change, thus preventing a user from deleting an item for example.
    Note: Canceling updates/deletions can break synchronization with ActiveSync or Outlook. So don't do this!
  • Performance: In most scenarios, the WebService receiving the push notifications will not reside on the Exchange server (I know a bunch of administrators who even hesitate to install the .NET Framework on the Exchange server let alone software which does not come from MS*). In this case, the push notifications work like this:
    image
    This makes three network hops to get the properties of a changed item. What makes things worse is the fact that the notifications are send for each modified element. With store event sinks, one could specify a filter so that the sink was only triggered for a subset of elements.

So, while store event sinks are certainly no unconfined pleasure to use, they are far more suited for certain scenarios. I would rather see them being replaced by a managed solution (like transport agents) than the stuff that is coming now.

By the way, the OnTimer, OnMdbShutdown, OnMdbStartup event sinks are being removed without any replacement.

Enough ranting for one day...

* Now, one could argue that a store event sink has far more impact on the Exchange server and those administrators would also hesitate to install them... while that is technically true, there is no other option yet, so they have to swallow that pill.


Posted by Henning Krause on Friday, May 23, 2008 4:41 PM, last modified on Tuesday, July 19, 2011 8:58 AM
Permalink | Post RSSRSS comment feed

Working with the MasterCategoryList Via WebDAV

Before Outlook 2007 categories were plain-text. They had a name, but nothing more. Outlook 2007 enhanced this concept by adding colors to categories:

 image

Each category can be given a color (one of 25) and a shortcut (CTRL+F2 through CTRL+F12). Unlike in former Outlook versions, this list of categories is no longer stored in the registry, but in the default calendar of the mailbox. Outlook 2007 creates a hidden message in that folder with the message class IPM.Configuration.CategoryList. The category configuration is stored in the MAPI property 0x7C08 type bin.base64. To query the master category list via WebDAV, two steps are necessary:

  1. Get the URL of the default calendar of a given mailbox (See Getting Well-Known Mailbox Folder URLs on MSDN).
  2. Search the default calendar for items with a message class of IPM.Configuration.CategoryList.
  3. Parse the binary stream

Getting the default calendar

To accomplish the first step, send a PROPFIND request to the root url of the mailbox:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <propfind xmlns="DAV:">
   3:     <prop>
   4:         <a:calendar xmlns:a="urn:schemas:httpmail:" />
   5:     </prop>
   6: </propfind>

The result might look like this:

   1: <?xml version="1.0"?>
   2: <a:multistatus xmlns:d="urn:schemas:httpmail:" xmlns:a="DAV:">
   3:     <a:response>
   4:         <a:href>http://server/exchange/mailbox/</a:href>
   5:         <a:propstat>
   6:             <a:status>HTTP/1.1 200 OK</a:status>
   7:             <a:prop>
   8:                 <d:calendar>http://server/exchange/mailbox/Calendar</d:calendar>
   9:             </a:prop>
  10:         </a:propstat>
  11:     </a:response>
  12: </a:multistatus>

Searching for the hidden message

Given the correct calendar folder path, a SEARCH query can be constructed to retrieve the property in question from the hidden message:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <searchrequest xmlns="DAV:">
   3:     <sql>
   3:         SELECT 
   4:             "http://schemas.microsoft.com/mapi/proptag/x7c080102", 
   5:             "http://schemas.microsoft.com/exchange/permanenturl" 
   6:             FROM SCOPE ('SHALLOW TRAVERSAL OF "http://server/exchange/mailbox/Calendar"') 
   7:             WHERE 
   8:                 ("DAV:isfolder" = false AND 
   9:                 ("http://schemas.microsoft.com/mapi/proptag/x001a001f" = 'IPM.Configuration.CategoryList')) 
  11:     </sql>
  12: </searchrequest>

adssdfdsf

If successful, the server returns something similar like this:

   1: <?xml version="1.0"?>
   2: <a:multistatus xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" 
   3:                xmlns:d="http://schemas.microsoft.com/mapi/proptag/" 
   4:                xmlns:e="http://schemas.microsoft.com/exchange/" 
   5:                xmlns:a="DAV:">
   6:     <a:contentrange>0-0</a:contentrange>
   7:     <a:response>
   8:         <a:href>http://server/exchange/mailbox/Calendar/IPM.Configuration.CategoryList.EML</a:href>
   9:         <a:propstat>
  10:             <a:status>HTTP/1.1 200 OK</a:status>
  11:             <a:prop>
  12:                 <d:x7c080102 b:dt="bin.base64">Base64 content ommited to improve readability</d:x7c080102>
  13:                 <e:permanenturl>http://server/exchange/mailbox/-FlatUrlSpace-/155ae68de4aeda4987585833042471bc-8a5e2/ac5d26d07c067f4ea4f38a18f64786a3-3a1c8a</e:permanenturl>
  14:             </a:prop>
  15:         </a:propstat>
  16:     </a:response>
  17: </a:multistatus>

sdfssdfsdfdsf

Decoding the Property value

The content can now be extracted by loading the result into a System.Xml.XmlDocument or System.Xml.Linq.XDocument and select the appropiate node using an XPath expression. To decode the value, use the System.Convert.FromBase64String and the System.Text.Encoding.UTF8.GetString method. This will yield the XML representation of the Master Category List. Since manipulation of raw XML data is not exactly fun, I've created a sample solution which uses the System.Xml.Serialization.XmlSerializer to construct a strong-typed object from the stream. Below is a class diagram of the available classes:

image

The MasterCategoryList has a static Load method which takes a System.IO.TextReader instance and reads the the value decoded earlier:

   1: using (var reader = new StringReader(masterCategoryListContent))
   2: {
   3:     var masterCategoryList = MasterCategoryList.Load(reader);
   4:     foreach (var category in masterCategoryList.Categories)
   5:     {
   6:         Console.Out.WriteLine("{0}: Color {1}, Shortcut {2}", category.Name, category.Color, category.KeyboardShortcut);
   7:     }
   8: }

This code assumes that the string representation of the Master Category List is stored in the masterCategoryListContent field.

Once done with manipulating the instance, it can be saved to a System.IO.TextWriter. The Master Category List in the mailbox can then be updated using a PROPPATCH request on the address used above.

Download: Source files

Links

Fellow MVP Glen Scales has an article on his blog about this topic as well: Adding Categories to the Master categories list in Outlook 2007 with a CDO 1.2 script


Posted by Henning Krause on Thursday, May 22, 2008 2:36 PM, last modified on Thursday, May 22, 2008 2:39 PM
Permalink | Post RSSRSS comment feed

Getting Mailbox Information On Exchange 2003 using WMI

A recent question in the Microsoft development forum for Exchange was how to get information about mailboxes on Exchange 2003 from an ASP.NET application.

The simplest solution is to use WMI in this case. Exchange exposes certain mailbox statistics via the Exchange_Mailbox WMI class. The following code can be used to iterate through all mailboxes and print some of the properties:

   1: using System;
   2: using System.Management;
   3:  
   4: namespace MailboxSizer
   5: {
   6:     class Program
   7:     {
   8:         static void Main(string[] args)
   9:         {
  10:             var scope = new ManagementScope("\\\\.\\root\\MicrosoftExchangeV2");
  11:             var query = new ObjectQuery("select * from Exchange_Mailbox");
  12:             var searcher = new ManagementObjectSearcher(scope, query);
  13:             foreach (ManagementObject mailbox in searcher.Get())
  14:             {
  15:                 Console.WriteLine("Display name: {0}", mailbox["MailboxDisplayName"]);
  16:                 Console.WriteLine("Size: {0} bytes", mailbox["Size"]);
  17:                 Console.WriteLine("Storage Limit: {0}", (StorageLimitInfo) (uint) mailbox["StorageLimitInfo"]);
  18:  
  19:                 Console.WriteLine();
  20:             }
  21:             Console.ReadLine();
  22:         }
  23:     }
  24:  
  25:     [Flags]
  26:     internal enum StorageLimitInfo
  27:     {
  28:         BelowLimit = 1,
  29:         IssueWarning = 2,
  30:         ProhibitSend = 4,
  31:         NoChecking = 8,
  32:         MailboxDisabled = 16
  33:     }
  34: }

You need to reference the System.Management assembly for this code to work.


Posted by Henning Krause on Monday, May 19, 2008 8:00 PM, last modified on Tuesday, May 20, 2008 11:51 AM
Permalink | Post RSSRSS comment feed

ExOleDB problems on Exchange 2007

If you are using the ExOleDB provider on Exchange 2007 it might fail with this error message:

Error : Provider cannot be found. it may not be properly installed. / 800A0E7A / ADODB.Connection

I know of two errors which can cause this message:

  • Your are trying to access the ExOleDB provider from a 32bit process. Exchange 2007 is completely 64bit. This typically breaks old Visual Basic programs or scripts running under the 32bit version of the Windows Scripting Host. To check this, open the Task manager and search for the process name under the "Processes" tab. If the process name is appended with a *32, it runs as a 32bit process.
  • The COM registration of the ExOleDB provider is messed up. This can be fixed by calling Regsvr32 "C:\Program Files\Exchsrvr\bin\exodbprx.dll".

Posted by Henning Krause on Monday, May 19, 2008 4:19 PM, last modified on Tuesday, July 26, 2011 9:56 PM
Permalink | Post RSSRSS comment feed

Working with the Recurrence pattern of Exchange 2007

One of the more difficult tasks with Exchange Web Services is coping with the recurrence pattern of an appointment or task item. Because of the limitations of WSDL and/or the proxy generator of Visual Studio, you end up with a RecurrenceType class which has two properties describing the recurrence of the element with such descriptive names as Item and Item1. To make things worse, the Microsoft did decide not to use enumerations to distinguish between different recurrence pattern types, but uses a unique type for each. Because this can be quite complex, I created this class diagram from the proxy class generated by Visual Studio:


Classdiagram with all the recurrence related types (click to enlarge)

The RecurrenceType type is used both, for appointments and tasks. But on appointments, you can't use the RegeneratingPatternBaseType and its descendants, because those are not supported by Outlook.

Creating a RecurrenceType structure is relatively straigt forward. You just take the appropiate types and fill the properties. Examining an existing structure is different: Since there is no enumeration, you'll have to use a bunch of instanceis type commands, or better yet, use the as operator (TryCast in Visual Basic).

    1 NumberedRecurrenceRangeType numberedRange = recurrence.Item1 asNumberedRecurrenceRangeType;

    2 EndDateRecurrenceRangeType endDateRange = recurrence.Item1 asEndDateRecurrenceRangeType;

    3 NoEndRecurrenceRangeType noEndDate = recurrence.Item1 asNoEndRecurrenceRangeType;

    4 

    5 if (numberedRange != null)

    6 {

    7     // Do something here

    8 }

    9 elseif (endDateRange != null)

   10 {

   11     // Do something here

   12 }

   13 elseif (noEndDate != null)

   14 {

   15     // Do something here                   

   16 }

In this example, the recurrence variable holds an instance of the RecurrenceType class.


Technorati:

Posted by Henning Krause on Tuesday, November 6, 2007 12:00 AM, last modified on Tuesday, November 6, 2007 12:00 PM
Permalink | Post RSSRSS comment feed

Private items with WebDAV

Outlook allows an item to be marked as private. For example, if Alice defines an appointment as private in her calendar and Bob, who has read access to Alice's calendar tries to read it, he won't get the details of that appointment. So while he technically has read access to that appointment, Outlook will block out the details. Note that Outlook is the instance which blocks out the instance. Using another protocol like WebDAV allows Bob to see the details.

This private flag can be read and written via WebDAV, but it's somewhat complicated. These are the relevant properties for the so called sensitivity of an item:

  • PR_SENSITIVITY (http://schemas.microsoft.com/mapi/proptag/0x360003)
  • Private Flag (http://schemas.microsoft.com/mapi/id/{00062008-0000-0000-C000-000000000046}/0x8506)
  • The Sensitivity header (urn:schemas:mailheader:sensitivity)

Reading or searching this property is easy: Just use the PR_Sensitivity with a SEARCH or PROPFIND request, and it will be returned as an integer value, which has this meaning:

  • 0: Normal
  • 1: Personal
  • 2: Private
  • 3: Confidential

You can also search for the private flag, which is a boolean value. If it's true, the item is marked private.

As far as I know, Outlook uses only the value 0 and 2.

If you want to set this value, you can just set this property do the desired value. That is, unless you want to clear the flag and set it to 0. This won't work. You have to set the sensitivity mail header to get this working. This is a string property, which must be set to one of these values:

  • Normal
  • Personal
  • Private
  • Company-Confidential

Searching for private items

As I mentioned above, you can search for items with a certain sensitivity. But since the properties are not part of the default item schema, you'll have to cast them to their datatypes. For example, to search for items marked as private using the Private flag, you can use this restriction in the WHERE clause of a SEARCH request:

    1 ("DAV:isfolder" = false) AND Cast("http://schemas.microsoft.com/mapi/id/{00062008-0000-0000-C000-000000000046}/0x8506" as "boolean") = true

 


Technorati:

Posted by Henning Krause on Tuesday, October 30, 2007 12:00 AM, last modified on Tuesday, October 30, 2007 12:00 PM
Permalink | Post RSSRSS comment feed

Manipulation of extended properties with Exchange 2007 using the ExtendedPropertyType type

To access MAPI properties or custom Outlook properties using the Exchange 2007 Web Services, the ExtendedPropertyType type is used.

Here is a class diagram of the ExtendedPropertyType and its associated properties:


Class diagram of the ExtendedPropertyType type and its associated properties (click to enlarge)

The ExtendedPropertyType has two properties which need to be filled: Item and ExtendedFieldUri. The latter one identfifies the property while the first one contains the value of the property. The Item property is of type object which suggests that it contains the property value in its native representation (e.g. a date stored as System.DateTime, a boolean value as type System.Boolean and so on). Unfortunately, this is not the case. If the property is multi-valued, the Item property contains a array of strings. Otherwise, it contains only one string. To actually do something with the value, it needs to be converted to it's corresponding type.

Here is an example request which performs a FindItem operation on the calendar of a mailbox, requesting the end date of the appointments using an extended property:

    1 privatestatic T[] DeserializeValues<T>(object[] items)

    2 {

    3     if (items == null) returnnew T[0];

    4 

    5     returnArray.ConvertAll<object, T>(items, delegate(object input) { return DeserializeValue<T>((string) input); });

    6 }

    7 

    8 privatestatic T DeserializeValue<T>(string item)

    9 {

   10     TypeConverter converter;

   11     Type type;

   12 

   13     if (string.IsNullOrEmpty(item)) returndefault(T);

   14 

   15     type = Nullable.GetUnderlyingType(typeof (T)) ?? typeof (T);

   16 

   17     if (type == typeof(byte[])) return (T)​(object)Convert.FromBase64String(item);

   18     elseif (type == typeof(DateTime)) return (T) (object) XmlConvert.ToDateTime(item, XmlDateTimeSerializationMode.Utc).ToLocalTime();

   19     elseif (type.IsEnum) return (T)Enum.Parse(type, item);

   20     else

   21     {

   22         converter = TypeDescriptor.GetConverter(type);

   23         return (T)converter.ConvertFromString(item);

   24     }

   25 }

   26 

   27 publicstaticvoid Main(string[] args)

   28 {

   29     ExchangeServiceBinding client = newExchangeServiceBinding();

   30 

   31     client.Url = "https://w2k3x64.contoso.local/ews/exchange.asmx";

   32     client.Credentials = newNetworkCredential("administrator", "password", "contoso");

   33 

   34     DistinguishedFolderIdType folderId = newDistinguishedFolderIdType();

   35     folderId.Id = DistinguishedFolderIdNameType.calendar;

   36 

   37     FindItemType request = newFindItemType();

   38 

   39     PathToExtendedFieldType propertyId = newPathToExtendedFieldType();

   40     propertyId.DistinguishedPropertySetId = DistinguishedPropertySetType.Appointment;

   41     propertyId.PropertyId = 0x820e;

   42     propertyId.PropertyType = MapiPropertyTypeType.SystemTime;

   43     propertyId.PropertyIdSpecified = true;

   44     propertyId.DistinguishedPropertySetIdSpecified = true;

   45 

   46     request.ParentFolderIds = newBaseFolderIdType[] {folderId};

   47     request.ItemShape = newItemResponseShapeType();

   48     request.Traversal = ItemQueryTraversalType.Shallow;

   49     request.ItemShape.BaseShape = DefaultShapeNamesType.Default;

   50     request.ItemShape.AdditionalProperties = newBasePathToElementType[] { propertyId };

   51 

   52     FindItemResponseType result = client.FindItem(request);

   53 

   54     if (result.ResponseMessages.Items[0].ResponseCode != ResponseCodeType.NoError)

   55     {

   56         Console.Out.WriteLine("Error = {0}", result.ResponseMessages.Items[0].ResponseCode);

   57         return;

   58     }

   59 

   60     FindItemResponseMessageType findItemResponse = (FindItemResponseMessageType)result.ResponseMessages.Items[0];

   61     ItemType[] items = ((ArrayOfRealItemsType)findItemResponse.RootFolder.Item).Items;

   62     if (items != null)

   63     {

   64         foreach (ItemType childItem in items)

   65         {

   66             Console.Out.WriteLine("item.Subject = {0}", childItem.Subject);

   67             Console.Out.WriteLine("ParseValue<DateTime?>(childItem.ExtendedProperty[0].Item) = {0}", DeserializeValue<DateTime?>((string)childItem.ExtendedProperty[0].Item));

   68         }

   69     }

   70 

   71     Console.WriteLine("Finished.");

   72     Console.ReadLine();

   73 }

The content of the main function is actually the boring part. More interesting is the DeserializeValue<T> method which takes a string and converts it to the specified value T. In this example, the value is converted to a nullable DateTime. This method in called in line 67.

To update the property, these methods can be used to serialize a typed value to a string:

    1 privatestaticExtendedPropertyType CreateExtendedProperty<T>(PathToExtendedFieldType id, IEnumerable<T> values)

    2 {

    3     ExtendedPropertyType result = newExtendedPropertyType();

    4 

    5     result.ExtendedFieldURI = id;

    6     result.Item = (values != null) ? newList<T>(values).ConvertAll<string>(delegate(T input) { return SerializeValue(input); }).ToArray() : null;

    7 

    8     return result;

    9 }

   10 

   11 privateExtendedPropertyType CreateExtendedProperty<T>(PathToExtendedFieldType id, T value)

   12 {

   13     ExtendedPropertyType result = newExtendedPropertyType();

   14     result.ExtendedFieldURI = id;

   15     result.Item = SerializeValue(value);

   16 

   17     return result;

   18 }

   19 

   20 privatestaticstring SerializeValue<T>(T value)

   21 {

   22     Type type;

   23     TypeConverter converter;

   24     object result;

   25 

   26     result = value;

   27 

   28     if (result == null) returnnull;

   29 

   30     //Unbox nullable types

   31     type = Nullable.GetUnderlyingType(typeof(T));

   32     if (type != null)

   33     {

   34         converter = newNullableConverter(typeof(T));

   35         result = converter.ConvertTo(value, type);

   36     }

   37     else type = typeof(T);

   38 

   39     if (type.IsEnum)

   40     {

   41         result = Convert.ChangeType(result, Enum.GetUnderlyingType(type));

   42         return result.ToString();

   43     }

   44     elseif (type == typeof(DateTime))

   45     {

   46         returnXmlConvert.ToString(((DateTime)result).ToUniversalTime(), XmlDateTimeSerializationMode.Utc);

   47     }

   48     elseif (type == typeof(bool))

   49     {

   50         return ((bool)result) ? "1" : "0";

   51     }

   52     elseif (type == typeof(byte[]))

   53     {

   54         returnConvert.ToBase64String((byte[])result);

   55     }

   56     else

   57     {

   58         returnstring.Format(CultureInfo.InvariantCulture, "{0}", result);

   59     }

   60 }

The SerializeValue<T> method can handle all formats used by the Exchange Web Services and copes with nullable types also.


Technorati:

Posted by Henning Krause on Sunday, October 21, 2007 12:00 AM, last modified on Thursday, November 25, 2010 4:25 AM
Permalink | Post RSSRSS comment feed

A WebDAV PROPPATCH which works for Exchange 2003 breaks with Exchange 2007

If you are using WebDAV to store data in Exchange you may encounter a breaking change when migrating from Exchange 2003 to 2007: A PROPPATCH request which works flawlessly with Exchange 2003 will break with Exchange 2007 returning a 400 Bad Request error.

This may happen if you upload data to the Exchange server containing control characters (chars 0x00 through 0x1f). If this happens inside a HTML-based property (e.g. the HTML body of the email), you can use the following workaround to encode the characters in question. Otherwise you should probably just strip them away.

Workaround

If you are using a .NET based language you can utilize a simply regular expression to convert or strip the invalid values from the data:

    1 Regex _ControlReplacer = newRegex("\\p{Cc}", RegexOptions.Compiled);

    2 

    3 // Use this line if you want to encode the control characters using HTML entities

    4 string result = _ControlReplacer.Replace(data, delegate(Match match) { return"&#x" + Convert.ToInt32(match.Value[0]).ToString("x2") + ";"; });

    5 

    6 // Use this line if you want to strip the control characters from the data

    7 string result = _ControlReplacer.Replace(data, "");


Technorati:

Posted by Henning Krause on Tuesday, September 18, 2007 12:00 AM, last modified on Tuesday, September 18, 2007 12:00 PM
Permalink | Post RSSRSS comment feed

Sending mails with WebDAV with recipients in the BCC field

This article on MSDN show how to send an email through a users mailbox using the DAV submission URI. Basically, you perform these steps:

  1. Construct the email following RFC 2822 (and RFC 2045 for more complex mails)
  2. Use the PUT command to upload the mail to the drafts folder of a users mailbox
  3. Use the MOVE command to move the mail to the DAV submission url.

The mail will then be sent to the recipients specified in the header of the mail using the TO: and CC: headers.

If you try to include a BCC: header to send the mail unnoticed of the other recipients, you will get an Unprocessable Entity error on step 3. This is because the BCC information is an envelope field rather than a header field (See RFC 2821 for more infos about SMTP envelopes and mail header).

To cope with this, you'll have to perform two things:

  1. When uploading the mail to the drafts folder using the PUT method, be sure to specify a content type of message/rfc822.
  2. Before moving the mail to the DAV submission uri, issue a PROPPATCH command on the mail in the drafts folder and set the field urn:schemas:httpmail:bcc to the intended BCC recipients. As described in the article, this is a comma delimited list of all recipients in the form "Someone" <someone@example.com>.

Technorati:

Posted by Henning Krause on Tuesday, September 4, 2007 12:00 AM, last modified on Tuesday, September 4, 2007 12:00 PM
Permalink | Post RSSRSS comment feed

Setting the Meeting organizer with WebDAV

If you plan a meeting with Outlook and invite attendees, Outlook will send meeting requests to all attendees. If they accept the request, a meeting will be created in the attendees calendar. While you can send meeting requests via WebDAV, you might want to create the meetings directly in the attendees mailbox. In this case, you are either creating the meeting with the account of the organizer (in this case that account needs access to the mailboxes of the attendees) are a server account which has those permissions. In either case you might want to control the meeting organizer.

The meeting organizer is stored in three properties:

  • PR_SENT_DISPLAYNAME (http://schemas.microsoft.com/mapi/proptag/x0042001f)
  • PR_SENT_ADDRESSTYPE (http://schemas.microsoft.com/mapi/proptag/x0064001f)
  • PR_SENT_EMAILADDRESS (http://schemas.microsoft.com/mapi/proptag/x0065001f)

The format of the these properties are documented here.


Technorati:

Posted by Henning Krause on Monday, September 3, 2007 12:00 AM, last modified on Monday, September 3, 2007 12:00 PM
Permalink | Post RSSRSS comment feed