Click here to Skip to main content
Email Password   helpLost your password?
Screenshot of a merged email

Introduction

There are numerous occasions where an application has to send e-mails: Feedback forms in the web, webmailers, loggers and so forth. And there are many commercial solutions available: EASendMail SMTP Component, MailBee.NET SMTP Component, Aspose.Network.Mail or aspNetEmail, just to mention some of them. If you're looking for a free solution with source code, supplying comparable features, you'll find it worthwhile having a closer look at MailMergeLib:

Feature Extending System.Net.Mail
Sender addresses (From, Sender) v Name and address may contain {placeholders}
Recipients addresses (To, Cc, Bcc) v Name and address may contain {placeholders}
Return addresses (ConfirmReading, Return Receipt, ReplyTo) v Name and address may contain {placeholders}
Test address (for tests before sending bulk mails) v Name may contain {placeholders}
HTML Body with automatically including Linked Images v Can be a template containing {placeholders}. It is optional.
Text Body v Can be a template containing {placeholders}. The HTML Body can be converted (i.e. without tags or entities) to the Text Body. It is optional.
Synchronous programming model v Events firing when mail or mailmerge is starting, in progress, failure or ending
Asynchronous programming mode v Events firing when mail or mailmerge is starting, in progress, failure or ending
Message output v SMTP server, Directory, IIS Pickup Directory, EML file
Calculate size of a mail message v
Mail Merge from DataTable or DataSet / DataMember v
Mail Merge from any IList or IEnumerable Datasource v Even enumerations like Arrays of anonymous types will work
Delay time between mail messages when merging v
MaxFailures, RetryDelayTime and MaxFailures for error handling v
Ignore empty mail messages during mail merge v
X-Mailer Header v
Mail priority Header and other custom headers v
Set local host name for SMTP hello command v A question of privacy: By default the computer name is used.
{placeholders} v Can be formatted with standard .NET format strings (culture specific) or a custom IFormatProvider, and they can have special display values for Null or Empty values

Internally MailMergeLib is using (and extending) the .NET System.Net.Mail library. Generally speaking, this works quite nice. Unfortunately Microsoft has not yet overcome its quite annoying bugs and RFC violations that their library was suffering from the very beginning. But fortunately MailMergeLib provides ways to fix the bugs in System.Net.Mail. - please see section "Bug fixes for System.Net.Mail".

Using the Code

For sending a mail in System.Net.Mail, you'll first create a MailMessage, and then send it with SmtpClient. In MailMergeLib this is quite similar: you'll create a MailMergeMessage and then send it with MailMergeSender.

MailMergeMessage

Create a New Message

So first let's create the message and adjust some settings:

MailMergeMessage mmm = new MailMergeMessage();
mmm.CharacterEncoding = Encoding.ASCII;
mmm.CultureInfo = new System.Globalization.CultureInfo("en-US");
mmm.TextTransferEncoding = System.Net.Mime.TransferEncoding.SevenBit;
mmm.BinaryTransferEncoding = System.Net.Mime.TransferEncoding.Base64;

Here's how to set the properties for a message which will contain an attachment:

Add Content including Placeholders

mmm.Subject = "CRON Job Status Report for Domain {DomainName}";
mmm.PlainText = "CRON Job successfully completed: 
		{JobEndDateTime:"{0:yyyy-MM-dd hh:mm}"}";

One big advantage of MailMergeLib comes from placeholders. {Placeholders} can be used in e-mail addresses, in the subject, in the plain text or HTML body, in text attachment content and in attachment names. Placeholders are the names of e.g. IList item properties, or the column names of a data table.

Add a DataSource

Working with placeholders requires to set the Datasource property of the MailMergeMessage. This is extremely flexible and can be any data source that you could use with .NET BindingSource: One of the most simple ones are data tables, but you can also use entity collections of O/R mappers, or even an array of anonymous type:

mmm.DataSource = new[]
     {
        new {AdminEmail = "harry@example.com", AdminName = "Harry",
            DomainName="example1.com", JobEndDateTime = DateTime.Now},
        new {AdminEmail = "jeff@example.com", AdminName = "Jeff",
            DomainName="example2.com", JobEndDateTime = DateTime.Now.AddDays(1)}
     };

In case of O/R mappers, an entity field could look like this: Department.Leader.FirstName. So you could use the field name as a placeholder: "{Department.Leader.FirstName}". Your DataSource would then be e.g. an entity collection Collection<Department>.

Formatting Capabilities

The placeholders and the formatting capabilities allow separation of code and design. In order to change the content of an e-mail, just change the template file for the plain text and you're done without a need to touch your code.

