.NET Best Method to Send Email (System.Net.Mail has issues)
Asked Answered
G

7

18

This seems to be pretty straight forward. I need to send email from some ASP.NET applications. I need to do this consistently without strange errors and without CPU utilization going through the roof. I'm not talking about mass emailing, just occasional emails.

System.Net.Mail appears to be horribly broken. The SmtpClient does not issue the Quit command (it may be because Microsoft(R) is not interested in following specifications), therefore a connection is left open. Therefore, if someone tries to email before that connection finally closes, you can get errors from the SMTP Server about too many connections open. This is a bug that Microsoft(R) is completely uninterested in fixing. See here:

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=146711

Also, if you look around some suggest to use this code to solve this problem:

smtpClient.ServicePoint.MaxIdleTime = 1;
smtpClient.ServicePoint.ConnectionLimit = 1;

Ok, yes that does "solve" the problem of connections being left open. However, this consistently, try it on a server if you like, causes the CPU on which the process (in this case w3wp.exe) is running to jump and remain at 100% until your application pool is recycled. For whatever reason, the thread that runs mscorwks.dll!CreateApplicationContext is the culprit.

This has the very nice side effect that if you are running on a web host that frowns on sustained 100% CPU usage, you will get your application pool disabled. So this is not as trivial as some suggest.

So my question is what to do? What I need to do is so simple; however getting those "too many connections open" errors is not acceptable and nor is the 100% CPU usage. I don't want to purchase a third party component, not because I'm cheap, but I buy enough components and MSDN subscription that it seems crazy to have to shell out $100-$300 for simple SMTP functionality.

I read that setting the MaxIdleTime higher can help but I'm skeptical of that. I don't want to risk my app pool being disabled just because Microsoft doesn't want to follow the SMTP specification.

Edit: I looked at quiksoft.com components, however it does not support SMTP authentication and it costs $500. There's got to be a solution to this problem.

Ghirlandaio answered 30/5, 2009 at 18:7 Comment(2)
According to the link you provided it appears the bug has been fixed. Is it possible you're missing a service pack?Obola
I saw where it said "resolved." I didn't see any other notes however. I have updated the server to the latest SP and all of that. When I have seen that before usually it seems MS just means they won't fix it. They also have the "workaround" I mentioned listed on there. It could be much more clear.Ghirlandaio
P
22

I faced the same CPU utilization problem with the settings described. I ended up opening a ticket with Microsoft to determine the cause of the problem. The CPU utilization problem lies in the ServicePoint class. Internally in the ServicePoint class, there is a timer that runs every (MaxIdleTime/2) milliseconds. See the problem? By changing the MaxIdleTime value to 2, the CPU utilization will drop down to normal levels.

Pentlandite answered 27/7, 2009 at 21:26 Comment(2)
I don't quite "see the problem", however this fix works. Interestingly, the default MaxIdleTime is 100000. When we set it to 1, we spiked CPU to near 100%. Setting MaxIdleTime to 2 dropped CPU to normal levels, just like you reported.Letta
@Scott Ferguson: The problem is that the timer will run at (1/2) milliseconds, which in integer division will evalutate to… every 0 milliseconds. Whoopsie.Schutt
C
30

In .NET 4.0, SmtpClient is now disposable. The SMTP QUIT command is issued upon disposal such as when used in a using block.

Cavin answered 29/11, 2010 at 18:40 Comment(5)
This is the correct answer, and I have tested it myself. Please +1 this comment.Plashy
This is the correct answer, you can just use SmtpClient in a using block.Floatplane
I've tried this but it doesn't seem to work as advertised. I'm still getting the "#4.x.2 Too many messages for this session" error. Anyone else having the same problem?Selfdeprecating
Using SmtpClient's disposal (properly) with Amazon's AWS SES, solved a ton of frequent sending errors for me. In my case, the code was originally built under .NET 2.0, upgraded to 4.x and then migrated to AWS. The SMTP host was swapped out for SES and it was throwing all sorts of errors because of the smtp session being held open. A simple using block solved it.Tricornered
This problem was reintroduced in the .NET Core library even though the documentation incorrectly states that it will send the QUIT command. See github.com/dotnet/corefx/issues/34868 for more details.Transept
P
22

