Programmatically adding SMTP X-Header

Add-in Express™ Support Service
That's what is more important than anything else

Programmatically adding SMTP X-Header
Programmatically adding SMTP X-Header 
Brad Smith




Posts: 49
Joined: 2005-11-22
I've been away from the forums for a while, and realized with horror that I'd failed to send in the code fragments I promised earlier covering how to add an SMTP X-Header to an outgoing message. The following is going to be a bit disjointed but hopefully can be "converted" to real code.
This is C++ code, with a few bits stripped out. Apologies in advance for formatting problems below:


// Undocumented "magic" GUID that lets us set X-Header values in outgoing SMTP headers
// Supported in OL2000 and above
GUID CLSID_X_Headers = { 0x00020386, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } };

SCODE CMsgHelper::SetSMTPXHeader(LPMESSAGE pMessage, LPCSTR szHeaderName, LPCSTR szValue)
{
  MAPINAMEID NamedProp;
  NamedProp.ulKind = MNID_STRING;
  MAPINAMEID* pNamedProp = &NamedProp;
  LPSPropTagArray lpTags = 0;
  // The name needs to be a wide character, so do a simple conversion
  wchar_t* pcwPropName = new wchar_t[(strlen(szHeaderName)+1)*2];
  memset(pcwPropName, 0, sizeof(pcwPropName));
  mbstowcs(pcwPropName, szHeaderName, strlen(szHeaderName)+1);
  // Get/Create our named properly, then set the value
  // Since this is going in a mail header, we want to make it a human-readable
  // string value, but something that can easily be parsed on receipt.
  NamedProp.lpguid = (LPGUID)&CLSID_X_Headers;
  NamedProp.Kind.lpwstrName = pcwPropName;
  hr = ((LPMAPIPROP)(IUnknown*)pMessage)->GetIDsFromNames( 1, &pNamedProp, MAPI_CREATE, &lpTags);
  delete [] pcwPropName;
  if(S_OK == hr && NULL != lpTags )
  {
    ULONG ulPropTag = lpTags->aulPropTag[0];
    SPropValue prop;
    prop.ulPropTag = PROP_TAG(PT_STRING8,PROP_ID(ulPropTag));
    prop.Value.LPSZ = (LPSTR)szValue;
    hr = HrSetOneProp((LPMAPIPROP)(IUnknown*)pMessage, &prop );
    MAPIFreeBuffer(lpTags);
  }

  return hr;
}


