Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • JavaScript • Angular
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

SmtpClient and Locked File Attachments


:P
On this page:

Got a note a couple of days ago from a client using one of my generic routines that wraps SmtpClient. Apparently whenever a file has been attached to a message and emailed with SmtpClient the file remains locked after the message has been sent. Oddly this particular issue hasn’t cropped up before for me although these routines are in use in a number of applications I’ve built.

The wrapper I use was built mainly to backfit an old pre-.NET 2.0 email client I built using Sockets to avoid the CDO nightmares of the .NET 1.x mail client. The current class retained the same class interface but now internally uses SmtpClient which holds a flat property interface that makes it less verbose to send off email messages.

File attachments in this interface are handled by providing a comma delimited list for files in an Attachments string property which is then collected along with the other flat property settings and eventually passed on to SmtpClient in the form of a MailMessage structure.

The jist of the code is something like this:

/// <summary>
/// Fully self contained mail sending method. Sends an email message by connecting 
/// and disconnecting from the email server.
/// </summary>
/// <returns>true or false</returns>
public bool SendMail()
{
    if (!this.Connect())
        return false;

    try
    {
        // Create and configure the message 
        MailMessage msg = this.GetMessage();

        smtp.Send(msg);

        this.OnSendComplete(this);

    }
    catch (Exception ex)
    {
        string msg = ex.Message;
        if (ex.InnerException != null)                
            msg = ex.InnerException.Message;
        
        this.SetError(msg);
        this.OnSendError(this);

        return false;
    }
    finally
    {
        // close connection and clear out headers
// SmtpClient instance nulled out
this.Close(); } return true; }
/// <summary>
/// Configures the message interface
/// </summary>
/// <param name="msg"></param>
protected virtual MailMessage GetMessage()
{
    MailMessage msg = new MailMessage();            

    msg.Body = this.Message;
    msg.Subject = this.Subject;
    msg.From = new MailAddress(this.SenderEmail, this.SenderName);

    if (!string.IsNullOrEmpty(this.ReplyTo))
        msg.ReplyTo = new MailAddress(this.ReplyTo);

    // Send all the different recipients
    this.AssignMailAddresses(msg.To, this.Recipient);
    this.AssignMailAddresses(msg.CC, this.CC);
    this.AssignMailAddresses(msg.Bcc, this.BCC);

    if (!string.IsNullOrEmpty(this.Attachments))
    {
        string[] files = this.Attachments.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
        foreach (string file in files)
        {
            msg.Attachments.Add(new Attachment(file));
        }
    }

    if (this.ContentType.StartsWith("text/html"))
        msg.IsBodyHtml = true;
    else
        msg.IsBodyHtml = false;

    msg.BodyEncoding = this.Encoding;
    … additional code  omitted

    return msg;
}

Basically this code collects all the property settings of the wrapper object and applies them to the SmtpClient and in GetMessage() to an individual MailMessage properties. Specifically notice that attachment filenames are converted from a comma-delimited string to filenames from which new attachments are created.

The code as it’s written however, will cause the problem with file attachments not being released properly. Internally .NET opens up stream handles and reads the files from disk to dump them into the email send stream. The attachments are always sent correctly but the local files are not immediately closed.

As you probably guessed the issue is simply that some resources are not automatcially disposed when sending is complete and sure enough the following code change fixes the problem:

// Create and configure the message 
using (MailMessage msg = this.GetMessage())
{
    smtp.Send(msg);

    if (this.SendComplete != null)
        this.OnSendComplete(this);
    // or use an explicit msg.Dispose() here
}

The Message object requires an explicit call to Dispose() (or a using() block as I have here) to force the attachment files to get closed.

I think this is rather odd behavior for this scenario however. The code I use passes in filenames and my expectation of an API that accepts file names is that it uses the files by opening and streaming them and then closing them when done. Why keep the streams open and require an explicit .Dispose() by the calling code which is bound to lead to unexpected behavior just as my customer ran into? Any API level code should clean up as much as possible and this is clearly not happening here resulting in unexpected behavior. Apparently lots of other folks have run into this before as I found based on a few Twitter comments on this topic.

Odd to me too is that SmtpClient() doesn’t implement IDisposable – it’s only the MailMessage (and Attachments) that implement it and require it to clean up for left over resources like open file handles. This means that you couldn’t even use a using() statement around the SmtpClient code to resolve this – instead you’d have to wrap it around the message object which again is rather unexpected.

Well, chalk that one up to another small unexpected behavior that wasted a half an hour of my time – hopefully this post will help someone avoid this same half an hour of hunting and searching.

Resources:

Posted in .NET  

The Voices of Reason


 

Nicholas Piasecki
December 18, 2009

# re: SmtpClient and Locked File Attachments

Yep, been there, done that. Nice to know I wasn't the only one who's been burned! =)

Kearns
December 18, 2009

# re: SmtpClient and Locked File Attachments

Weird, the same thing just happened to me. Out of the blue, the SmtpClient started locking attachment files. I just added the .Dispose() to correct it, but it really threw me for a loop, since the code worked perfectly well previously.

Jason Haley
December 19, 2009

# Interesting Finds: December 19, 2009

Interesting Finds: December 19, 2009

George
December 19, 2009

# re: SmtpClient and Locked File Attachments

the SmtpClient is not closing TCP/IP connection also after it's done sending email.
It closes only after timeout... try to send 10 email with Gmail using SmtpClient and you will see that after 5-6 successful emails the Gmail server starts to refuse connections form your server because you have too many connections open. Wait for 10 minutes and it will start working again because open connections had timed out and were closed by the server... I think there is a KB article about that but no fix so far.

So "CDO nightmares of the .NET 1.x" as you put it is the solution i use since it works properly.. despite Email solution in .NET >=2.0

And i am not talking about mass mailing... 5 emails in 10 minutes is what my commerce site does when we shipping orders. Every time shipping label is printed customer gets an email that his package was shipped.

George
December 19, 2009

# re: SmtpClient and Locked File Attachments

PS: found a link to KB (it's not a KB actually)
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=337557

And the workaround posted is not working.

PimpThisBlog.com
January 15, 2010

# SmtpClient and Locked File Attachments

Thank you for submitting this cool story - Trackback from PimpThisBlog.com

Craig
February 11, 2010

# re: SmtpClient and Locked File Attachments

Nice one. Manually disposing of my mailmessage sorted it.

Gazza
August 25, 2010

# re: SmtpClient and Locked File Attachments

The only way to be sure that you have released the locks on the file attachments is to dispose of the actual attachments once you have finished with them.

For example.

// Send the Email message via SMTP Client
objClient.Send(objMail);

// Clean up attachments
foreach (var attachment in objMail.Attachments)
{
attachment.Dispose();
}

// Clean up SMTP Client resources
objClient.Dispose()

Vishnoo Rath
June 30, 2014

# re: SmtpClient and Locked File Attachments

This is a life saver. Had me wondering where I was going wrong, until I saw that the original coder had not disposed the MailMessage object.

Thanks!

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2021