I faced the same CPU utilization problem with the settings described. I ended up opening a ticket with Microsoft to determine the cause of the problem. The CPU utilization problem lies in the ServicePoint class. Internally in the ServicePoint class, there is a timer that runs every (MaxIdleTime/2) milliseconds. See the problem? By changing the MaxIdleTime value to 2, the CPU utilization will drop down to normal levels.

Pentlandite answered 27/7, 2009 at 21:26 Comment(2)
I don't quite "see the problem", however this fix works. Interestingly, the default MaxIdleTime is 100000. When we set it to 1, we spiked CPU to near 100%. Setting MaxIdleTime to 2 dropped CPU to normal levels, just like you reported.Letta
@Scott Ferguson: The problem is that the timer will run at (1/2) milliseconds, which in integer division will evalutate to… every 0 milliseconds. Whoopsie.Schutt
P
2

I've always used Quiksoft's EasyMail .NET components with no issues at all.

Product home page: http://www.quiksoft.com/emdotnet/

They also have a free version of the component if you only need to send out e-mails:

http://www.quiksoft.com/freesmtp/

Pachydermatous answered 30/5, 2009 at 18:13 Comment(4)
I agree. We switched to Quiksoft after numerous problems with System.Net.Mail and have never looked back.Microseism
This is interesting, I will look at this. If anyone else has other suggestions, I'd appreciate it as well!Ghirlandaio
The free version doesn't support SMTP authentication with a username and password. It costs $500 for this.Ghirlandaio
Quiksoft is gone.Tish
S
2

We have used hMailserver with great success. The configuration can take a while to get used to but it has been a great -- and free -- mail server product.

If you want to roll your own (which I did years ago when I was having a bear of a time with CDONTS), you can start with the code below and customize to your heart's content. It uses the TcpClient to create a TCP connection directly to the mailserver. Not that I'd recommend this when there are so many established and debugged solutions, but I found this very useful for debugging and determining where the problem was with the prefab MS mail components.

    private void Send_Email() 
    {
        #region Email Sending Function
        string strMail = "";

        try 
        {
            // See RFC821 http://www.faqs.org/rfcs/rfc821.html for more specs
            // TcpClient is an abstraction of a TCP Socket connection
            TcpClient myTCP = new TcpClient();

            // Connect to the mail server's port 25
            myTCP.Connect(mailserver, 25);

            // Open a network stream which sends data to/from the TcpClient's socket
            System.Net.Sockets.NetworkStream ns = myTCP.GetStream();

            // The data to send to the mail server, basically a raw SMTP mail message
            strMail = "HELO\n";
            strMail += "MAIL FROM:[email protected]\n";
            strMail += "RCPT TO:" + recipient + "\n";
            strMail += "DATA\n";
            strMail += "Subject: mySubject\n";
            strMail += "To:" + recipient + "\n";
            strMail += "From: \"From Real Name\" <[email protected]>\n";
            strMail += "\n";
            strMail += " ---------------------------------------\n";
            strMail += "Name:     " + txtName.Text + "\n";
            strMail += "Address1: " + txtAddress1.Text + "\n";
            strMail += "Address2: " + txtAddress2.Text + "\n";
            strMail += "City:     " + txtCity.Text + "\n";
            strMail += "State:    " + txtState.Text + "\n";
            strMail += "Zip:      " + txtZip.Text + "\n";
            strMail += "Email:    " + txtEmail.Text + "\n";
            strMail += "Dealer:   " + txtDealer.Text + "\n";
            strMail += " ---------------------------------------\n";
            strMail += "THIS IS AN AUTOMATED EMAIL SYSTEM. DO NOT REPLY TO THIS ADDRESS.\n";
            strMail += "\n.\n";

            // Defines encoding of string into Bytes (network stream needs
            // an array of bytes to send -- can't send strings)
            ASCIIEncoding AE = new ASCIIEncoding();
            byte[] ByteArray = AE.GetBytes(strMail);

            // send the byte-encoded string to the networkstream -> mail server:25
            ns.Write(ByteArray, 0, ByteArray.Length);

            //ns.Read(ByteArray, 0, ByteArray.Length);
            //lblStatus.Text = ByteArray.ToString();

            // close the network stream
            ns.Close();

            // close the TCP connection
            myTCP.Close();
        }
        catch(Exception ex) 
        {
            throw new Exception("Couldn't send email: <p>" + ex.Message);
        }

        #endregion

    }