It is possible to add standard .NET formatting attributes to your placeholders. For a date this could look like this: {JobEndDateTime:"{0:yyyy-MM-dd hh:mm}"}. But what if a value is null? Just add another format string for Null values: {DomainName:"{0}{null:Domain unknown.}"}. In case of strings, there is another format option with "empty" in addition to "null".

If a column of a DataTable has ExtendedProperties for null, empty and/or format, these properties can be used. Examples:

myDataTable.Columns["DomainName"].ExtendedProperties.Add("format", "{0}");
myDataTable.Columns["DomainName"].ExtendedProperties.Add("empty", "Domain unknown.");

Of course, data types and formatting attributes must fit each other.

You can insert a text file by using the special syntax {IncludeFile:"file"}."IncludeFile" is the variable name (e.g. the column name of a DataTable) while file means to interpret the variable value as a file which content shall be included. Such text files may also include placeholders.

If all these formatting capabilities are still not enough, set your own IFormatProvider:

mmm.GetTextVariableManager().FormatProvider = myFormatProvider;

Message Body

The message body parts can be added or changed quite easily either by using them as a parameter in the constructor, or by setting the properties. You can either supply HTML or plain text or both.

mmm.HtmlText = new System.IO.StreamReader("HtmlBody.html").ReadToEnd();
// set the plain text either by converting Html or by setting it independently
// (or leave it empty).
mmm.PlainText = _mmm.ConvertHtmlToPlainText(null);  	// use the default converter 
						// or provide your own

MailMergeMessage supplies the ParsingHtmlConverter (default) for converting HTML to plain text. It uses the Microsoft open source Html Agility Pack (HAP) (and thus the assembly HtmlAgilityPack.dll is required) and has a similar approach like Markdown by John Gruber. It converts the most common HTML tags and attributes which appear in e-mails, not on web pages, to plain text with quite good results. If this is not available or fails, the very basic RegExHtmlConverter is used (similar to this solution), unless you provide your custom converter implementing IHtmlConverter.

More magic comes with image tags included in the HTML content: Just set the directory where your Linked Images are located...

mmm.FileBaseDir = "Drive:\\PathToImageDirectory";

