Runtime: Add SmtpClient support

Created on 24 Feb 2015  Â·  32Comments  Â·  Source: dotnet/runtime

Is there any plans to add System.Net.Mail into aspnetcore50?

area-System.Net

Most helpful comment

I've posted a UserVoice request for this: Please vote for it here if you are in favor of having System.Net.Mail in CoreFX.

All 32 comments

Its not in our current plans but the code is available here if you are interested in porting it to make it work.

Thank you very much, I will try to port.

I was just directed here by @Eilon on https://github.com/aspnet/Home/issues/467. I have a need for basic SMTP capabilities in CoreCLR, and I was hoping to not need to roll my own TcpClient SMTP functionality. Can you say why it is thought that CoreCLR installs wouldn't need basic E-mail functionality? I thought it was a very popular API.

I've posted a UserVoice request for this: Please vote for it here if you are in favor of having System.Net.Mail in CoreFX.

Here is a code sample that I gathered from bits off the Net for a client on port 25 to get mail out through a network-based mail server (behind a firewall with the app so that sending unencrypted credentials isn't an issue).

Obviously, I didn't fix it up or anything ... It's not production quality, of course. I'll fix it up later. I just wanted to get this out now in case anyone needs a plan to get started with a little SMTP action.

If someone wants to wrap the stream in an sslStream to show how to make a secure connection, I hope they will post that sample ... and please vote at UserVoice for adding System.Net.Mail to CoreFX (CoreCLR).

using System.Net.Sockets;
using System.Text;
using System.IO;

using (var client = new TcpClient(server, port))
{
    using (var stream = client.GetStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream) { AutoFlush = true })
    {
        writer.WriteLine("HELO " + server);
        outputString += "1: " + reader.ReadLine() + " ";

        writer.WriteLine("AUTH LOGIN");
        outputString += "2: " + reader.ReadLine() + " ";

        string username = "[email protected]";
        var plainTextBytes1 = System.Text.Encoding.UTF8.GetBytes(username);
        string base64Username = System.Convert.ToBase64String(plainTextBytes1);
        writer.WriteLine(base64Username);
        outputString += "3: " + reader.ReadLine() + " ";

        string password = "PASSWORD_FOR_SERVICE_ACCOUNT";
        var plainTextBytes2 = System.Text.Encoding.UTF8.GetBytes(password);
        string base64Password = System.Convert.ToBase64String(plainTextBytes2);
        writer.WriteLine(base64Password);
        outputString += "4: " + reader.ReadLine() + " ";

        writer.WriteLine("MAIL FROM:<[email protected]>");
        outputString += "5: " + reader.ReadLine() + " ";

        writer.WriteLine("RCPT TO:<[email protected]>");
        outputString += "6: " + reader.ReadLine() + " ";

        writer.WriteLine("DATA");
        outputString += "7: " + reader.ReadLine() + " ";

        writer.WriteLine("From: \"Service Account\" <[email protected]>");
        writer.WriteLine("To: \"Somebody\" <[email protected]>");
        writer.WriteLine("Subject: Test message!");
        // Put one blank line after the Subject line, then start the message body.
        writer.WriteLine("");
        // Start the message body here
        writer.WriteLine("Hello Somebody,");
        writer.WriteLine("This is a test message.");
        writer.WriteLine("");
        writer.WriteLine("Candy’s dandy … but liquor is quicker!!");
        // Send a period to denote the end of the message body
        writer.WriteLine(".");
        outputString += "8: " + reader.ReadLine() + " ";

        writer.WriteLine("QUIT");
        outputString += "9: " + reader.ReadLine() + " ";
    }
}

Outputs:

1: 250 Hello.
2: 334 XXXXXXXXX
3: 334 XXXXXXXXX
4: 235 authenticated.
5: 250 OK
6: 250 OK
7: 354 OK, send.
8: 250 Queued (0.032 seconds)
9: 221 goodbye

