Outlook Redemption Frequently Asked Questions
• I get an error when I attempt to create an instance of a Redemption object even if Redemption is successfully installed.
When I compile my .Net code in Visual Studio as "Any CPU" and run my code under the 64 bit version of Windows and 32 bit version of Outlook.
When I run my 32 bit executable on a machine with the 64 bit version of Outlook.
• I get a MAPI_E_FAILONEPROVIDER error when I call RDOSession.LogonExchangeMailbox.
• I am using Redemption objects on multiple threads. Is there anything I need to be aware of?
• When I send a message using Redemption, it simply stays in the Drafts folder
• My code using Redemption is causing Outlook to stay in memory even after it is closed.
• When I set SafeMailItem.Item property to an Outlook item, my program crashes
• Outlook XP becomes unstable after I use Redemption
• I am trying to use Redemption with CDO. Are there any tips/tricks?
• Is there any way I can select an account to be used for sending a message with Redemption?
• My C# or VB.Net code start behaving erratically (e.g. MAPI_E_CALL_FAILED error is returned) after I process a large number of items in a folder.
I get an error when I attempt to create an instance of a Redemption object even if Redemption is successfully installed.
The bitness of your application, Redemption, and Outlook/MAPI must all match: Redemption is an in-proc COM library, therefore its bitness must match that of the parent process. Redemption loads MAPI (which is a set of dlls) also in-proc, hence MAPI system bitness must also be the same.
When your compile your code in Visual Studio as "Any CPU", it gets compiled at run-time to match the bitness of the host OS, but you need to match the bitness of Outlook (e.g. 32 bit Outlook under a 64 bit OS). This means you need to compile 2 versions of your app: 32 bit and 64 bit, and install the appropriate version during the installation.
The only time when it makes sense to use "Any CPU" is when your code is in a COM add-in, and therefore its bitness is determined by the bitness of Outlook when it loads your dll.
If you build your app as 32 bit, and the 64 bit version of Outlook is installed, your only option is to recompile your app in 64 bit.
Outlook bitness can be determined at install or run-time from the Bitness string registry value (x64 or x86) at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\16.0\Outlook. For the Click-To-Run (C2R) Outlook installation, the key will be in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\REGISTRY\MACHINE\Software\Microsoft\Office\16.0\Outlook
One possible solution to the problem of mismatched bitness between your application and Outlook is to create Redemption objects inside the Outlook process space using Application.CreateObject method in the Outlook Object Model. Since Redemption object is created inside the outlook.exe address space, it is guaranteed that Redemption bitness will match that of Outlook/MAPI. Redemption objects (all of them are IDispatch-based) will then be marshaled by the COM system into your process, whether the bitness matches or not. One possible drawback is that all calls to the Redemption objects will be marshaled through the Outlook's main thread, which might make lengthy operations impractical.
'start Outlook if it is not running
set app = CreateObject("Outlook.Application")
set ns = app.GetNamespace("MAPI")
ns.Logon
'Create RDOSDession 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 & "'" |
I get a MAPI_E_FAILONEPROVIDER error when I call RDOSession.LogonExchangeMailbox.
This generally happens when you attempt to open a mailbox owned by the domain user different from the current one (parent process wise). You might also get the WSAECONNRESET error if the connection was actively refused - this can happen if a firewall is blocking the connection, or you are running under a user context that has no right to access any network resources (such as the local Service account).
Exchange is really picky about the current user context, so it is a better idea to call LogonExchangeMailbox (once) for the current user, then open another user's mailbox using RDOSession.GetSharedMailbox.
I am using Redemption objects on multiple threads. Is there anything I need to be aware of?
You can use Redemption, and MAPI in general, on multiple threads just fine. The only rule of thumb 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.
Another important point is that MAPI considers the first thread in the process to call MAPIInitialize to be the main thread - MAPI allocates all of its objects with a thread affinity (such as hidden windows used for notifications) on this thread and expects the thread to remain alive until all other threads that use MAPI exit.
This means you will need to create RDOSession object (or any other creatable Redemption object, such as SafeMailItem) on the main thread and ensure it remains alive for the lifetime of your application before you create any other Redemption objects on secondary threads.
If you want to share the same MAPI session between all threads, you can logon on the main thread (Logon, LogonExchangeMailbox, etc), then set the RDOSession.MAPIOBJECT property of the secondary thread's RDOSession to RDOSession.MAPIOBJECT from the main thread.
I am using Outlook 2013 Click-to-Run and some Redemption methods raise "interface not supported" errors when I set RDOSession.MAPIOBJECT, Safe*Item.Item or MAPITable.Item properties
A fix is available
from
http://www.dimastr.com/redemption/Outlook_2013_C2R_fix.ZIP
The fix (provided by Microsoft) restores missing MAPI marshalling registry keys.
Microsoft plans to fix the Outlook 2013 click-to-run installer, but in the mean
time you will need this fix if your code requires marshalling of the MAPI
objects – i.e. it is running in an executable other than outlook.exe (i.e. it is
not a VBA project or a COM add-in) and you are using the Safe*Item family of
objects or set the RDOSession.MAPIOBJECT property to Namespace.MAPIOBJECT from
the Outlook Object Model.
When a retrieve a message from an IMAP4 store using GetMessagefromID in the OnNewMail event, the call hangs. IMAP4 provider also causes frequent crashes,
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.
When you attempt to open a message using GetMessageFromID, the IMAP4 provider
tries to retrieve the data from the IMAP4 server; it does so on a separate
thread, and when it tries to switch to that thread, the call hangs since the new
mail notification is still ongoing (but blocked).
You can either store the entry id and fire a timer (use the Timer class from the
Forms namespace in case of .Net) – by the time the timer event fires, you are
now out of the new mail notification callback, and there should be no hang.
Do not use an async/await in .Net– it will run on a thread where MAPI has not
been initialized. Timer (from the Forms namespace) will fire an event on the
main thread when the application is idle.
Another possible
solution is to bypass the IMAP4 layer and work directly with the underlying PST
store (which the IMAP4 provider is based on).
You can do that by unwrapping the IMAP4 store:
RDOStore unwrappedStore = RDOSession.Stores.UnwrapStore(YourIMAP4Store); RDOMail msg = unwrappedStore.GetMessageFromID(messageEntryId); |
Keep in mind however that most likely the message will be just an envelope
without the body and the attachments.
When I send a message using Redemption, it simply stays in the Drafts folder
Message submission is a two step process in Extended MAPI:
1. Calling IMessage::Submit()
2. Flushing the outgoing message queue.
If you are using an Exchange Server, step #2 is not required since Exchange Message Store is tightly bound with the Exchange Transport provider. If you however are using a POP3/SMTP transport and a PST file as a message store, step #2 is required. To flush the message queues, create an instance of Redemption.MAPIUtils object and call its DeliverNow method (similar to Session.DeliverNow in CDO) after calling SafeMailItem.Send:
MailItem.Send
Set Utils = CreateObject("Redemption.MAPIUtils")
Utils.DeliverNow
There is however one problem if you are using Outlook 2002 or newer with a PST file and POP3/SMTP transport provider or Outlook 2000 installed in the Internet Only Mode: there is no way to flush the queues using Extended MAPI. That part of Outlook is simply broken. Note however that Outlook 2002/2003 (online) with an Exchange Server or Outlook 2000 C/W in any configuration are fine.
If you are using Outlook 2003 or higher with Exchange in a cached mode, it will exhibit the same problem. Uncheck "Use cached mode" in the Exchange Server properties to force online mode - that will ensure that messages are delivered immediately.
As a last resort, you can simulate clicking "Send/Receive" button in Outlook after sending a message:
MailItem.Send
Set Btn = Application.ActiveExplorer.CommandBars.FindControl(1, 5488)
Btn.Execute
Note that in Outlook 2003 that button is now a dropdown, the real Send/Receive is a subitem of the button:
Set Btn = Application.ActiveExplorer.CommandBars.FindControl(1, 7095)
Btn.Execute
Note that the code above assumes that there is an active Explorer; this will not be the case if you start Outlook programmatically (and it was not previously started by a user) and do not display any folders. In this case you can start a sync using the the Namespace.SyncObjects collection.:
set NS = Application.GetNamespace("MAPI")
NS.Logon
Set Sync = NS.SyncObjects.Item(1)
Sync.Start
In Outlook 2010 you can also use Namespace.SendAndReceive method.
When I create an instance of the SafeCurrentUser object, it looks like all of its fields are empty. I am using Outlook 2002 with a POP3/SMTP server and a PST file.
Outlook 2002 broke the part of the Extended MAPI related to retrieving the current user identity if you are using POP3/SMTP transport provider and a PST file as a message store. Microsoft knows about this problem, but no workaround has been provided so far.
When I set a property in Outlook using either Outlook Object Model or through the UI and then read that property using Redemption, it has a different value or is empty.
When you set a property (any property) in Outlook, it is not immediately saved until you explicitly save it by either calling Save or clicking File | Save menu. In the mean time, the property lives in a nowhere land - even though you can access it using Outlook Object Model, Extended MAPI (which Redemption uses) does not yet see the change. To make sure that the change is committed to the Extended MAPI storage, explicitly call Save on the Outlook item; then use Redemption to access it.
I have noticed that accessing a large number of messages in a folder using Redemption can be slower than when using Outlook object model
Outlook tries to optimize access to most message properties when you read them from an Outlook folder. Redemption does not have access to this cache and it needs to explicitly query Extended MAPI for most of the message properties. If you are going to read a large number of messages from an Outlook folder, try to limit the number of the messages first. For example if you are looking for the messages from John Smith, do not loop through all the messages, let Outlook do the dirty work:
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 solution would be to use MAPI Tables.
My code using Redemption is causing Outlook to stay in memory even after it is closed.
Some Redemption objects (such as MAPIUtils or AddressEntry) need Extended MAPI session. For the performance reasons, the session is then saved for later use either by the same or other Redemption objects. Due to the redesigned Extended MAPI support in Outlook 2002, it might not close properly if there is an outstanding reference to the session. To fix the problem, call MAPIUtils.Cleanup or AddressEntry.Cleanup (or SafeCurrentUser.Cleanup); this will cause Redemption to release the Extended MAPI session.
When I set SafeMailItem.Item property to an Outlook item, my program crashes
The chances are you have Visual Studio .Net beta 2 installed. VS. Net beta 2 replaces some system libraries responsible for providing dynamic type-info support, these files are not used by Outlook (that's why it appears to function normally), Redemption however heavily relies on this functionality. To fix the problem either uninstall VS.Net or upgrade to a later version.
When my application is used on a system where Outlook is not the default e-mail client, Redemption complains that a function is missing from mapi32.dll or something to that extend.
Here's what MSDN has to say, essentially you need to create a registry entry to mark your executable as one using Extended MAPI:
Explicitly Mapping MAPI Calls to MAPI DLLs
In some cases, MAPI calls made from a particular DLL or executable file need to be routed to the system MAPI DLL (called Mapi32x.dll) or another custom MAPI DLL even though the default mail client supports the call. Such DLLs or executables files can be listed as string registry values in the HKLM\Software\Microsoft\Windows Messaging Subsystem\MSMapiApps key. The registry value for these keys can be empty or identify a mail client key that resides under HKLM\Software\Clients\Mail.
When the stub library resolves a MAPI call, it first enumerates the DLL and executable files listed under the HKLM\Software\Microsoft\Windows Messaging Subsystem\MSMapiApps key checking to see if the DLL or executable file is currently in process. If there is a match, the stub library gets the string value. If the value is the empty string, the stub library routes the call to the system MAPI DLL, Mapi32x.dll. If the string is not empty, the stub uses the string value to find the key below HKLM\Software\Clients\Mail where it can find the appropriate registry value to dispatch the call, one of DLLPath, DLLPathEx, or MSIComponentID values. For example:
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)
When I call Safe*Item.Import() or Safe*Item.CopyTo(), the resulting message is always shown by Outlook as unsent, even through the original item had been sent.
On the low level, MAPI mandates that some message flags are read/write only before the first save, and read-only after that. In case of sent/unsent message flag (MSGFLAG_UNSENT bit in PR_MESSAGE_FLAGS property), this means that Redemption (i.e. MAPI) cannot override that flag. To work around this you can create the message in the sent state, e.g. instead of creating a MailItem object, create PostItem object - Application.CreateItem(olPostItem), assign it to Safe*Item.Item property, then call Safe*Item.Import():
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 our change to
the MessageClass property
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 is shown with 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
Note that if you are using the RDO family of objects (RDOMail object) you can simply set the RDOMail.Sent property to true before you call Save for the very first time. Unlike OOM, RDOMail.Sent property is read/write.
The following sample scripts create 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
Outlook XP becomes unstable after I use Redemption
Various Redemption objects need Extended MAPI session to function properly. After a session is retrieved, it is saved for later use by other Redemption objects. Outlook XP (unlike other versions of Outlook) may not like that. To make sure the session is not cached, create an instance of Redemption.MAPIUtils object and call MAPIUtils.Cleanup after you are done with Redemption. This will ensure that the shared session is released:
'this code goes after you are done with other Redemption objects
set Utils = CreateObject("Redemption.MAPIUtils")
Utils.Cleanup
I am trying to use Redemption with CDO. Are there any tips/tricks?
Redemption can be easily used with CDO, not just with the Outlook Object Model. SafeXXXItem objects only rely on the objects you assign to its Item property to have the MAPIOBJECT property, which most OOM and CDO objects have.
When used with CDO, some Redemption methods can be sped up quite a bit if you give Redemption the MAPI session you are already using in 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
I noticed that if I send a message using Outlook UI, it is delivered in HTML; if I use Redemption, it is in plain text. Any workarounds?
Selecting an encoding programmatically has always been a mystery in Outlook. Recently, however, things became a bit clearer. You need to set a couple of extra properties:
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
I am using Outlook 98/2000 installed in the Internet Only Mode (IMO) (Help|About in Outlook) and I have noticed that several Redemption features do not work in that mode (C/W mode work fine). Are there are workarounds?
Officially, there are not workarounds. Microsoft does not support Extended MAPI in IMO at all, that was one of the reasons MS got rid of the IMO mode in Outlook 2002. Some things however do work, but there are never any guarantees, sometimes things work, sometimes they don't, even in the same configuration. Below is the list of Redemption objects, methods and properties potentially affected in the IMO mode:
AddressEntry object
MAPIUtils object
SafeMAPIFolder object
CurrentUser object
AddressLists object
Recipient.Resolve method
Recipient.FreeBusy method
Note that most Safe*Item object properties and methods should always work.
I noticed that Redemption supports import and export of the RFC822 (EML) messages (SaveAs/Import). I am writing Extended MAPI code in C++/Delphi, is there any way I can utilize Redemption RFC822 import/export features if I do not use Outlook Object Model or CDO?
Yes. Redemption.dll exports two functions - HrIMessageToRFC822Message() and HrRFC822MessageToIMessage() exactly for that purpose. You will need to load the Redemption.dll using LoadLibrary() Windows API function, then use GetPropAddress() to dynamically load these functions. The functions are defined as follows:
HrIMessageToRFC822Message:
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 - IMAPISEession
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
HrRFC822MessageToIMessage:
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
Example:
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(pTargetMs.g, lpSession, ulFileSize, pszBuffer)) {
pTargetMsg->SaveChanges(KEEP_OPEN_READWRITE)
Is there any way I can select an account to be used for sending a message with Redemption?
Generally speaking, no. If you look at the message sent using non-default account using OutlookSpy, you will notice that Outlook sets a couple of named properties; one of them is the name of the account, another one is a combination of the account integer index and its name. RDOMail object exposes Account property that you can use to read or set the account to be used when sending a message. You can also use MailItem.SendUsingAccount property in Outlook 2007 or higher.
The good news however is that you can do much better than just selecting an account: you can set the sender name and address to an arbitrary value, you do not need to have an account configured with that name and address. The trick is based on the fact that you can add a named property with a particular GUID to an outgoing message and force Outlook to use the name of the property as an RFC822 header and its value as the value of the header. By adding a property with the name "From" and the value in the form "Someone <whoever@domain.com>" you add an RFC822 header:
From: Someone <whoever@domain.com>
Both Exchange and IMAIL providers are smart enough to replace an existing header if one exists, i.e. you will not get two "From" headers. The only limitation is that the message must be converted to the RFC822 format along the way, it will not work if the message is sent between two mailboxes on the same Exchange server. The message in your Sent Items folder will still have the default sender name, but the recipients will see the new value. Whether you use IMAIL (POP3/SMTP) or Exchange provider in Outlook to send a message, doesn't matter at all, it will work in both cases.
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
I am using SafeMailItem.Import to import MSG or EML files, but it looks like ReceivedTime and SentOn are always set to the current time. Is this a Redemption problem? Any workarounds?
This is really an Outlook problem - it always resets these two properties. As a workaround, use Redemption.MessageItem instead: it 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
I process a large number of Outlook items using Redemption. After processing a large (400-500) number of items, Redemption starts returning errors.
Try to create a Redemption object (any object, such as Redemption.MAPIUtils) when your app starts and keep it referenced in a global variable until your app terminates - each Redemption object calls MAPIInitialize when created and MAPIUninitialize when destroyed. Some versions of Outlook have a problem when MAPIInitialize/MAPIUninitialize are called too many times (400-500 times). Having a global object ensures that MAPI is initialized only once.
This problem was fixed in Outlook 2003 SP1.
My C# or VB.Net code start behaving erratically (e.g. MAPI_E_CALL_FAILED error is returned) after I process a large number of items in a folder.
Your code most likely runs out of the 255 RPC channels/process limit enforced by Exchange since .Net does not immediately release COM objects, so it is easy to go over the limit. Try to call GC.Collect() periodically, release COM objects as soon as you are done with them using Marshal.ReleaseCOMObject(), and avoid using multiple dot notation (e.g. Folder.Items.Item(index)) to avoid implicit variables created by the compiler that cannot be explicitly released.
Also look into using the MAPITable object (available either as a standalone object or returned by RDOFolder.Items.MAPITable) - it allows to retrieve properties form multiple messages without opening them (and hence wasting RPC channels).