Troubleshooting notes for common Redemption deployment, threading, message submission, and Outlook compatibility issues.
This error usually means the bitness of your application, Redemption, and Outlook/MAPI do not match. Redemption is an in-process COM library, so its bitness must match the parent process. Redemption also loads the MAPI DLLs in process, so the MAPI system bitness must match as well.
If you compile .NET code as Any CPU, it runs with the bitness of the host OS. That can be wrong when 32-bit Outlook is installed on 64-bit Windows. In that case, build and install the 32-bit version of your application. The only common case where Any CPU makes sense is an Outlook COM add-in, because Outlook determines the add-in process bitness when it loads it.
If your application is 32-bit and 64-bit Outlook is installed, the standalone process cannot load Outlook's 64-bit MAPI stack. Recompile the application as 64-bit or create Redemption objects inside the Outlook process.
Outlook bitness can be detected from the Bitness string value under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\16.0\Outlook. For Click-to-Run installations, check HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\REGISTRY\MACHINE\Software\Microsoft\Office\16.0\Outlook.
One workaround for a standalone application is to create the Redemption object inside outlook.exe through Application.CreateObject. The object is then guaranteed to match Outlook/MAPI bitness, and COM marshals calls back to your process. The tradeoff is that calls are marshaled through Outlook's main thread, which can make lengthy operations impractical.
'start Outlook if it is not running
set app = CreateObject("Outlook.Application")
set ns = app.GetNamespace("MAPI")
ns.Logon
'Create RDOSession object in the Outlook process address space
'Redemption bitness is guaranteed to match Outlook
set Session = app.CreateObject("Redemption.RDOSession")
'use the same MAPI session used by Outlook
Session.MAPIOBJECT = ns.MAPIOBJECT
'now test if it works - the call will be marshaled to Redemption object
'running inside Outlook process
MsgBox "There are " & Session.Stores.Count & " stores in the profile named '" & Session.ProfileName & "'"
You can use Redemption, and MAPI in general, on multiple threads. The rule is that MAPI must be initialized on every thread where a MAPI object is used. Every creatable Redemption object, such as RDOSession, initializes MAPI by calling MAPIInitialize when it is created.
MAPI considers the first thread in the process to call MAPIInitialize the main thread. MAPI allocates objects with thread affinity, such as hidden notification windows, on that thread and expects it to remain alive until all other MAPI-using threads exit.
Create an RDOSession object, or another creatable Redemption object such as SafeMailItem, on the main thread and keep it alive for the lifetime of the application before creating Redemption objects on secondary threads.
To share the same MAPI session across threads, log on on the main thread, then set the secondary thread's RDOSession.MAPIOBJECT property to the main thread's RDOSession.MAPIOBJECT.
This generally happens when you attempt to open a mailbox owned by a domain user different from the current process user. You can also get WSAECONNRESET if the connection is actively refused, for example when a firewall blocks the connection or the process runs under a user context that cannot access network resources.
Exchange is strict about the current user context. Prefer calling RDOSession.LogonExchangeMailbox once for the current user, then use RDOSession.GetSharedMailbox to open another user's mailbox.
If Outlook 2013 Click-to-Run raises "interface not supported" errors when you set RDOSession.MAPIOBJECT, Safe*Item.Item, or MAPITable.Item, install the Microsoft-provided fix from Outlook_2013_C2R_fix.ZIP.
The fix restores missing MAPI marshalling registry keys. It is needed when your code requires MAPI object marshalling, for example when it runs in an executable other than outlook.exe and uses the Safe*Item family or sets RDOSession.MAPIOBJECT from Outlook's Namespace.MAPIOBJECT.
When MAPI fires the new mail event, it does so on a thread different from the one where RDOSession was created. Redemption switches to the main thread by calling SendMessage() on a window handle created on the main thread.
If you open a message through GetMessageFromID during that notification, the IMAP4 provider may try to retrieve data from the IMAP server on a separate thread. When it switches threads while the new mail notification is still blocked, the call can hang.
Store the entry ID and use a timer instead. In .NET WinForms, use the Forms Timer class so the event fires on the main thread when the application is idle. Do not use async/await for this path because it can continue on a thread where MAPI has not been initialized.
Another option is to bypass the IMAP4 layer and work with the underlying PST store by unwrapping the IMAP4 store.
RDOStore unwrappedStore = RDOSession.Stores.UnwrapStore(YourIMAP4Store);
RDOMail msg = unwrappedStore.GetMessageFromID(messageEntryId);
Most likely the retrieved item will only be an envelope without the body and attachments.
Message submission is a two-step process in Extended MAPI:
IMessage::Submit().With Exchange Server, step 2 is not required because the Exchange message store is tightly bound to the Exchange transport provider. With POP3/SMTP transport and a PST store, step 2 is required. To flush the message queue, create a Redemption.MAPIUtils object and call DeliverNow after SafeMailItem.Send.
MailItem.Send
Set Utils = CreateObject("Redemption.MAPIUtils")
Utils.DeliverNow
There is no reliable Extended MAPI way to flush queues in Outlook 2002 or newer with a PST file and POP3/SMTP transport, Outlook 2000 Internet Only Mode, or Outlook 2003+ Exchange Cached Mode. For cached Exchange mode, disable cached mode to force online mode if you need immediate delivery.
As a last resort, simulate the Outlook Send/Receive command after sending the message.
MailItem.Send
Set Btn = Application.ActiveExplorer.CommandBars.FindControl(1, 5488)
Btn.Execute
In Outlook 2003, the button became a dropdown. The actual Send/Receive command is a child command:
Set Btn = Application.ActiveExplorer.CommandBars.FindControl(1, 7095)
Btn.Execute
The code above assumes there is an active Explorer. If Outlook was started programmatically and no folder is displayed, use the Namespace.SyncObjects collection:
set NS = Application.GetNamespace("MAPI")
NS.Logon
Set Sync = NS.SyncObjects.Item(1)
Sync.Start
In Outlook 2010 and newer, you can also use Namespace.SendAndReceive.
Outlook 2002 broke the Extended MAPI area used to retrieve the current user identity when using a POP3/SMTP transport provider and a PST message store. Microsoft knew about this problem, but no workaround was provided.
When you set a property in Outlook, the value is not immediately saved until you explicitly call Save or use the Outlook UI to save the item. Until then, Outlook Object Model can see the changed value, but Extended MAPI, and therefore Redemption, cannot.
Call Save on the Outlook item before reading the property through Redemption.
Outlook optimizes access to many message properties when you read them from an Outlook folder. Redemption does not have access to that cache and must query Extended MAPI for most properties.
If you need to read many messages, limit the set first. For example, do not loop through every item if Outlook can filter by sender:
Set Items = MAPIFolder.Items
Set Msg = Items.Find("[SenderName] = 'John Smith'")
Do While not (Msg is Nothing)
' now you can access the message
Debug.Print Msg.Subject
Set Msg = Items.FindNext
Loop
An even better approach is to use MAPI Tables.
Some Redemption objects, such as MAPIUtils or AddressEntry, need an Extended MAPI session. For performance, the session is cached for later use by the same or other Redemption objects.
Because of the redesigned Extended MAPI support in Outlook 2002, Outlook might not close correctly while there is an outstanding session reference. Call MAPIUtils.Cleanup, AddressEntry.Cleanup, or SafeCurrentUser.Cleanup to make Redemption release the Extended MAPI session.
This was typically caused by Visual Studio .NET beta 2 replacing system libraries used for dynamic type-info support. Outlook appeared to work normally, but Redemption relied on that functionality. The fix was to uninstall the beta or upgrade to a later Visual Studio version.
If Outlook is not the default mail client, MAPI calls from a particular DLL or executable may need to be explicitly routed to the system MAPI DLL. Register your executable under the MSMapiApps registry key.
In short, list the executable or DLL as a string value under HKLM\Software\Microsoft\Windows Messaging Subsystem\MSMapiApps. An empty value routes the call directly to Mapi32x.dll. A non-empty value points to a mail client key under HKLM\Software\Clients\Mail.
HKLM\Software\Microsoft\Windows Messaging Subsystem\MSMapiApps::exchng32.exe = ""
(route call directly to Mapi32x.dll)
HKLM\Software\Microsoft\Windows Messaging Subsystem\MSMapiApps::msspc32.dll = "Microsoft Outlook"
(route call using Microsoft Outlook key under HKLM\Software\Clients\Mail)
At the MAPI level, some message flags are writable only before the first save and read-only after that. For the sent/unsent flag, MSGFLAG_UNSENT in PR_MESSAGE_FLAGS, MAPI cannot override the flag after the item is saved.
One workaround is to create the message in the sent state. Instead of creating a mail item, create a post item, assign it to Safe*Item.Item, then call Safe*Item.Import() or CopyTo().
PR_ICON_INDEX = &H10800003
set Item = Application.CreateItem(olPostItem) 'create a Post item instead of a regular unsent message
Item.Save 'otherwise EntryId is inaccessible
strEntryID = Item.EntryID
set Item = Nothing 'dereference and reopen the item, otherwise Outlook overwrites MessageClass
set Item = Application.Session.GetItemFromID(strEntryID)
Item.MessageClass = "IPM.Note"
set rItem = CreateObject("Redemption.SafeMailItem")
rItem.Item = Item
rItem.Fields(PR_ICON_INDEX) = Empty 'delete the property, otherwise the message has a wrong icon
Item.Save
rItem.Import(...) 'or call CopyTo(), or set the properties one by one
rItem.Save
set Item = Nothing
set rItem = Nothing
If you use the RDO family of objects, set RDOMail.Sent to true before the first save. Unlike Outlook Object Model, RDOMail.Sent is read/write before the first save.
This sample creates a new message in the sent state in the Inbox:
set Session = CreateObject("Redemption.RDOSession")
Session.MAPIOBJECT = Application.Session.MAPIOBJECT
set Inbox = Session.GetDefaultFolder(olFolderInbox)
set Msg = Inbox.Items.Add("IPM.Note")
Msg.Sent = true 'can only do this before the first Save()
Msg.Subject = "Test received message"
Msg.Body = "test body"
Msg.Unread = true
'sent/received dates
Msg.SentOn = Now
Msg.ReceivedTime = Now
'sender related props
msg.Sender = Session.CurrentUser
msg.SentOnBehalfOf = Session.CurrentUser
'all done, save
Msg.Save
Various Redemption objects need an Extended MAPI session. After the session is retrieved, it is saved for later use by other Redemption objects. Outlook XP may not tolerate that cached session as well as other Outlook versions.
To make sure the session is not cached, create a Redemption.MAPIUtils object and call MAPIUtils.Cleanup after you are done with Redemption.
'this code goes after you are done with other Redemption objects
set Utils = CreateObject("Redemption.MAPIUtils")
Utils.Cleanup
Redemption can be used with CDO, not just with Outlook Object Model. Safe*Item objects only require the object assigned to Item to expose a MAPIOBJECT property, which most OOM and CDO objects do.
When using CDO, some Redemption calls are faster if you give Redemption the MAPI session already used by CDO:
set Session = CreateObject("MAPI.Session")
Session.Logon
'now you can tell Redemption that you want to use an existing session:
'all Redemption objects will be using that session from now on until
'Redemption.dll is unloaded.
set Utils = CreateObject("Redemption.MAPIUtils")
Utils.MAPIOBJECT = Session.MAPIOBJECT
Selecting an encoding programmatically has always been unclear in Outlook. Set the editor format and Internet mail override properties before sending.
set Appt = Application.CreateItem(olMailItem)
set sItem = CreateObject("Redemption.SafeMailItem")
sItem.Item = Appt
sItem.Recipients.Add "user@domain.com"
sItem.Recipients.ResolveAll
sItem.Subject = "test subject"
sItem.HTMLBody = "<html><body><b>bold</b> text</body></html>"
PR_InetMailOverrideFormat = &H59020003
ENCODING_PREFERENCE = &H00020000
BODY_ENCODING_TEXT_AND_HTML = &H00100000
ENCODING_MIME = &H00040000
PR_MSG_EDITOR_FORMAT = &H59090003
EDITOR_FORMAT_PLAINTEXT = 1
EDITOR_FORMAT_HTML = 2
sItem.Fields(PR_InetMailOverrideFormat) = ENCODING_PREFERENCE or ENCODING_MIME or BODY_ENCODING_TEXT_AND_HTML
sItem.Fields(PR_MSG_EDITOR_FORMAT) = EDITOR_FORMAT_HTML
sItem.Send
There are no official workarounds. Microsoft did not support Extended MAPI in Internet Only Mode, which is one reason that mode was removed in Outlook 2002. Some features may work in some configurations, but there are no guarantees.
Objects, methods, and properties potentially affected in Internet Only Mode include:
AddressEntry object
MAPIUtils object
SafeMAPIFolder object
CurrentUser object
AddressLists object
Recipient.Resolve method
Recipient.FreeBusy method
Most Safe*Item object properties and methods should still work.
Redemption exports HrIMessageToRFC822Message() and HrRFC822MessageToIMessage() for code that works directly with Extended MAPI rather than Outlook Object Model or CDO. Load Redemption.dll with LoadLibrary(), then use GetProcAddress() to load these functions dynamically.
C/C++:
_stdcall HrIMessageToRFC822Message(LPMESSAGE pMsg, LPMAPISESSION pSession, ULONG *pCount, LPVOID FAR *lppBuffer)
Delphi:
function HrIMessageToRFC822Message(pMsg : IMessage; pSession : IMAPISession; var lpCount : ULONG; var lppBuffer : pointer): HResult; stdcall;
Parameters:
input : pMsg - IMessage, pSession - IMAPISession
output: lpCount - number of bytes in the output buffer, including trailing 0
lppBuffer - output buffer with the RFC822 message, 0 terminated.
Must be freed with MAPIFreeBuffer.
C/C++:
_stdcall HrRFC822MessageToIMessage(LPMESSAGE pMsg, LPMAPISESSION pSession, ULONG Count, LPVOID FAR lpBuffer)
Delphi:
function HrRFC822MessageToIMessage(pMsg : IMessage; pSession : IMAPISession; Count : ULONG; lpBuffer : pointer): HResult; stdcall;
Parameters:
input : pMsg - IMessage
pSession - IMAPISession
Count - number of bytes in the input buffer
lpBuffer - input buffer with the RFC822 message
typedef HRESULT (_stdcall *HrRFC822MessageToIMessage)(LPMESSAGE pMsg, LPMAPISESSION pSession, ULONG Count, LPVOID FAR lpBuffer);
HrRFC822MessageToIMessage pfnHrRFC822MessageToIMessage = NULL;
hInst = LoadLibrary("c:\\temp\\redemption.dll");
if (hInst) {
pfnHrRFC822MessageToIMessage = (HrRFC822MessageToIMessage)GetProcAddress(hInst, "HrRFC822MessageToIMessage");
// load an RFC822 formatted file into pszBuffer buffer of size ulFileSize
...
// dump the contents of pszBuffer into an existing IMessage (pTargetMsg)
if (S_OK == pfnHrRFC822MessageToIMessage(pTargetMsg, lpSession, ulFileSize, pszBuffer)) {
pTargetMsg->SaveChanges(KEEP_OPEN_READWRITE);
}
}
For account selection, use RDOMail.Account to read or set the account used for sending a message. You can also use Outlook 2007 and newer MailItem.SendUsingAccount.
You can do more than select an account: you can set the sender name and address to an arbitrary value. The trick is to add a named property with the name From. Outlook converts that property to an RFC822 From header when the message is converted to MIME.
Both Exchange and Internet Mail providers replace an existing From header, so recipients do not receive two headers. This works only when the message is converted to RFC822 along the way; it will not work between two mailboxes on the same Exchange server. The Sent Items copy still shows the default sender, but recipients see the new value.
set sItem = CreateObject("Redemption.SafeMailItem")
sItem.Item = MailItem
tag = sItem.GetIDsFromNames("{00020386-0000-0000-C000-000000000046}", "From")
tag = tag or &H1E 'the type is PT_STRING8
sItem.Fields(Tag) = "Someone <whoever@domain.com>"
sItem.Subject = sItem.Subject 'to trick Outlook into thinking that something has changed
sItem.Save
This is an Outlook behavior: it resets these two properties. As a workaround, use Redemption.MessageItem, which does not try to be smart.
MailItem.Save 'Save the OOM object just to make sure EntryID is available
EntryID = MailItem.EntryID 'remember the entry id
set Utils = CreateObject("Redemption.MAPIUtils")
set rMessage = Utils.GetItemFromID(EntryID) 'reopen the same message as Redemption.MessageItem
rMessage.Import "c:\test.eml", 1024
rMessage.Save
'important: do not modify and save the original object (MailItem) after this
'you will get an error saying that the message has changed
Create a Redemption object, such as Redemption.MAPIUtils, when the application starts and keep it referenced in a global variable until the application terminates. Each Redemption object calls MAPIInitialize when created and MAPIUninitialize when destroyed. Some Outlook versions had problems after hundreds of repeated initialize/uninitialize cycles.
This problem was fixed in Outlook 2003 SP1.
Your code most likely runs out of the 255 RPC channels per process limit enforced by Exchange. .NET does not immediately release COM objects, so it is easy to exceed the limit.
Call GC.Collect() periodically, release COM objects as soon as you are done with them using Marshal.ReleaseCOMObject(), and avoid multiple-dot notation such as Folder.Items.Item(index). Multiple-dot notation creates implicit variables that cannot be explicitly released.
Also look into using MAPITable, either as a standalone object or returned by RDOFolder.Items.MAPITable. It retrieves properties from multiple messages without opening each message, which avoids wasting RPC channels.