... so you can do a .StartsWith() to make sure processing at each step went ok.

I think we can leave this issue open and up for grabs. Its totally reasonable to have this library and the code for it is already licensed appropriately here.

@dopare, were you going to try to port this or should we leave it up for grabs for others to pick up?

@Petermarcu I was trying to find the project where I can start to port. But didn't find appropriate one.

System.Net.Mail is quite a feat of engineering! However, it's great scope and depth look to make it a real bear to port. I took a look at where Roslyn chokes on just the System.Net.Mail namespace. There are ~2,400 issues, broken down below. It's good to see that string resources and logging knock out >1,000 of those. It's a big question mark how much work it would take to deal with that many external references. That last ~150 "Other" group is a real hodgepodge of little things to address.

| Issue | Count (# are approx) |
| --- | --- |
| String Resources | 860 |
| Assembly References | 744 |
| Logging | 246 |
| ValidationHelper | 92 |
| ResourceScope | 72 |
| GlobalLog | 68 |
| InvokeCallback | 32 |
| HeaderCollection | 26 |
| LazyAsyncResult | 18 |
| TlsStream | 14 |
| ConnectionPoolManager | 10 |
| SecurityAction | 8 |
| PermissionState | 6 |
| SecurityPermissionFlag | 6 |
| Other | ~150 |

And here is a sample for wrapping an SMTP connection into an sslStream for secure mail to places like Gmail. Again, this is just a kind of pseudo-code walk-through of what you must do to make the protocol work. This code will run fine, but it's far from production quality. Hopefully, this will tide CoreCLR folks over until we have something solid to use.

using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using System.IO;
using System.Net.Security;

const string server = "smtp.gmail.com";
const int port = 587;
using (var client = new TcpClient(server, port)) {
    using (var stream = client.GetStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream) { AutoFlush = true }) {
        outputString += "1: " + reader.ReadLine() + " ";

        writer.WriteLine("HELO " + server);
        outputString += "2: " + reader.ReadLine() + " ";

        writer.WriteLine("STARTTLS");
        outputString += "3: " + reader.ReadLine() + " ";

        using (var sslStream = new SslStream(client.GetStream(), false)) {

            sslStream.AuthenticateAsClient(server);

            using (var secureReader = new StreamReader(sslStream))
            using (var secureWriter = new StreamWriter(sslStream) { AutoFlush = true }) {

                secureWriter.WriteLine("AUTH LOGIN");
                outputString += "4: " + secureReader.ReadLine() + " ";

                string username = "YOUR_GMAIL_FULL_EMAIL_ADDRESS";
                var plainTextBytes1 = System.Text.Encoding.UTF8.GetBytes(username);
                string base64Username = System.Convert.ToBase64String(plainTextBytes1);
                secureWriter.WriteLine(base64Username);
                outputString += "5: " + secureReader.ReadLine() + " ";

                string password = "YOUR_GMAIL_PASSWORD";
                var plainTextBytes2 = System.Text.Encoding.UTF8.GetBytes(password);
                string base64Password = System.Convert.ToBase64String(plainTextBytes2);
                secureWriter.WriteLine(base64Password);
                outputString += "6: " + secureReader.ReadLine() + " ";

                secureWriter.WriteLine("MAIL FROM:<MESSAGE_FROM_EMAIL_ADDRESS>");
                outputString += "7: " + secureReader.ReadLine() + " ";

                secureWriter.WriteLine("RCPT TO:<MESSAGE_TO_EMAIL_ADDRESS>");
                outputString += "8: " + secureReader.ReadLine() + " ";

                secureWriter.WriteLine("DATA");
                outputString += "9: " + secureReader.ReadLine() + " ";

                secureWriter.WriteLine("From: \"MESSAGE_FROM_NAME\" <MESSAGE_FROM_EMAIL_ADDRESS>");
                secureWriter.WriteLine("To: \"MESSAGE_TO_NAME\" <MESSAGE_TO_EMAIL_ADDRESS>");
                secureWriter.WriteLine("EMAIL_SUBJECT");
                // Leave one blank line after the subject
                secureWriter.WriteLine("");
                // Start the message body here
                secureWriter.WriteLine("Hello Luke,");
                secureWriter.WriteLine("");
                secureWriter.WriteLine("Cuz! You gotta try Beck's Sapphire! It ROCKS!");
                secureWriter.WriteLine("");
                secureWriter.WriteLine("Later,");
                secureWriter.WriteLine("");
                secureWriter.WriteLine("Luke");
                // End the message body by sending a period
                secureWriter.WriteLine(".");
                outputString += "10: " + secureReader.ReadLine() + " ";

                secureWriter.WriteLine("QUIT");
                outputString += "11: " + secureReader.ReadLine() + " ";
            }
        }
    }
}