Splay answered 4/11, 2010 at 6:25 Comment(0)
C
1

While I haven't had any specific problems with System.Net.Mail so far, you can always use the older System.Web.Mail API which is a wrapper for CDOSYS.

Canady answered 30/5, 2009 at 18:12 Comment(4)
As far as not having trouble with System.Net.Mail, have you tried sending an email from one of your pages and then right away sending another one from the page? Your server might allow more than one connections. I bet if you run TCPView, your connection remains open for long time. Also, doesn't System.Web.Mail rely on that local SMTP service? I need to send email through a remote host that has properly configured MX etc to avoid being marked as spam.Ghirlandaio
I didn't deny that it has problems. I just said I didn't encounter it. I haven't used it much (just simple "contact us" forms). System.Web.Mail doesn't rely on local SMTP server and supports using remote SMTP servers. I have used that extensively. It's not very elegant but has been working very well.Canady
Oh, I wasn't being argumentative, just curious. :)Ghirlandaio
I wasn't either. Just wanted to assure that I didn't endorse it.Canady
T
1

I've used Quicksoft in the past and have no complaints. Another thing you can try is switching the SMTP configuration to use a pickup folder instead of sending using the network which should get around the "it doesn't send QUIT" issue.

Topnotch answered 31/5, 2009 at 13:4 Comment(0)
S
-1

I send most of my mail using a Sproc. I can even attach a file.


CREATE PROCEDURE [dbo].[sendMail_With_CDOMessage]  
    @to VARCHAR(64), 
    @CC VARCHAR(1024)='',
    @BCC VARCHAR(1024)='',
    @subject VARCHAR(500)='', 
    @body VARCHAR(8000)='' ,
    @from VARCHAR(64),
    @filename VARCHAR(255)='',
    @priority INT = 0
AS  
BEGIN  
    SET NOCOUNT ON  

    DECLARE  
        @handle INT,  
        @return INT,  
        @s VARCHAR(64),  
        @sc VARCHAR(1024),  
        @up CHAR(27),   
        @server VARCHAR(255)  

    SET @s = '"http://schemas.microsoft.com/cdo/configuration/'  

    SELECT  
        @s = 'Configuration.Fields(' + @s,  
        @up = 'Configuration.Fields.Update',
        @server = 'smtp.yourdomain.com' 



    EXEC @return = sp_OACreate 'CDO.Message', @handle OUT  
    SET @sc = @s + 'sendusing").Value'  
    EXEC @return = sp_OASetProperty @handle, @sc, '2'  
    SET @sc = @s + 'smtpserver").Value'  
    EXEC @return = sp_OASetProperty @handle, @sc, @server  
    EXEC @return = sp_OAMethod @handle, @up, NULL  
    EXEC @return = sp_OASetProperty @handle, 'To', @to  
    EXEC @return = sp_OASetProperty @handle, 'CC', @CC 
    EXEC @return = sp_OASetProperty @handle, 'BCC', @BCC 
    EXEC @return = sp_OASetProperty @handle, 'From', @from  
    EXEC @return = sp_OASetProperty @handle, 'Subject', @subject  
    EXEC @return = sp_OASetProperty @handle, 'HTMLBody', @body    
    EXEC @return = sp_OASetProperty @handle, 'Priority', 'cdoHigh'  

    IF @filename IS NOT NULL  
        EXEC @return = sp_OAMethod @handle, 'AddAttachment', NULL, @filename  

    EXEC @return = sp_OAMethod @handle, 'Send', NULL  
    IF @return  0  
    BEGIN  
        PRINT 'Mail failed.'  
        IF @from IS NULL  
            PRINT 'From address undefined.'  
        ELSE  
            PRINT 'Check that server is valid.' 
    END 
    ELSE  
        PRINT 'Mail sent.'  

    EXEC @return = sp_OADestroy @handle  
END  

Sneakbox answered 1/6, 2009 at 18:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.