... and edit your HTML image tags as usual. (Instead of setting the FileBaseDir, you can also use the HTML tag <base href="file://Drive:/PathToImageDirectory">, or simply provide absolute paths to your image files. Your image files will be automatically attached as "linked images".

<img src="imagefile.png" width="48" height="48" 
	alt="icon: job successfully completed" />

Attachments

You may also want to add some personalized attachments by adding placeholders to the file name. E.g.:

mmm.FileAttachments.Add
    (new FileAttachment("testmail_{AdminName}.pdf", "sample.pdf", "application/pdf"));

And that's the way to add string attachments:

mmm.StringAttachments.Add(new StringAttachment
    ("Some programmatically created content", "logfile.txt", "text/plain"));

Mail Addresses

For sending a mail, we need supply at least one recipient's address and the sender's address. Again, using placeholders makes it possible to create personalized e-mails.

mmm.MailMergeAddresses.Add(new MailMergeAddress(MailAddressType.To, "{AdminEmail}",
    "{AdminName}", Encoding.ASCII));
mmm.MailMergeAddresses.Add(new MailMergeAddress(MailAddressType.From,
    "sender@example.com", Encoding.ASCII));

Miscellaneous

If you're using a data source with variables, it may well happen that you have recipients with empty e-mail fields. That's why you may want to not throw an exception with empty addresses.

mmm.IgnoreEmptyRecipientAddr = true;

Want to change MailMergeLib's identification? Set the mail's Xmailer header to anything you like.

mmm.Xmailer = "MailMergeLib 3.0";

MailMergeSender

First of all, MailMergeSender is much like System.Net.Mail.SmtpClient: Create an instance of the class and provide some SMTP related settings.

SMTP Settings

Setup the mail sender:

MailMergeSender mailSender = new MailMergeSender();
mailSender.MessageOutput = MessageOutput.SmtpServer;

Set up SMTP server login details:

mailSender.SmtpHost = "smtp.server.com";
mailSender.SmtpPort = 25;
mailSender.SetSmtpAuthentification("username", "password");
mailSender.LocalHostName = "my.localhostname.com"; // used in SMTP Hello command

EventHandlers

With SmtpClient, you cannot add custom event handlers. MailMergeSender will raise events OnBeforeSend, OnAfterSend, OnSendFailure, OnMergeBegin, OnMergeComplete, and OnMergeProgress:

mailSender.OnAfterSend += (delegate(object obj, MailSenderAfterSendEventArgs args)
         {    // do something useful here, like updating a progress bar   });

Sending a Message

For the Send job, there are several alternatives:

  1. Send a single message as an asynchronous operation:
    mmm.DataSource = myDataSource;  // setting a DataSource is optional
    mmm.CurrentDataPosition = 2;    // only if a DataSource is set
    mailSender.SendAsync(mmm);
  2. Send all messages for all items in the DataSource as an asynchronous operation:
    mmm.DataSource = myDataSource;
    mailSender.SendAllAsync(mmm);
  3. Send messages as a synchronous operation for all items in the DataSource:
    mmm.DataSource = myDataSource;
    mailSender.SendAll(mmm);
  4. Or just send a single message providing a Dictionary with name/value pairs synchronously:
    Dictionary<string, string> nameValuePairs = new Dictionary<string, string>();
    nameValuePairs.Add("Email", "to@addr.com");
    nameValuePairs.Add("Nickname", "Bill");
    mmm.SetVariables(nameValuePairs);
    mailSender.Send(mmm);

Cancelling a Send Operation

Asynchronous Send operations can be cancelled at any time:

mailSender.SendCancel();

Influencing Error Handling

Timeout in milliseconds:

mailSender.Timeout = 100000;

Maximum number of failures until sending a message will finally fail:

mailSender.MaxFailures = 3;

Retry delay time between failures:

mailSender.RetryDelayTime = 3000;

Delay time between each message:

mailSender.DelayBetweenMessages = 1000;

Bug Fixes for System.Net.Mail

Sure, with System.Net.Mail, you can send mail messages. The point is: some bugs and RFC violations will increase the spam rating of spam filters for your message, and mail clients may even show parts as garbage. MailMergeLib addresses the following issues:

Bugs fixed in .NET 3.5 SP1:

  1. MailMessage.MailAddressCollection.Clear does not clear the corresponding headers. If you want to re-use an existing MailMessage object for different recipients, you will have to remove the recipients headers yourself (e.g. MailMessage.Headers.Remove("to"))
  2. Setting the transfer encoding to TransferEncoding.SevenBit turns into a header text sevenbit, instead of 7bit. sevenbit is not RFC compliant and causes problems with some mail clients.
  3. MailMessage.To.ToString() returns the encoded string only after the message was sent. According to the documentation, this should be the case no matter whether the message was already sent or not.
  4. Display names of mail addresses that are not all 7bit characters must be encoded. This works fine with every address type except for to addresses. to addresses will never be encoded, no matter which Encoding parameter is used for a new MailAddress.

Bugs not fixed up to .NET 4.0:

  1. Quoted-Printable encoding is not limited to a maximum of 76 characters in System.Net.Mail. RFC 2045 requires that Quoted-Printable encoding encodes lines be no more than 76 characters long. If longer lines are to be encoded with the Quoted-Printable encoding, "soft" line breaks must be used.
  2. White space in display names of a MailAddress must be encoded (RFC2047 and RFC 822 violation).
  3. With MailMessage.Headers there is a bug where headers will have white space in an encoded text. This will lead to non-RFC 2047 compliant messages.
  4. MailMessage.Subject is not encoded correctly. Same problem as with headers.
  5. Attachment.ContentDisposition.FileName must be encoded, if it contains other characters than 7bit or spaces. System.Net.Mail does not encode filenames, but throws a FormatException 'MailHeaderFieldInvalidCharacter', so that such filenames cannot be used at all.
  6. If the display name of an attachment contains non-ASCII characters System.Net.Mail throws an exception instead on encoding them. Anyway: The display name of an attachment must be encoded if it contains non-ASCII characters (e.g. Chinese, German Umlaut, etc.)
  7. When talking to an SMTP server, SmtpClient should double all dots at the beginning of a line for all text-based content. This works fine if MailMessage.Body is used. But it does not work if AlternateViews or text attachments are used. The result is that these dots get lost or - even worse - all content after the dot gets lost if it is followed by CRLF (RFC 2821 violation).
  8. Each line of characters MUST be no more than 998 characters, and SHOULD be no more than 78 characters, excluding the CRLF (RFC2822 violation).

So although Microsoft has supplied some bug fixes, quite a few can be considered as persistent bugs without too much hope for getting them fixed. All bugfixes are included in a static class Bugfixer which hopefully won't be needed in one of the glorious future versions of .NET. Pie in the Microsoft sky?

The bug fixes supplied make heavy use of System.Reflection in order to hack System.Net.Mail. The drawback of this is, that any changes introduced by Microsoft to non-public class members will break bug fixing. In the best case an Exception is thrown, in the worst case you do not notice it. You are warned: Every update of the .NET Framework requires testing of Bugfixer.

Note: If needed, you can easily switch off all bug fixing by setting the IsSwitchedOn property of Bugfixer to false.

Conclusion

By fixing the bugs in System.Net.Mail, it was interesting to dig into its design and to supply modifications using System.Reflection. Although all the bugs mentioned were reported to Microsoft a very long time ago, they didn't remove them. Life could be so easy... Anyway, MailMergeLib works well (again, after .NET 3.5 SP1 and .NET 4.0 partly broke it) and gives a lot more than just bug fixes. It should be noted, however, that it is not designed to send bulk mail with thousands of messages at a time.

Special Thanks To

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralRe: Period(dot) in the very first position of the line
Member 727407
18:11 26 Oct '09  
I used QuotedPrintable encoding, but when it wraps the line if the very first character is . it causes some problems.
The . in the very first column of the line should be replaced with =2E and it solves the problem.

I made this modification in the QPEncoder.cs line 433 (based on the last version)

I modified this line:
stringwriter.Write(towrite);

To these lines:
if (columnposition == 1 && towrite == ".")
{
columnposition += 2;
stringwriter.Write("=2E");
}
else
{
stringwriter.Write(towrite);
}

Is it the right solution?
AnswerRe: Period(dot) in the very first position of the line [modified]
Norbert Bietsch
15:03 30 Oct '09  
Hi!
First of all IMHO this is another bug in SmtpClient. Just to make clear to any reader what it is all about:
Sending a message with these 2 lines
..
...
will be processed by SmtpClient in a way, that the recipient will get
.
..
This is - again - due to a violation of RFC 2821[^] , section 4.5.2 (transparency).

I think the right workaround is what you mentioned in your first post. However, this has nothing to do with encoding quoted-printable the right way. It's the responsibility of SmtpClient to have an RFC compliant transfer of any message to the SMTP server "as is".

For the moment I don't have an idea how to hack SmtpClient in order to solve the "first dot problem".

Norbert

modified on Tuesday, November 10, 2009 2:44 PM

AnswerRe: Period(dot) in the very first position of the line
Norbert Bietsch
12:50 29 Nov '09  
System.Net.Mail does not handle the first dot of a line properly in all attachments with transfer encoding set to TransferEncoding.QuotedPrintable or TransferEncoding.SevenBit. It does, however, with text in MailMessage.Body. I found a way to fix the problem. A new version of MailMergeLib will be released shortly (after some more testing).
GeneralGetting exception: "Field 'System.Net.Mail.SmtpClient.localHostName' not found.
jwhurst
8:29 13 Jul '09  
Hello, my compliments upon an excellent article and upon your well-documented code! And I deeply appreciate your sharing it with us.

I'm trying to use it to send a single email message, very simplistic at this point just to get a message out. I'm using Windows Vista SP1, Visual Studio 2010 beta, and the .NET Framework 4.0 Have you tried it with this framework?

It throws an exception when I call MailSender.Send, saying "Field 'System.Net.Mail.SmtpClient.localHostName' not found."

Tracing it into your code, I see that that happens within the setter localHostName, in MailMergeSender.cs

I'm a bit confused by this portion of your code. You have two setters with almost identical names: LocalHostName, and localHostName. I don't understand the comment on the 2nd: "Gets or sets the name of the local machine sent to the SMTP server a SMTP transaction by using a private member of System.Net.Mail.SmtpClient". But in anycase, I guess you're using reflection here to set this value and perhaps the method signature has changed now?

I appreciate your insight on this - thank you.

jh

James Hurst
"The greatness of a nation and its moral progress can be judged by the way its animals are treated."
Mahatma Gandhi

AnswerRe: Getting exception: "Field 'System.Net.Mail.SmtpClient.localHostName' not found.
Norbert Bietsch
9:55 13 Jul '09  
Thanks for the compliments and your vote Rose
In version 4 beta of the .NET Framework the private member "localHostName" of the SmtpClient class was removed by Microsoft Mad . I'll do more investigation on the system.net.mail classes of .NET Fx 4 as soon as I find some time (preferably after it's released). See my post below for a workaround.
GeneralRe: Getting exception: "Field 'System.Net.Mail.SmtpClient.localHostName' not found.
jwhurst
10:39 13 Jul '09  
Thank you Norbert. I noticed that SmtpClient does have a property called "Host" - could that be what Microsoft intends us to use for it? I substituted that, and it seems to work. So, in the context of my limited, barely-minimal test - yes your lib seems to work with .NET 4 thus far.

James Hurst
"The greatness of a nation and its moral progress can be judged by the way its animals are treated."
Mahatma Gandhi

AnswerFixed: Getting exception: "Field 'System.Net.Mail.SmtpClient.localHostName' not found.
Norbert Bietsch
11:12 13 Jul '09  
No, this won't work. "Host" is the SMTP host to be used, while localHostName is the name of the sending machine. See my post below for the slight change to be made in MailMergeSender.cs.
GeneralRe: Getting exception: "Field 'System.Net.Mail.SmtpClient.localHostName' not found.
fsifabio
9:57 13 Jul '09  
Read the topic below!
GeneralField 'System.Net.Mail.SmtpClient.localHostName' not found.
fsifabio
7:53 13 Jul '09  
Hi,

I'm trying to send e-mail using the example project from my machine (WIN 7 release).
I did some tests on VISTA OS, and WIN 7(BETA), and it's works fine.

I tried to force the _mailSender.LocalHostName, but doesn't work.

Can you help-me?

Thanks.
AnswerRe: Field 'System.Net.Mail.SmtpClient.localHostName' not found.
Norbert Bietsch
8:25 13 Jul '09  
MailMergeLib is not (yet) verfied to work with .NET Framework 4.0. In version 4 (at least in the beta I have) the private member "localHostName" of the SmtpClient class seems to be removed by Microsoft, and there's no equivalent. I wonder why, but still...
As a fast workaround please remove localHostName = _localHostName; in PrepareSettings() of MailMergeSender.cs
GeneralRe: Field 'System.Net.Mail.SmtpClient.localHostName' not found.
fsifabio
9:58 13 Jul '09  
thanks!
it works!
AnswerFixed: Field 'System.Net.Mail.SmtpClient.localHostName' not found.
Norbert Bietsch
11:09 13 Jul '09  
In .NET Framework 4.0 (beta) you have to invoke member "clientDomain" instead of "localHostName". It seems that Microsoft did some refactoring of the code. This is the relevant part of MailMergeSender.cs:
private string localHostName
{
get
{
// .NET 4 beta: invoke member "clientDomain"
return typeof(SmtpClient).InvokeMember("localHostName",
BindingFlags.DeclaredOnly |
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.GetField, null, _smtp, null).ToString();
}
set
{
string heloName = string.IsNullOrEmpty(value) ? System.Environment.MachineName : value;
// .NET 4 beta: invoke member "clientDomain"
typeof(SmtpClient).InvokeMember("localHostName",
BindingFlags.DeclaredOnly |
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.SetField, null, _smtp, new object[] { heloName });
}
}

GeneralRe: Fixed: Field 'System.Net.Mail.SmtpClient.localHostName' not found.
Joe Gershgorin
14:10 17 Oct '09  
Event though I'm not running .NET 4.0 I ran into the same issue. I checked my System.Net version in the GAC, it's 3.5.0.0 with a file version of 3.5.30729.4926. The only thing I can think that's probably different about my setup is I'm running under Windows 7. I change the getter/setter to check for Windows 7/Server 2008 or newer version and that seemed to work:

private string localHostName
{
get
{



if (System.Environment.Version.Major == 4 ||
(System.Environment.OSVersion.Version.Major > 6) ||
(System.Environment.OSVersion.Version.Major == 6 && System.Environment.OSVersion.Version.Minor >= 1)
)
{
return typeof(SmtpClient).InvokeMember("clientDomain",
BindingFlags.DeclaredOnly |
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.GetField, null, _smtp,
null).ToString();
}

if (System.Environment.Version.Major == 2 && System.Environment.Version.Build == 50727)
{
return typeof(SmtpClient).InvokeMember("localHostName",
BindingFlags.DeclaredOnly |
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.GetField, null, _smtp, null).ToString();
}

return System.Environment.MachineName;
}
set
{
string heloName = string.IsNullOrEmpty(value) ? System.Environment.MachineName : value;

// member "localHostName" changed to "clientDomain" in .NET Fx 4.0
if (System.Environment.Version.Major == 4 ||
(System.Environment.OSVersion.Version.Major > 6) ||
(System.Environment.OSVersion.Version.Major == 6 && System.Environment.OSVersion.Version.Minor >= 1)
)
{
typeof(SmtpClient).InvokeMember("clientDomain",
BindingFlags.DeclaredOnly |
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.SetField, null, _smtp, new object[] { heloName });
}
else if (System.Environment.Version.Major == 2 && System.Environment.Version.Build == 50727)
{
typeof(SmtpClient).InvokeMember("localHostName",
BindingFlags.DeclaredOnly |
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.SetField, null, _smtp, new object[] { heloName });
}

}
}

GeneralRe: Fixed: Field 'System.Net.Mail.SmtpClient.localHostName' not found.
ncsol
3:00 16 Nov '09  
FYI...
I found that my Windows 7 upgrade (from XP) shows an OS version of > 6 (as expected) but the System.Environment.Version.Major still equals 2 (not expected. I expected Net 4.x to be instaslled with Windows 7) and still didn't work. I was running a Net 3.5 project under VS2008. I also commented out the localHost == _localHost and that didn't work either, but with a different error. Due to time contraints, I had to use the regular system.netmail class in the framework and that worked (as expected).

just some thoughts
GeneralA question about ShouldUseBase64Encoding.
xrascal
20:58 12 Jul '09  
hi Norbert,
Why don't you use Base64 Encoding to Encode mail subject direct? Why do you use ShouldUseBase64Encoding to auto- choose the 'B' or 'QP', what it is ?
AnswerRe: A question about ShouldUseBase64Encoding.
Norbert Bietsch
7:53 13 Jul '09  
Hi,
The one and only reason is SPAM ranking of the mail message.
If you encode characters unnecessarily, SPAM ranking will get worse.
Most popular mail programs act like this.
Norbert
GeneralRe: A question about ShouldUseBase64Encoding.
xrascal
17:06 13 Jul '09  
thank you for your anwser.
but i send mail with microsoft outlook express,'Base64' is used for its all mail.what is the reason ?(i am in GB2312 environment.)
GeneralA bug should be fixed - System.Net.Mail.Attachment
xrascal
19:11 11 Jul '09  
dear Norbert Bietsch,
I am using your mailMergeLib. it is good.thank you very much!
But you didn't fixed the bugs of System.Net.Mail completely.
I found a bug when i send mail that contain attachment with MailMergeLib.

Let's do a test:
// -------------------------------------------------
private TransferEncoding _textTransferEncoding = TransferEncoding.SevenBit;
private TransferEncoding _binaryTransferEncoding = TransferEncoding.Base64;
private Encoding _characterEncoding = Encoding.GetEncoding("gb2312");
public Attachment GetAttachment()
{
string filePath = @"d:\中文文件.txt";
//string displayName = "=?gb2312?B?1tDOxM7EvP4udHh0?=";
string displayName = "中文文件.txt";
Attachment _attachment = new System.Net.Mail.Attachment(filePath);
_attachment.NameEncoding = _characterEncoding;

_attachment.ContentType.MediaType = "text/plain";
_attachment.ContentDisposition.Inline = false;
_attachment.ContentDisposition.FileName = "中文文件.txt";
//_attachment.ContentDisposition.FileName = "=?gb2312?B?1tDOxM7EvP4udHh0?=";
_attachment.ContentDisposition.CreationDate = System.IO.File.GetCreationTime(filePath);
_attachment.ContentDisposition.ModificationDate = System.IO.File.GetLastWriteTime(filePath);
_attachment.ContentDisposition.ReadDate = System.IO.File.GetLastAccessTime(filePath);

displayName = displayName.Replace(" ", string.Empty);
if (_attachment.ContentType.MediaType.ToLower().StartsWith("text/"))
{
_attachment.ContentType.CharSet = _characterEncoding.HeaderName;
_attachment.TransferEncoding = _textTransferEncoding;
}
else
{
_attachment.ContentType.CharSet = null;
_attachment.TransferEncoding = _binaryTransferEncoding;
}
return _attachment;
}

// -------------------------------------------------
The code will throw a FormatException:'MailHeaderFieldInvalidCharacter' .('在邮件标头中找到无效的字符。') because the "_attachment.ContentDisposition.FileName" is set by Chinese character.

if use the code :
_attachment.ContentDisposition.FileName = "=?gb2312?B?1tDOxM7EvP4udHh0?=";
that will work.
so , i think that is a bug.the _attachment.ContentDisposition.FileName should be encoded with Base64 or QP.
if it doesn't encoded , the code don't work when use the Chinese character , Korea character etc.
GeneralRe: A bug should be fixed - System.Net.Mail.Attachment
xrascal
23:25 15 Jul '09  
i have fixed the bug of Attachment.ContentDisposition.FileName .

in the BugFixer.cs,I add a method to encode the Attachment.Name:

public static void EncodeAttachmentFileName(ref String displayName, Encoding enc)
{
if (System.Environment.Version.Major != 2 || System.Environment.Version.MajorRevision != 0 || System.Environment.Version.Build != 50727)
return;

object mimePart = typeof(System.Net.Mail.AttachmentBase).InvokeMember("part",
BindingFlags.DeclaredOnly |
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.GetField, null, new Attachment(new System.IO.MemoryStream(new ASCIIEncoding().GetBytes(string.Empty)), "dummy"), null);

string _displayName = displayName;

bool IsAscii = Convert.ToBoolean(mimePart.GetType().BaseType.InvokeMember("IsAscii",
BindingFlags.DeclaredOnly | BindingFlags.FlattenHierarchy |
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.Static | BindingFlags.InvokeMethod,
null, mimePart, new object[] { _displayName, false }));

if (((_displayName != null) && (_displayName.Length != 0)) && !IsAscii)
{
if (enc == null)
{
enc = Encoding.GetEncoding("utf-8");
}

bool ShouldUseBase64Encoding = Convert.ToBoolean(mimePart.GetType().BaseType.InvokeMember("ShouldUseBase64Encoding",
BindingFlags.DeclaredOnly | BindingFlags.FlattenHierarchy |
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.Static | BindingFlags.InvokeMethod,
null, mimePart, new object[] { enc }));

_displayName = mimePart.GetType().BaseType.InvokeMember("EncodeHeaderValue",
BindingFlags.DeclaredOnly | BindingFlags.FlattenHierarchy |
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.Static | BindingFlags.InvokeMethod,
null, mimePart, new object[] { _displayName, enc, ShouldUseBase64Encoding }).ToString();

if (!ShouldUseBase64Encoding) displayName = _displayName.Replace(" ", "=20");
}
}

// ----------------------------------------------------
in the AttachmentBuilder.cs, i update the construct:

public AttachmentBuilder(FileAttachment fileAtt)
{
_attachment = new Attachment(fileAtt.Filename);
_attachment.ContentType.MediaType = fileAtt.MimeType;
_attachment.ContentDisposition.Inline = false;
string _DisplayName = fileAtt.DisplayName;
//use bugfixer to encode the _DisplayName.
Bugfixer.EncodeAttachmentFileName(ref _DisplayName, _characterEncoding);
_attachment.ContentDisposition.FileName = _DisplayName;
_attachment.ContentDisposition.CreationDate = System.IO.File.GetCreationTime(fileAtt.Filename);
_attachment.ContentDisposition.ModificationDate = System.IO.File.GetLastWriteTime(fileAtt.Filename);
_attachment.ContentDisposition.ReadDate = System.IO.File.GetLastAccessTime(fileAtt.Filename);
_attachment.NameEncoding = _characterEncoding;

_attachment.Name = _DisplayName;

if (_attachment.ContentType.MediaType.ToLower().StartsWith("text/"))
{
_attachment.ContentType.CharSet = _characterEncoding.HeaderName;
_attachment.TransferEncoding = _textTransferEncoding;
}
else
{
_attachment.ContentType.CharSet = null;
_attachment.TransferEncoding = _binaryTransferEncoding;
}

//Bugfixer.CorrectSevenBitTransferEncoding(_attachment);
Bugfixer.CorrectQuotedPrintableRFC2045compliant(_attachment, _characterEncoding);
}

// ----------------------------------------------------------
Laugh Laugh Laugh Laugh
JokeRe: A bug should be fixed - System.Net.Mail.Attachment
Norbert Bietsch
7:38 20 Jul '09  
Welcome to the crew of unknown MS freelancers Big Grin Big Grin
and thanks for your contribution. I'll add it to the next release which will also cover .NET 4.
GeneralProblems on some SMTP servers
Jerk_ua
1:06 4 Mar '09  
Hi Norbert
Sorry, I'm not powerful with English, I try to explain my problem as I can

I have a problem with specified SMTP server - smtp.sasktelmail.com. It's our corporate email provider.
I used CDOSYS library with .NET 1.1 web application to send emails using this SMTP

Now I implementing new version of that application under .NET 3.5
I desided to not use old CDOSYS lib, and tried to migrate to System.Net.Mail

But my SMTP provider refusing to send emails using it.
I tried to use different SMTP providers, and everything OK with them. So problem is not in settings, problem with SMTP server itself

I run into the problem deeply, and found that some users also experienced problems with this SMTP provider.

Let's assume changing SMTP provider is not a choice. Using CDOSYS library is 'escape way' in case if I not find solution. We are in negotiations with SMTP provider, but I'm not sure they will change something on their side


Here go technical details about the issue

Error itself:
The SMTP server requires a secure connection or the client was not authenticated. The server response was: 5.7.0 No AUTH command has been given.

Here's the link with user with same problems on same SMTP server

Now teltet information (bold text 0 is user input. Not bold text - returned from stmp provider)


======SUCCSES TO CONNECT TO SMTP SERVER =====
ehlo localhost 250-bgmpomr2.sasknet.sk.ca
250-8BITMIME
250-PIPELINING
250-DSN
250-ENHANCEDSTATUSCODES
250-HELP
250-XLOOP 3A35E0CA11D432DC00089F0FC11C0059
250-STARTTLS
250-AUTH PLAIN LOGIN
250-AUTH=LOGIN
250-ETRN
250 SIZE 0
auth login 334 VXNlcm5hbWU6
p3ltcAKw89== 334 UGFzc3dvcmQ6
ZcM67Bhyl4== 235 2.7.0 login authentication successful.


======THIS WAY MICROSOFT SYSTEM.NET.MAIL SENDS =====
ehlo localhost 250-bgmpomr2.sasknet.sk.ca
250-8BITMIME
250-PIPELINING
250-DSN
250-ENHANCEDSTATUSCODES
250-HELP
250-XLOOP 3A35E0CA11D432DC00089F0FC11C0059
250-STARTTLS
250-AUTH PLAIN LOGIN
250-AUTH=LOGIN
250-ETRN
250 SIZE 0
auth login p3ltcAKw89== 501 5.5.0 Invalid input (Invalid authentication protocol)



As you can see - Microsoft System.Net.Mail sends user login in 1 line with "auth login" command.
In most SMTP servers it works. But not with this specified SMTP question

So here is a question:

Is it possible to change to force sending "auth login" and user login as separate commands?
Some kind of 'hack' of something
Or better to return to CSOSYS ^^

I will glad for any reply. Thanks

modified on Wednesday, March 4, 2009 6:13 AM

AnswerHacking System.Net.Mail.SmtpClient to enforce a certain type of authentification
Norbert Bietsch
12:36 4 Mar '09  
Hi,
From my point of view this is an issue of the SMTP server, and you'll find it mentioned in numerous forums^ back to the year 2006. There are 2 ways for AUTH LOGIN: 1. Simple 2. login with User Name - and your SMTP server obviously only supports the first one.
References:
SMTP Protocol AUTH LOGIN Extension Specification (Microsoft)^ SMTP Service Extension for Authentication^
For the moment I cannot see a way to hack SmtpClient so that it will use the simple AUTH LOGIN. However, there are ways to force SmtpClient to use a completely different authentification method as defined in an (internal) enum System.Net.Mail.SupportedAuth (GGSAPI, Login, None, NTLM, WDigest).
References for hacks:
How to force System.Net.Mail to use AUTH LOGIN^ Hacking System.Net.Mail.SmtpClient^
Maybe this is a starting point for your own investigations.
Cheers, Norbert
GeneralUsing MailMergeLib in Web Service
Member 727407
6:31 1 Mar '09  
I have used MailMergeLib to create an email web service and it's been working fine.
Sometimes (In the past 6 months it has happened 2 times) the email is sent to the all recipients tons of times.
I really don't know if it is MailMergeLib problem or what.
In my ASP.NET code I call that web service to send email to the recipients. Yesterday only one of our customers got 1200 emails, all the same.
I reviewed all the code and could not find anythning wrong.
Could somebody please help me.
Thanks.
AnswerRe: Using MailMergeLib in Web Service
Norbert Bietsch
12:31 3 Mar '09  
Hi,
unless you pass more information about which way you have implemented MailMergeLib it's hard to give any advice. I'm running such a web service for about 2 years without any problem. So I would start to review the way your web service uses MailMergeLib, and then how your ASP.NET app uses the web service.
GeneralRe: Using MailMergeLib in Web Service
Member 727407
3:32 7 Mar '09  
Thanks for the reply.
Yesterday it happened again and I noticed the following information:
In windows event log at that time for calling the emailing web service the following error happened:

The operation has timed out.

Stack trace: at System.Web.Services.Protocols.WebClientProtocol.GetWebResponse(WebRequest request)
at System.Web.Services.Protocols.HttpWebClientProtocol.GetWebResponse(WebRequest request)
at Microsoft.Web.Services3.WebServicesClientProtocol.GetResponse(WebRequest request, IAsyncResult result)
at Microsoft.Web.Services3.WebServicesClientProtocol.GetWebResponse(WebRequest request)

Strange thing is that in IIS log file there is no entry at that time to that specific asmx file.
And also our provider said that at that time SMTP server had an oputage.

When everything is fine in netwotk and SMTP server, it works without any problem.
I really need help on this matter, Thank you.


Last Updated 6 Feb 2010 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010