Will output:

1: 220 mx.google.com ESMTP xxxxxxxxxxxxxxxxxxxxx - gsmtp
2: 250 mx.google.com at your service
3: 220 2.0.0 Ready to start TLS
4: 334 xxxxxxxxxxxxxxx
5: 334 xxxxxxxxxxxxxxx
6: 235 2.7.0 Accepted
7: 250 2.1.0 OK xxxxxxxxxxxxxxxxxxx - gsmtp
8: 250 2.1.5 OK xxxxxxxxxxxxxxxxxxx  - gsmtp
9: 354 Go ahead xxxxxxxxxxxxxxxxxxx  - gsmtp
10: 250 2.0.0 OK xxxxxxxxxx xxxxxxxxxxxxxxxxxxx  - gsmtp
11: 221 2.0.0 closing connection xxxxxxxxxxxxxxxxxxx  - gsmtp

One final example in case the mail server doesn't support TLS but does support SSL (port 465), such as GoDaddy :toilet: mail servers :poop:.

using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using System.IO;
using System.Net.Security;

const string server = "smtpout.secureserver.net";
const int port = 465;
using (var client = new TcpClient(server, port)) {
    using (var stream = new SslStream(client.GetStream(), false)) {

        stream.AuthenticateAsClient(server);

        using (var reader = new StreamReader(stream))
        using (var writer = new StreamWriter(stream) { AutoFlush = true }) {
            outputString += "1: " + reader.ReadLine() + " ";

            writer.WriteLine("HELO " + server);
            outputString += "2: " + reader.ReadLine() + " ";

            writer.WriteLine("AUTH LOGIN");
            outputString += "3: " + reader.ReadLine() + " ";

            string username = "YOUR_ACCOUNT_EMAIL_ADDRESS";
            var plainTextBytes1 = System.Text.Encoding.UTF8.GetBytes(username);
            string base64Username = System.Convert.ToBase64String(plainTextBytes1);
            writer.WriteLine(base64Username);
            outputString += "4: " + reader.ReadLine() + " ";

            string password = "YOUR_ACCOUNT_PASSWORD";
            var plainTextBytes2 = System.Text.Encoding.UTF8.GetBytes(password);
            string base64Password = System.Convert.ToBase64String(plainTextBytes2);
            writer.WriteLine(base64Password);
            outputString += "5: " + reader.ReadLine() + " ";

            writer.WriteLine("MAIL FROM:<MESSAGE_FROM_EMAIL_ADDRESS>");
            outputString += "6: " + reader.ReadLine() + " ";

            writer.WriteLine("RCPT TO:<MESSAGE_TO_EMAIL_ADDRESS>");
            outputString += "7: " + reader.ReadLine() + " ";

            writer.WriteLine("DATA");
            outputString += "8: " + reader.ReadLine() + " ";

            writer.WriteLine("From: \"MESSAGE_FROM_NAME\" <MESSAGE_FROM_EMAIL_ADDRESS>");
            writer.WriteLine("To: \"MESSAGE_TO_NAME\" <MESSAGE_TO_EMAIL_ADDRESS>");
            writer.WriteLine("EMAIL_SUBJECT");
            // Leave one blank line after the subject
            writer.WriteLine("");
            // Start the message body here
            writer.WriteLine("Hello Luke,");
            writer.WriteLine("");
            writer.WriteLine("Cuz! You gotta try Beck's Sapphire! It ROCKS!");
            writer.WriteLine("");
            writer.WriteLine("Later,");
            writer.WriteLine("");
            writer.WriteLine("Luke");
            // End the message body by sending a period
            writer.WriteLine(".");
            outputString += "9: " + reader.ReadLine() + " ";

            writer.WriteLine("QUIT");
            outputString += "10: " + reader.ReadLine() + " ";
        }
    }
}

