2009-01-23

Sending email in C# - Maybe there *is* a way

Almost a year ago, I was bemoaning the fact that sending email is hard. Well, recently, my client went from a PC to a Mac, and he needed some help installing the program in the virtual XP session. When I was there, I set up the Outlook Express client so that the PDF statements could be emailed out from the virtual XP world. I figured that, since I know his email client, perhaps I could find something specific to that client for sending email.

Googling around for sending email from C# with Outlook Express, I eventually came across this CodeProject article: Programmatically adding attachments to emails in C# and VB.NET. I took the code and dropped it into my project pretty much as-is. My development machine is now a Windows Vista laptop, but this code worked just fine, starting up a Windows Live Mail window with the file attached.

There were a couple issues. One, if Live Mail wasn't started first, then I'd get an error from the MAPI call — apparently the client needs to be initialized before the call is made. (The MAPI call was starting Live Mail, but Live Mail did not appear to be in a ready state then the email was sent to it.) The second is that the SendMailPopup call (which is the one I want) blocks until the email message is closed (i.e. sent or discarded). The comments in that article, however, mention a method to split that call onto another thread, so I should be able to prepare and open several emails at once.

So I think I will create a configuration parameter that will allow the toggling of attempting to use MAPI. It should work, but it'll be preferable to have something to fall back on.

Incidentally, a discussion thread over at Channel 9 pretty much summed up the problem as such:

  • If you have Outlook, you can do it. (Use Outlook automation)
  • If you have a MAPI profile (e.g. Outlook Express), you can do it.
  • If you have SMTP, you can do it. (System.Web.Mail standard)

To which I'll add:

  • If you don't know what you have, you can try it. (mailto: link, attachments not supported)

Just for kicks, I tried this on my PC at work, which is running XP and has the Mozilla Thunderbird email client installed. It worked beautifully, whether I had Thunderbird running before I called SendMailPopup or not. I don't know if Outlook would also respond to this code or not. (I'll have to try the compiled program on my desktop with Outlook when I'm done.) If so, it could simplify things, as much as they can be considered "simplified" in what seems to be a complicated situation.

At least I'm in the fortunate situation that I'm writing this for a known, single client and a known configuration.

I do wonder why there's not a managed MAPI wrapper though. For my own convenience, I'm gathering emails into a collection of System.Web.Mail.MailMessage objects before I try sending them. Why is there no System.Web.Mail.Mapi.Send method? Maybe a System.Web.Mail.Mapi.IsClientRunning method, and so on.

3 comments:

Spencer said...

You could just *write* a managed MAPI wrapper. ;)

Yakko Warner said...

Eh, more or less, that's what the code from CodeProject amounts to, which should suffice for this project.

As long as nothing goes wrong with all this "marshaling to an unmanaged DLL" crap...

Can said...

hi
i read the codeproject article and i was using this class in my .Net application it was working fine with outlook express but i have some problems with windows live mail. Sendmailpopup method doesn't working it just only open windows live mail application and nothing happens. Also i try to take this process to another thread but nothing change.Sometimes after windows live mail opening it gives general mapi failure error [2]. I could send mail messages directly but this is not what i want, do you have any idea why is this happening? because i am stuck. Any help would be appreciated.

here is my code:

private void btnComposeEmail_Click(object sender, EventArgs e)
{
//EmailPopup();
ActionThread = new Thread(EmailPopup);
ActionThread.Priority = ThreadPriority.Normal;
ActionThread.SetApartmentState(ApartmentState.STA);
ActionThread.Start();

}

private void EmailPopup()
{
try
{
//GridAttachments
MAPI mapi = new MAPI();
Boolean IsErrorExist = false;
if (gridOfferReport.Rows.Count > 0)
{
foreach (DataGridViewRow dgr in gridOfferReport.Rows)
{
mapi.AddAttachment(Convert.ToString(dgr.Cells["OfferReportPath"].Value));
}

}
else
IsErrorExist = true;

foreach (DataGridViewRow dgr in gridAttachments.Rows)
{
if (dgr.Cells["AttachmentChkColumn"].Value != null)
{
if (((bool)dgr.Cells["AttachmentChkColumn"].Value) == true)
{
mapi.AddAttachment(Convert.ToString(dgr.Cells["AttachmentPath"].Value));
}

}
}


//GridTo
foreach (DataGridViewRow dgr in gridTo.Rows)
{
if (dgr.Cells["ToChkColumn"].Value != null)
{
if (((bool)dgr.Cells["ToChkColumn"].Value) == true)
{
if (Convert.ToString(dgr.Cells["ToMail"].Value) != "")
mapi.AddRecipientTo(Convert.ToString(dgr.Cells["ToMail"].Value));
else
IsErrorExist = true;


}

}
}

//GridCC

foreach (DataGridViewRow dgr in gridCC.Rows)
{
if (dgr.Cells["CCChkColumn"].Value != null)
{
if (((bool)dgr.Cells["CCChkColumn"].Value) == true)
{
if (Convert.ToString(dgr.Cells["CCMail"].Value) != "")
mapi.AddRecipientCC(Convert.ToString(dgr.Cells["CCMail"].Value));
else
IsErrorExist = true;
}

}
}
if (IsErrorExist == true)
{
MessageBox.Show("Seçtiğiniz kişilerden en az birinin email adresi bulunmamaktadır veya Teklif oluşturmadınız.");
mapi = null;
}
else
mapi.SendMailPopup(myOffer._Project.Name, myOffer._Project.Subject);
}
catch (Exception ex)
{

}
finally
{

ActionThread.Abort();
ActionThread.Join();
}

}