You'd call this something like:
HRESULT hr = CMsgHelper::SetSMTPXHeader(pMessage, \"X-My-Header\", \"My String\" );

This sets a "magic" named property which, when Outlook or Exchange formats the message for SMTP transit, adds a line like:
x-my-header=My String
Note that the name is lowercased. This is a known bug/limitation. Since SMTP headers are expected to be case-insensitive, this isn't much of an issue.
Also note that Outlook/Exchange does absolutely *no* syntax checking. So if' you're not careful, you can render the message unreadable.

More to follow...

Posted 20 Apr, 2006 11:31:53 Top
Brad Smith




Posts: 49
Joined: 2005-11-22
Within an Exchange environment (ie. not sent via the Internet), you can read back this named property using MAPI again:


std::string CMsgHelper::GetSMTPXHeader(LPMESSAGE pMessage, LPCSTR szHeaderName)
{
  std::string strTmp;
  std::string strValue;

  // Attempt to get direct from MAPI first. If that fails, then we'll have
  // to parse the SMTP headers.

  MAPINAMEID NamedProp;
  NamedProp.ulKind = MNID_STRING;
  MAPINAMEID* pNamedProp = &NamedProp;
  LPSPropTagArray lpTags = 0;
  // The name needs to be a wide character, so do a simple conversion
  wchar_t* pcwPropName = new wchar_t[(strlen(szHeaderName)+1)*2];
  memset(pcwPropName, 0, sizeof(pcwPropName));
  mbstowcs(pcwPropName, szHeaderName, strlen(szHeaderName)+1);
  NamedProp.lpguid = (LPGUID)&CLSID_X_Headers;
  NamedProp.Kind.lpwstrName = pcwPropName;
  HRESULT hr = ((LPMAPIPROP)(IUnknown*)pMessage)->GetIDsFromNames( 1, &pNamedProp, 0, &lpTags);
  delete [] pcwPropName;
  if(S_OK == hr && NULL != lpTags )
  {
    // and now the value
    ULONG ulPropTag = lpTags->aulPropTag[0];
    SPropValue* pProp = NULL;
    hr = HrGetOneProp((LPMAPIPROP)(IUnknown*)pMessage, PROP_TAG(PT_STRING8, PROP_ID(ulPropTag)), &pProp );
    if(SUCCEEDED(hr) && PROP_TYPE(pProp->ulPropTag) == PT_STRING8)
    {
      strValue = pProp->Value.LPSZ;
    }
    MAPIFreeBuffer(lpTags);
  }
  return strValue;
}


If the above doesn't return anything, then you reset to grabbing the PR_TRANSPORT_MESSAGE_HEADERS MAPI property, which returns the entire SMTP header blob, and parse out your desired header line (remember, case-insensitive).

I hope this helps, and humble apologies for the delay in getting this out. Now I just get to sit back and wait for somebody to port to C# for me. :-)

Brad.
Posted 20 Apr, 2006 11:42:53 Top
Sergey Grischenko


Add-in Express team


Posts: 7187
Joined: 2004-07-05
Hi Brad.

Thank you very much for the code. I will try to rewrite it in C#.
Posted 20 Apr, 2006 12:04:32 Top
nistech




Posts: 30
Joined: 2005-09-04
Thanks! I look forward to the conversion Sergey.
Posted 22 Apr, 2006 01:26:01 Top
Sergey Grischenko


Add-in Express team


Posts: 7187
Joined: 2004-07-05
Hi Brad.

The code is ready. You can download the example here:
http://www.add-in-express.com/projects/olxheaderexample.zip
Posted 26 Apr, 2006 11:33:46 Top
nistech




Posts: 30
Joined: 2005-09-04
Thanks! I'll take a look.
Posted 26 Apr, 2006 12:26:13 Top
Esteban Astudillo




Posts: 145
Joined: 2006-02-27
This is great!!

Thank you Brad and Sergey!

I will post here later my results using this code.

Posted 26 Apr, 2006 13:52:49 Top
Jim Kimbrough




Posts: 17
Joined: 2006-02-13
Regarding the sample code posted above:

When this code calls SetHeader()...

Outlook._MailItem mitem = item as Outlook._MailItem;
mitem.Save();
SetHeader(mitem.MAPIOBJECT,"x-xyz-header","xyz-xyz");

In the function SetHeader...

private void SetHeader(object itemObj, string headerName, string _value)
{
IMAPIProp mail = null
mail = itemObj as IMAPIProp;

if (mail != null)
.
.
.

ItemObj receives an object argument, but "mail" is always null after itemObj is assigned as IMAPIProp.

What do I need to do to get a successful object assignment to "mail"?

Thank you.
Posted 05 May, 2006 11:18:48 Top
Sergey Grischenko


Add-in Express team


Posts: 7187
Joined: 2004-07-05
Hi Jim.

It is very strange. The fact is that the mitem.MAPIOBJECT always returns IMAPIProp interface if the mitem field is an Outlook item.
Didn't you change the code of the example?
Posted 05 May, 2006 11:53:25 Top
Jim Kimbrough




Posts: 17
Joined: 2006-02-13
Yes, I moved the relevant portions of your sample code, and your references, with new GUIDs, into another project for x-header testing.

It builds. It runs. But this argument always goes to null when assigned to the IMAPIProp mail object. Maybe an interop or duplicate reference problem. I will experiment with alternate references and see if I can raise some other symptom.

Any thoughts about which reference might be at issue? Seems odd since the IMAPIProp interface is specified right in the code.

Thank you, Sergey.

Jim



Posted 05 May, 2006 13:38:18 Top