Outputs:

1: 220 p3plsmtpa12-09.prod.phx3.secureserver.net ESMTP
2: 250 p3plsmtpa12-09.prod.phx3.secureserver.net hello [xxx.xxx.xxx.xxx], secureserver.net
3: 334 xxxxxxxxxxxxxxx
4: 334 xxxxxxxxxxxxxxx
5: 235 ... authentication succeeded :: xxxxxxxxxxxxxxx
6: 250 <sender@sender_server.com> sender ok
7: 250 <recipient@recipient_server.com> recipient ok
8: 354 enter mail, end with "." on a line by itself
9: 250 xxxxxxxxxxxxxxx mail accepted for delivery
10: 221 p3plsmtpa12-09.prod.phx3.secureserver.net closing connection

As others have discovered, this may not be as straight forward to port as it may have first seemed. @SidharthNabar, can you speak to any plans that may be evolving around doing an official port of this as the rest of the networking stack moves onto Github?

I think porting https://github.com/jstedfast/MailKit (or at least the SMTP parts) might be way easier, since it already targets Windows 8.1 Universal, which afaik is a subset of .NET Core.

Yep - our team owns the networking APIs within .NET framework and System.Net.Mail is on our roadmap to bring to CoreCLR. Currently, we are working on some higher priority issues (including open-sourcing some other networking APIs that are supported in .NET Core). Unfortunately, we do not have a specific timeline right now.

As others have discovered, it will be challenging to move System.Net.Mail to CoreCLR since it has dependencies on the old managed networking stack (e.g. ServicePointManager) that is not yet supported in .NET Core. @akoeplinger is right - Windows 8.1 Universal .NET surface is a subset of .NET Core at least as far as networking is concerned, and MailKit may be a good short-term mitigation till we get System.Net.Mail to .NET Core.

@SidharthNabar Thank you for the update. I'm happy to hear it's on the roadmap. In the meantime, my ugly little SMTP bits that I posted here are working fine and will tide me over.

If anyone has any questions about MailKit, let me know, I'll be happy to answer any questions and provide any assistance that is needed.

@jstedfast I created an issue for this here :) https://github.com/jstedfast/MailKit/issues/212

(Regarding the solution proposed by @GuardRex)
Note, some hosts may require a "." ending, instead of the proposed ". QUIT".

                    writer.WriteLine("\r\n.\r\n");
                    outputString += "9: " + reader.ReadLine() + " ";

Came across this issue when (self-hosting) a project of mine, it would send mail fine in a localhost (debugging) environment, but would time out in a production environment.

@AdmirR Thanks for adding that note. The code I posted works with Google, GoDaddy, and hMailServer. You're right ... beyond that ... best wishes ... it's probably the Wild West when it comes to mail servers.

This is probably a good time to ask for an update on System.Net.Mail. @Petermarcu Is there any news on how its going with that effort? When we last left off, they said they had plans to do it, but they had no idea when they would get to it.

@SidharthNabar

If possible, you might want to mark this "planned" or such on UserVoice ... just so folks don't get too upset about it being on the backlog for a little while. It might spare you a few death threats. :smiley:

http://aspnet.uservoice.com/forums/252111-asp-net-vnext/suggestions/7648077-corefx-should-include-system-net-mail

This is still on our backlog and is planned to be supported. We are still working on finishing up previously planned namespaces such as System.Net.Sockets, System.Net.Security and others, which System.Net.Mail will likely depend on anyway. If this is something you would be interested in contributing to, please comment here and we'd be happy to engage with you.

Thanks
Sid

Thanks @GuardRex - I posted a reply on the UserVoice as well :-)

For anyone interested in MailMessage / SmtpClient equivalent support, I've just finished porting MimeKit and MailKit over to CoreCLR.

I'll be cleaning up the code a bit more this weekend and expect to publish new nugets by the end of the weekend.

... and as soon as this new System.Net.Sockets issue is worked out, I'll update the examples here if you just need a quick-and-dirty method.

for anyone who needs to send email on dnxcore50, @jstedfast just got his MailKit/MimeKit projects working https://github.com/jstedfast/MailKit/issues/212

nugets are available now.

If you are as grateful for that news as I am please join me in sending a donation to his pledgie page http://www.pledgie.com/campaigns/29300

Thanks @jstedfast - Really appreciate your contribution here! We will continue to keep System.Net.Mail for .NET Core in our plans, but it's really nice to see .NET Core developers unblocked thanks to you :)

Glad to help :)

This is an update on using TcpClient and SslStream. I've had trouble talking to GoDaddy servers lately. Some 2012R2 boxes working and others refusing to play nice with GoDaddy servers (SCHANNEL and cipher suite problems perhaps). However, Gmail seems to be working. Here's the latest code for hitting up Gmail for mail forwarding on port 587. Keeping in mind, of course, this isn't production quality. It's just a prototype to show you the ropes. Enjoy!

[EDIT] Note: You may need to go into the Google Account security settings and allow less secure applications to use the account. You may also need to hit this page: https://accounts.google.com/DisplayUnlockCaptcha while logged in to the Google account, which I think disables captcha requirements for the application logging in (I think).

[EDIT] I'm updating the code to correctly fix a problem with the forwarded mail. The prior code I had here didn't have a proper "Reply-To" setup so that when you click to reply, it would fill the Gmail account address into the To field. With the "Reply-To" explicitly set to the original senders name and address, the To field in a message reply will be filled in correctly.

