So far I've got this working with an orchestration with a dynamic send port. It's still a bit of work, but it gets the job done with the stock component. Following description is based on the stock SMTP-adapter included in BizTalk 2013R2.
Note: even though my solution works, it feels like a workaround and something i shouldn't have to do, if the adapter was just slightly smarter about this.
First of all, let's look example email snippet which causes issues in some clients:
------=_NextPart_000_0001_01D4502F.8A6A1500
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset="utf-8"
See attached email.
------=_NextPart_000_0001_01D4502F.8A6A1500
Content-Type: application/pdf; name="CDM_Order - Copy.pdf"
Content-Disposition: attachment; filename="CDM_Order - Copy.pdf"
Content-Description: body
Content-Transfer-Encoding: base64
JVBERi0xLjQKJeLjz9MNCjUgMCBvYmoKPDwvRFsgMyAwIFIvWFlaIG51bGwgODQxLjg4OTc3IG51
bGwgXQo+PgplbmRvYmoKOCAwIG9iago8PC9EWyAzIDAgUi9YWVogbnVsbCAyOTAuMjM2NTcgbnVs
bCBdCj4+ (etc etc base64 your file)...
Notice the Content-Description: body
part. This is the reason why some clients read body.xml
or in my case body.pdf
, even though the Disposition part looks great: Content-Disposition: attachment; filename="CDM_Order - Copy.pdf"
.
Hard setting MIME.FileName
isn't just going to work, even though it will set the Content-Disposition
right eventually, it'll never update the Content-Description
. This is because either on a static send port you've set the Attach only body part
or you specified the corresponding numeric value 1
on a dynamic send port.
However, it will work with the Attach all parts
or 2
value for the type MessagePartsAttachments
. This involves making a multi-part message in your orchestration. This will have two parts;
- First one is the
BodyPart
, now this one will include your message text and not your attachment. Make sure you specify this one as Message Body Part
in the Message Type
.
- Second part will be your actual attachment, specify this type according to your attachment type. I named this
Attachment
in this example.
Now you might think it will send the BodyPart
as attachment as well since i've said we needed Attach all parts
. This is true, so to correct that, your BodyPart
has to be defined as a RawString
, this turns the string into plain text in the BizTalk message part. For completeness i'll put the C# class at the bottom for reference.
Now that it's defined as a RawString
, the SMTP adapter will put this as the body instead of as attachment. As a side effect, the SMTP adapter will no longer put the Content-Description: body
part in the attachment part, but in the actual body part instead. It looks like this:
------=_NextPart_000_0001_01D450E4.A7E9A5E0
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset="utf-8"
Content-Description: body
See attached email.
------=_NextPart_000_0001_01D450E4.A7E9A5E0
Content-Type: application/pdf; name="ID_0_Nummer_0.pdf"
Content-Disposition: attachment; filename="ID_0_Nummer_0.pdf"
Content-Transfer-Encoding: base64
JVBERi0xLjQKJeLjz9MNCjUgMCBvYmoKPDwvRFsgMyAwIFIvWFlaIG51bGwgODQxLjg4OTc3IG51
bGwgXQo+PgplbmRvYmoKOCAwIG9iago8PC9EWyAzIDAgUi9YWVogbnVsbCAyOTAuMjM2NTcgbnVs
bCBdCj4+ (etc etc base64 your file)...
Really nothing else is different except the placement of the Content-Description: body
part, exactly what we want. Now the email looks fine for every client.
The most important properties, besides the ones i already mentioned, must be set as well to make it behave properly:
Content type of your body:
MsgPdfOrder.BodyPart(Microsoft.XLANGs.BaseTypes.ContentType) = "text/plain";
Content type of your attachment:
MsgPdfOrder.Attachment(Microsoft.XLANGs.BaseTypes.ContentType) = "application/pdf";
Attachment filename:
MsgPdfOrder.Attachment(MIME.FileName) = "CDM_Order - Copy.pdf"
Body character set (will result in Unknown Error Description
if not set):
MsgPdfOrder(SMTP.EmailBodyTextCharset) = "UTF-8";
Make sure you don't set the SMTP.EmailBodyText
because we already have the BodyPart
for that.
RawString class, use it like this in an orchestration MsgPdfOrder.BodyPart = new Yournamespace.Components.RawString("See attached email.");
:
using System.Runtime.Serialization;
using System;
using System.IO;
using System.Text;
using System.Xml.Serialization;
using Microsoft.XLANGs.BaseTypes;
namespace Yournamespace.Components
{
public abstract class BaseFormatter : IFormatter
{
public virtual SerializationBinder Binder
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public virtual StreamingContext Context
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public virtual ISurrogateSelector SurrogateSelector
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public abstract void Serialize(Stream stm, object obj);
public abstract object Deserialize(Stream stm);
}
public class RawStringFormatter : BaseFormatter
{
public override void Serialize(Stream s, object o)
{
RawString rs = (RawString)o;
byte[] ba = rs.ToByteArray();
s.Write(ba, 0, ba.Length);
}
public override object Deserialize(Stream stm)
{
StreamReader sr = new StreamReader(stm, true);
string s = sr.ReadToEnd();
return new RawString(s);
}
}
[CustomFormatter(typeof(RawStringFormatter))]
[Serializable]
public class RawString
{
[XmlIgnore]
string _val;
public RawString(string s)
{
if (null == s)
throw new ArgumentNullException();
_val = s;
}
public RawString()
{
}
public byte[] ToByteArray()
{
return Encoding.UTF8.GetBytes(_val);
}
public override string ToString()
{
return _val;
}
}
}