``` c#
private async Task SendMailOnPort587(
Visitor visitor,
string server,
int port,
string smtpServerAccountName,
string smtpServerUsername,
string smtpServerPassword,
string destinationEmailAddress,
string destinationEmailName,
string destinationSubjectLine)
{
string outputString = string.Empty;
try
{
using (var client = new TcpClient()) {
await client.ConnectAsync(server, port);
using (var stream = client.GetStream())
using (var reader = new StreamReader(stream))
using (var writer = new StreamWriter(stream) { AutoFlush = true }) {
outputString += "1: " + reader.ReadLine() + " ";

            writer.WriteLine("HELO " + server);
            outputString += "2: " + reader.ReadLine() + " ";

            writer.WriteLine("STARTTLS");
            outputString += "3: " + reader.ReadLine() + " ";

            using (var sslStream = new SslStream(client.GetStream(), false)) {

                await sslStream.AuthenticateAsClientAsync(server);

                using (var secureReader = new StreamReader(sslStream))
                using (var secureWriter = new StreamWriter(sslStream) { AutoFlush = true }) {

                    secureWriter.WriteLine("AUTH LOGIN");
                    outputString += "4: " + secureReader.ReadLine() + " ";

                    string username = smtpServerUsername;
                    var plainTextBytes1 = System.Text.Encoding.UTF8.GetBytes(username);
                    string base64Username = System.Convert.ToBase64String(plainTextBytes1);
                    secureWriter.WriteLine(base64Username);
                    outputString += "5: " + secureReader.ReadLine() + " ";

                    string password = smtpServerPassword;
                    var plainTextBytes2 = System.Text.Encoding.UTF8.GetBytes(password);
                    string base64Password = System.Convert.ToBase64String(plainTextBytes2);
                    secureWriter.WriteLine(base64Password);
                    outputString += "6: " + secureReader.ReadLine() + " ";

                    secureWriter.WriteLine("MAIL FROM:<" + smtpServerUsername + ">");
                    outputString += "7: " + secureReader.ReadLine() + " ";

                    secureWriter.WriteLine("RCPT TO:<" + destinationEmailAddress + ">");
                    outputString += "8: " + secureReader.ReadLine() + " ";

                    secureWriter.WriteLine("DATA");
                    outputString += "9: " + secureReader.ReadLine() + " ";

                    secureWriter.WriteLine("From: \"" + smtpServerAccountName + "\" <" + smtpServerUsername + ">");
                    secureWriter.WriteLine("To: \"" + destinationEmailName + "\" <" + destinationEmailAddress + ">");
                    secureWriter.WriteLine("Subject: " + destinationSubjectLine);
                    secureWriter.WriteLine("Reply-To: \"" + visitor.Name + "\" <" + visitor.Email + ">");
                    secureWriter.WriteLine("");
                    secureWriter.WriteLine("Attn: " + visitor.Department);
                    secureWriter.WriteLine("");
                    secureWriter.WriteLine("Name: " + visitor.Name);
                    secureWriter.WriteLine("E-mail: " + visitor.Email);
                    secureWriter.WriteLine("Phone: " + visitor.Phone);
                    secureWriter.WriteLine("");
                    secureWriter.WriteLine(visitor.Message);
                    secureWriter.WriteLine("");
                    secureWriter.WriteLine("VIRUS Warning! DO NOT click links in E-mail messages!");
                    secureWriter.WriteLine("");
                    secureWriter.WriteLine(".");
                    outputString += "10: " + secureReader.ReadLine() + " ";

                    secureWriter.WriteLine("QUIT");
                    outputString += "11: " + secureReader.ReadLine() + " ";
                }
            }
        }
    }
    serverReply = outputString;
}
catch (Exception ex)
{
    serverReply = serverReply + " Exception: " + ex.Message + " outputString: " + outputString;
}
return serverReply;

}

`serverReply` if all goes well ...

1: 220 smtp.gmail.com ESMTP g81sm28380166pfj.1 - gsmtp
2: 250 smtp.gmail.com at your service
3: 220 2.0.0 Ready to start TLS
4: 334 VXNxcx5hbWUx
5: 334 xGFzc3dvxmx6
6: 235 2.7.0 Accepted
7: 250 2.1.0 OK g81sm28380166pfj.1 - gsmtp
8: 250 2.1.5 OK g81sm28380166pfj.1 - gsmtp
9: 354 Go ahead g81sm28380166pfj.1 - gsmtp
10: 250 2.0.0 OK 1455403723 g81sm28380166pfj.1 - gsmtp
11: 221 2.0.0 closing connection g81sm28380166pfj.1 - gsmtp
```

Any update on inclusion of the the namesapce? I 'm guessing it won't make RC2 at this point.

Correct ... it won't. I did put my extremely hacky barely prototype Email package up. It works well with Gmail servers. It's at least the basis of something that might tide some folks over to MailKit being updated for RC2 or the release of a new System.Net.Mail ... https://github.com/GuardRex/Email~~ Moved to https://github.com/GuardRex/GuardRex.RexMail and https://www.nuget.org/packages/GuardRex.RexMail/

Closing this as dotnet/corefx#11792 will track this work.

How anybody could contemplate building a web targeted development framework and consider packaged SMTP support as unnecessary is beyond me. Jus sayin.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

aggieben picture aggieben  Â·  3Comments

jchannon picture jchannon  Â·  3Comments

omariom picture omariom  Â·  3Comments

bencz picture bencz  Â·  3Comments

Timovzl picture Timovzl  Â·  3Comments